/**************/ /* ddeser.c */ /**************************************************************************** PROGRAM: ddeser PURPOSE: dde-palvelimen yksinkertainen käyttö AUTHOR: Vesa Lappalainen 13.8.1993 Kari Heikkilä -92 ( alkuperäinen ei-aliohjelmaversio ) USAGE: ( toistaiseksi tuetaan vain CF_TEXT -muotoa) Projektiin laitettava ddeser.c ja clipboard.c. Esimerkiksi EXCELIN kaavassa: =AUTOLDDE|LKM!HA ^ ^ ^ | | | item | | topic | service Jotta oma ohjelma osaisi vastata Excelin DDE-kyselyyn (Excel on tällöin asiakas, client), pitää omaan ohjelmaan tehdä muutamia lisäyksiä. Pääasiallisesti on kaksi vaihtoehtoa. A) Annetaan ddeser-kirjaston pitää huolta yhdistelmistä. B) Pidetään itse kirjaa topic/item yhdistelmistä, joihin ostataan vastata. C) Edellisten sekoitus. A-kohta voidaan jakaa vielä kahteen alakohtaan: a) Palvelun tuloksena halutaan antaa jonkin dialogi-ikkunan sisältö, joka voidaan selvittää kutsulla GetWindowText. Tällöin voidaan ddeser-kirjaston antaa huolehtia kaikesta automaattisesti. b) Huolehditaan itse palveluista Kaikkein helpointa käyttö on kohdan Aa-mukaisesti. Seuraavassa käsitellään kutakin tapaa erikseen: Aa) DDESER-kirjasto huolehtii palveluista: ------------------------------------------ 1) (pää)ohjelmaan lisättävä alustus ja lopetus: InitDDEServer(hInstance,SERVICE,NULL); ... CloseDDEServer(); SERVICE on palvelun nimi (yleensä sama kuin ohjelman nimi) 2) Kun palvelut halutaan aktiiviseksi, lisätään (vaikka heti InitDDEServer-rivin jälkeen: AddAutoTextService(topic1,item1,ID1,hWnd); AddAutoTextService(topic2,item2,ID2,hWnd); ... Nyt luetaan automaattisesti dialogin hWnd kenttiä ID1,... hWnd voi tietysti olla erikin kullekin alustukselle. 3) Jos halutaan automaattinen päivitys, niin täytyy kutsua InformDDEChangeSN(ID1); aina kun kentän ID1 sisältö on muuttunut. 4) Jos halutaan leikekirjaan tieto miten tehdään linkki esimerkiksi kenttään ID1, kutsutaan LinkToClipboardSN(hWnd,ID1) Nyt esim. Excelissä linkki voidaan muodostaa valitsemalla Edit/Paste Link. Siis minimissään 2-3 lisäystä ohjelmaan riittää! Ab) DDESER-kirjasto huolehtii palveluista, itse palvellaan ----------------------------------------------------------- 1) Kuten edellä 2) Kun palvelut halutaan aktiiviseksi, lisätään (vaikka heti InitDDEServer-rivin jälkeen: AddDDEService(topic1,item1,fmt1,ID1,MyFunc1,NULL); AddDDEService(topic2,item2,fmt2,ID2,MyFunc2,NULL); ... 3) & 4) kuten edellä 5) Kirjoitetaan funktiot MyFunc1 ja MyFunc2 char *MyFunc1(int n) jotka palauttavat palvelua n vastaavan osoittimen haluttuun dataan. B) Huolehditaan itse topic/item yhdistelmistä: ---------------------------------------------- 1) (pää)ohjelmaan lisättävä alustus ja lopetus: InitDDEServer(hInstance,SERVICE,MyGetData); ... CloseDDEServer(); SERVICE on palvelun nimi (yleensä sama kuin ohjelman nimi) 2) Kirjoitettava funktiot: char *MyGetData(char *topic, char *item, UINT wFmt,int query); Jos query!=0 on tehtävänä on vastata kysymykseen, löytyykö aihetta topic ja asiaa item ja formaattia wFmt. Jos löytyy palautetaan osoitin joka on !=NULL, Jollei löydy, niin osoitin NULL. Jos query==0, palautetaan topic/item/wFmt:tä vastaava merkkijono, jos sellainen on olemassa TAI NULL jollei ole. Merkkijono palautetaan osoitteena staattiseen tai globaaliin muuttujaan, jonka DDESER kopioi heti itselleen eikä muuta osoitteen sisältöä. 3) Jos DDE-yhteydessä halutaan ilmoittaa asiakkaalle kaikki muutokset, pitää vielä kirjoittaa muutoksen tapahtuttua kutsu: InformDDEChange(topic,item); 4) Jos halutaan kopioida DDE-linkin vaatima tieto leikekirjaan, lisätään sopivaan kohtaan (esim kommennon Edit/Copy alle) kutsu: LinkToClipboard(hWnd,topic,item); Käyttöehdotuksia: ----------------- Tapa Aa on helpoin jos on vähän ikkunoita, joista palvelua halutaan antaa. Tapa Ab sopii jos on vähän palveluita, mutta ne ovat jotakin, joka ei ole valmiina jossakin dialogin ikkunassa. Esimerkiksi kahden ikkunan summa. Tapa B sopii, jos palveluita on suuri määrä, esimerksiksi taulukkolaskimessa on palveluina kaikki solut ja tällöin item-nimet ovat muotoa R1C1,R1C2 jne. Tällöin omaan palvelufunktioon on helpointa tehdä tulkki, joka osaa palauttaa oikean asian. Kaikki tavat Aa, Ab ja B ovat mahdollisia sotkea samassa ohjelmassa. Esimerkiksi: dialogin kaksi kentää HA ja KA tavalla Aa kenttien summa HA+KA tavalla Ab monimutkaiset tiedot (???) tavalla B 1) Alustus: InitDDEServer(hInstance,SERVICE,MyGetData); AddDDEService("Lkm","ha",0,HA,NULL,hWnd); AddDDEService("Lkm","ka",0,KA,NULL,hWnd); AddDDEService("Lkm","sum",0,SUM,MySum,NULL); 2) Kirjoitetaan monimutkaiset tiedot käsittelevä funktio: char *MyGetData(char *topic, char *item, UINT wFmt,int query) { static char result[100]; if ( !IsLeagal(topic,item,wFmt) ) return NULL; if ( query ) return result; FillResult(topic,item,wFmt,result,sizeof(result)); return result; } 3) Kirjoitetaan summan käsittelevä funktio ( WindInt(int n) oletetaan palauttavan tavalla tai toisella n:en ikkunan sisällön kokonaislukuna): char *MySum(int n) { static char csum[20]; int sum = WindInt(HA) + WindInt(KA) ; sprintf(csum,"%d\r\n",sum); return csum; } 4) Kun HA tai KA ikkunan sisältö muuttuu, niin: InformDDEChangeSN(HA); InformDDEChangeSN(SUM); InformDDEChange(DIFFICULT_TOPIC,DIFFICULT_ITEM); ============ Aliohjelmat: (ddeser.c, ddeser.h) ============ BOOL InitDDEServer(HINSTANCE hInst,char *service,tServerFunc func) - alustus BOOL CloseDDEServer( void ) - lopetus void InformDDEChange(char *topic, char *item) - muutoksen ilmoittaminen void InformDDEChangeSN(int sn) - muutoksen ilmoittaminen DWORD LinkToClipboardSN(HWND hWnd,int sn) DWORD LinkToClipboard(HWND hWnd,char *topic,char *item) - leikekirjaan itse staattinen tieto, DDE-linkin vaatima tieto (jotta saadaan kaava esim. taulukkolaskennassa Edit/Paste Link) sekä Clipboard vieweria varten vielä näyttöön tuleva teksti. tService *FindService(char *topic, char *item, UINT wFmt); - palautetaan topic/item/wFmt -palvelua vastaavan tietueen osoitin tService *FindServiceSN(int sn); - palautetaan palvelunumeroa sn vastaavan tietueen osoitin Tietueen sisältö ks. ddeser.h. Näutä käytetään mm. jos tarvitsee saada ikkunan kahva tai topic/item selville kesken palvelun. int DeleteDDEService(char *topic,char *item, int sn); - poistaa kaikki palevelut topic/item ja/tai ne joilla on palvelunumero sn. Jos topic==NULL, niin vain sn tutkitaan, jos sn == 0, niin vain topic/item tutkitaan. int AddAutoTextService(topic,item,sn,hWnd) - makro joka kutsuu seuraavaa funktiota wFmt = CF_TEXT ja ServerFuncN = NULL int AddDDEService(char *topic,char *item, UINT wFmt, int sn, tServerFuncN ServerFuncN, HWND hWnd); - lisätään palvelujen listaan palvelu jolla on annettu topic/item/wFmt jos yhdistelmä löytyy jo ennestään, korvataan sen tiedot. Palvelusta käytetään jatkossa numeroa sn. Kun palvelua pyydetään, niin toimitaan seuraavan algortimin mukaan: A) Jos palvelu löytyy listasta b) jos ServerFuncN annettu, kutsutaan sitä a) jos hWnd annettu ja CF_TEXT-pyyntö, niin otetaan ikkunan hWnd kentän sn sisältö c) palautetaan virheteksti B) Jos funktio annettu InitDDEServer-kutsussa, kutsutaan sitä. Huomattakoon, että samaa topic/item-yhdistelmää voi vastata useita eri wFmt-muotoja. ==================== Itsekirjoitettavat: (oma.c) ==================== const char *MyGetData(const char *topic, const char *item, UINT wFmt, int query) - palauttaa NULL jos topic & item & wFmt ei löydy, muuten palauttaa topic & itemia vastaavan merkkijonon jos query==0 tai minkä tahansa != NULL osoittimen jos query != 0 ****************************************************************************/ #include #include /* DDE Management Library */ /* d */ #include #include "ddeser.h" #include "clipboar.h" /***************************************************************************/ /* DDE: */ /***************************************************************************/ typedef struct { DWORD idInst; /* Ohjelman DDECallback funktion esiintymä*/ HCONV hConvApp; /* Keskustelukahva */ FARPROC lpDdeProc; /* DDECallback funktion osoitin */ HSZ hszService; /* Merkkijonokahva palvelun nimeen */ HDDEDATA reg; /* Onko palvelu kirjattu */ tServerFunc ServerFunc; /* Topic, Item, format -> vast.merk.jono */ tService Services[MAX_SERVICE]; /* Taulukko palveluista */ } tDDEServer; static tDDEServer DDE = {0,0,0,0,0,0}; /***************************************************************************/ /* Service taulukon käsittely alkaa */ /***************************************************************************/ /*-------------------------------------------------------------------------*/ /* Taulukon muodon (miten indeksoitu) tietävät funktiot: */ #define ST DDE.Services /***************************************************************************/ static tService *GetDDEService(int n) /* Indeksia n vastaava palvelu */ { if ( n < 0 || MAX_SERVICE <= n ) return NULL; return ST+n; } /***************************************************************************/ static int TableIndexSN(int sn) /* Etsitään palvelunumeroa sn vastaavan palvelun indeksi */ /* -1 = ei löydy */ { int i; for (i=0; itopic[0] = 0; Ser->sn = 0; return 0; } /***************************************************************************/ static int PutService(tService *Ser,tService *NewSer) /* Laitetaan palveluksi Ser palvelu joka on osoitteessa NewSer */ { if ( !Ser ) return 1; if ( NewSer->topic[0] == 0 ) return 2; *Ser = *NewSer; return 0; } /***************************************************************************/ static int AddNewService(tService *NewSer) /* Lisätään palvelu NewSer (tehdään kopio) */ { tService *Ser=FindServiceSN(0); if ( !Ser ) return 1; return PutService(Ser,NewSer); } /***************************************************************************/ /* Service taulukon käsittely loppuu */ /***************************************************************************/ /***************************************************************************/ static BOOL IsItem(HSZ hszTopic, HSZ hszItem,UINT wFmt) /* Löytyykö palvelua */ { char szItem[20],szTopic[20]; DdeQueryString(DDE.idInst, hszTopic, szTopic, sizeof(szTopic), 0); DdeQueryString(DDE.idInst, hszItem, szItem, sizeof(szItem), 0); /* Tarkistetaan, löytyykö palvelu taulukoiduista */ if ( FindService(szTopic,szItem,wFmt) ) return TRUE; /* Onko palvelufunktiota ja jos on, niin mitä se palauttaa. */ if ( DDE.ServerFunc ) return DDE.ServerFunc(szTopic,szItem,wFmt,1) != NULL; return FALSE; } /***************************************************************************/ static const char *GetDDEDataC(const char *szTopic, const char *szItem,UINT wFmt) /* Palautetaan palvelua vastaava merkkijono. ** Jollei pystytä palvelemaan, palautetaan NULL ** A) Ensin etsitään voidaanko palvelu löytää palvelutaulukosta ** b) Jos palvelulle funktio, kutsutaan sitä ** a) Jos ikkunasta tiedot, niin otetaan ne ** B) Kutsutaan yleistä palvelufunktiota */ { tService *Ser; /* Tarkistetaan, löytyykö palvelu taulukoiduista */ if ( ( Ser = FindService(szTopic,szItem,wFmt) ) != NULL ) { if ( Ser->ServerFuncN ) /* Jos palvelulle oma funktio, niin kuts. sitä */ return Ser->ServerFuncN(Ser->sn); if ( Ser->hWnd && wFmt == CF_TEXT ) { /* Jos dialogin kenttä, luet. se */ static char buf[200]; HWND hsWnd = GetDlgItem(Ser->hWnd,Ser->sn); if ( !hsWnd ) return "No dialog item"; GetWindowText(hsWnd,buf,sizeof(buf)); return buf; } } /* Onko palvelufunktiota ja jos on, niin mitä se palauttaa. */ if ( DDE.ServerFunc ) return DDE.ServerFunc(szTopic,szItem,wFmt,0); return NULL; } /***************************************************************************/ static const char *GetDDEData(HSZ hszTopic, HSZ hszItem,WORD wFmt) /* Kuten edellä, mutta merkkijonot kahvojen perusteella */ { char szItem[20],szTopic[20]; DdeQueryString(DDE.idInst, hszTopic, szTopic, sizeof(szTopic), 0); DdeQueryString(DDE.idInst, hszItem, szItem, sizeof(szItem), 0); return GetDDEDataC(szTopic,szItem,wFmt); } /***************************************************************************/ static DWORD MyError(char *szVirheMessu) { MessageBox(NULL,szVirheMessu,NULL, MB_APPLMODAL | MB_OK | MB_ICONEXCLAMATION); return 0; } /***************************************************************************/ static DWORD MyInfo(const char *szInfoMessu) { MessageBox(NULL, szInfoMessu, "DDE", MB_APPLMODAL | MB_OK); return 0; } /***************************************************************************/ void InformDDEChange(const char *topic, const char *item) /* Välitetään DDE-palvelimelle tieto, että tieto on muuttunut */ { HSZ hszItem,hszTopic; hszItem = DdeCreateStringHandle(DDE.idInst, item, 0); hszTopic = DdeCreateStringHandle(DDE.idInst, topic, 0); DdePostAdvise(DDE.idInst, hszTopic, hszItem ); DdeFreeStringHandle(DDE.idInst, hszTopic ); DdeFreeStringHandle(DDE.idInst, hszItem ); } /***************************************************************************/ void InformDDEChangeSN(int sn) { tService *Ser = FindServiceSN(sn); if ( Ser ) InformDDEChange(Ser->topic,Ser->item); } /***************************************************************************/ HDDEDATA EXPENTRY _export DDECallbackServer ( WORD wType, /* transaktion tyyppi */ WORD wFmt, /* datan tyyppi (clipboard type) */ HCONV hConv, /* keskustelukahva */ HSZ hsz1, /* merkkijonokahva, kts. ko. transaktiosta */ HSZ hsz2, /* " */ HDDEDATA hData, /* kahva globaaliin muistilohkoon */ DWORD dwData1, /* transaktiokohtaista dataa */ DWORD dwData2 ) /* " */ { #pragma argsused char szBuffer[100]; const char *p; switch ( wType ) { case XTYP_CONNECT: /* Asiakas haluaa avata keskustella */ /* hsz1 = topic */ /* hsz2 = service */ if ( hsz2 == DDE.hszService ) return (HDDEDATA)TRUE; /* Meillä on sopiva keskusteluaihe, ok */ else return (HDDEDATA)FALSE; /* Ei sopivaa aihetta */ case XTYP_ADVSTART: /* Asiakas haluaa tietää jokaisesta muutoksesta */ case XTYP_ADVSTOP: /* Asiakas ei enää halua tietää ... */ /* hsz1 = topic */ /* hsz2 = item */ /* Tarkistetaan, että pyydetty toiminto löytyy */ if ( IsItem(hsz1,hsz2,wFmt) ) /* Jos toiminto löytyi, palautetaan asiakkaalle myönteinen vastaus */ return (HDDEDATA)TRUE; return (HDDEDATA)FALSE; /* Ei löydy */ case XTYP_REQUEST: /* Asiakas pyytänyt dataa */ case XTYP_ADVREQ: /* Data muuttunut palvelimella */ /* hsz1 = topic */ /* hsz2 = item */ if ( ( p = GetDDEData(hsz1,hsz2,wFmt) ) == NULL ) return NULL; /* Ei löydy toimintoa */ strcpy(szBuffer,p); return DdeCreateDataHandle(DDE.idInst,(void *)p,strlen(p)+1,0L,hsz2,CF_TEXT,0); case XTYP_CONNECT_CONFIRM: /* Asiakas hyväksynyt keskusteluyhteyden */ /* hsz1 = topic */ /* hsz2 = service */ DDE.hConvApp = hConv; break; case XTYP_DISCONNECT: /* Asiakas halusi lopettaa keskustelun */ /* hsz1 = topic */ /* hsz2 = service */ // MyInfo( "DDE asiakas sulki yhteyden." ); DDE.hConvApp = NULL; break; case XTYP_ERROR: /* Jokin virhe */ MyInfo( "DDE virhe." ); break; } return NULL; } /***************************************************************************/ static int CloseDDEServerError(int ret) { CloseDDEServer(); return ret; } /***************************************************************************/ int InitDDEServer(HINSTANCE hInst,const char *service,tServerFunc ServerFunc) { (void)hInst; DDE.lpDdeProc = MakeProcInstance( (FARPROC) DDECallbackServer, hInst ); if ( !DDE.lpDdeProc ) return 1; if ( DdeInitialize(&DDE.idInst,(PFNCALLBACK)DDE.lpDdeProc, APPCLASS_STANDARD | CBF_FAIL_EXECUTES | CBF_FAIL_POKES,0L ) ) { MyError( "DDE:n alustus ei onnistu!" ); return CloseDDEServerError(2); } DDE.hszService = DdeCreateStringHandle(DDE.idInst, service, 0); if ( !DDE.hszService ) return CloseDDEServerError(3); /* Rekisteröidään palvelu */ DDE.reg = DdeNameService(DDE.idInst, DDE.hszService, NULL, DNS_REGISTER); if ( !DDE.reg ) return CloseDDEServerError(4); DDE.ServerFunc = ServerFunc; #ifdef MYINFO MyInfo( "DDE alustettu." ); #endif return 0; } /***************************************************************************/ int CloseDDEServer( void ) { if ( DDE.hConvApp ) { DdeDisconnect(DDE.hConvApp); DDE.hConvApp = NULL; } if ( DDE.reg ) { /* Poistetaan palvelu rekisteristä */ DdeNameService(DDE.idInst,DDE.hszService, NULL, DNS_UNREGISTER); DDE.reg = 0; } if ( DDE.hszService) { DdeFreeStringHandle(DDE.idInst, DDE.hszService); DDE.hszService = 0; } if ( DDE.idInst ) { DdeUninitialize(DDE.idInst); DDE.idInst = 0; } if ( DDE.lpDdeProc ) { (void)FreeProcInstance(DDE.lpDdeProc); DDE.lpDdeProc = NULL;} #ifdef MYINFO MyInfo ( "DDE suljettu." ); #endif return 0; } /***************************************************************************/ DWORD LinkToClipboard(HWND hWnd,const char *topic,const char *item) /* Leikekirjaan linkki, näyttöteksti ja itse staattinen tieto. */ { DWORD ret; char pText[100]; const char *p; int plen; if ( !DDE.idInst ) return -4; if ( !OpenClipboard(hWnd) ) return -1; DdeQueryString(DDE.idInst,DDE.hszService, pText, sizeof(pText)-1, 0); strcat(pText,"|"); plen=strlen(pText); strncat(pText,topic,sizeof(pText)-plen-1); strcat(pText,"!"); plen=strlen(pText); strncat(pText,item,sizeof(pText)-plen-1); ret = AddLinkToClipboard(NULL,pText,"Link",TRUE); p = GetDDEDataC(topic,item,CF_TEXT); if ( p && !ret ) ret = AddToClipboard(NULL,CF_TEXT,p,strlen(p)+1,FALSE); CloseClipboard(); return ret; } /***************************************************************************/ DWORD LinkToClipboardSN(HWND hWnd,int sn) /* Laitetaan leikekirjaan palvelua sn vastaavat tiedot linkkitietoineen */ { tService *Ser = FindServiceSN(sn); if ( !Ser ) return -5; return LinkToClipboard(hWnd,Ser->topic,Ser->item); } #define STRCPY(s,p) { strncpy(s,p,sizeof(s)); s[sizeof(s)-1] = 0; } /***************************************************************************/ int AddDDEService(const char *topic,const char *item, UINT wFmt, int sn, tServerFuncN ServerFuncN, HWND hWnd) /* Lisätään listaan uusi palvelu. Jos palvelu on ennestään ** (sama topic,item,wFmt), niin muutetaan sen muut tiedot lisäämättä mitään. ** sn == 0 ei ole sallittu, vaan palauttaa virheen. */ { tService NewSer,*Ser=FindService(topic,item,wFmt); if ( !sn ) return -1; STRCPY(NewSer.topic,topic); STRCPY(NewSer.item,item); if ( wFmt == 0 ) wFmt = CF_TEXT; NewSer.wFmt = wFmt; NewSer.ServerFuncN = ServerFuncN; NewSer.hWnd = hWnd; NewSer.sn = sn; if ( Ser ) return PutService(Ser,&NewSer); return AddNewService(&NewSer); } /***************************************************************************/ int DeleteDDEService(const char *topic,const char *item, int sn) /* Poistetaan kaikki palvelut joissa topic,item (jos ne != NULL) ** Poistetaan kaikki palvelut, joiden numero sn (>0). ** Jos topic == NULL, poistetaan vain sn:än perusteella. ** Jos sn == 0, poistetaan vain topic,item:in perusteella. */ { tService *Ser; int count = 0; if ( topic && item ) while ( ( Ser = FindService(topic,item,0xffff) ) != NULL ) if ( DeleteDDEServiceS(Ser) == 0 ) count++; if ( sn ) while ( ( Ser = FindServiceSN(sn) ) != NULL ) if ( DeleteDDEServiceS(Ser) == 0 ) count++; return count; }