SDK Index Previous page Next page

DDEサーバーを作る


DDEの解説

アプリケーション間でデータをやりとりするには、ファイル マッピングや動的データ交換(DDE)を使います。プログラムの難易度からいえばファイルマッピングの方が遙かに簡単ですが、汎用性という点ではDDEが勝つと思います。
DDEは、サーバーとクライアントの間でデータをやりとりし、サーバーはクライアントからの要求を受け取ってその要求に応答し、必要であればクライアントにデータを返します(この関係は、サーバークライアント型ネットワークと全く同じものです)。DDEを使うプログラムは通常DDEを直接使わずに動的データ交換管理ライブラリ(DDEML)を使います。

DDEを使うためにはサーバーに、今から君とデータのやりとりをするぞ、という事をサーバーに知らせなければなりません。そのとき、クライアント側はサービス名とトピック名をDDEMLに渡してそのことをサーバーに知らせます。DDEMLは、受け取ったサービス名を持つサーバーにトピック名を渡します。サーバー側では、受け取ったトピック名を調べてデータ交換を始めるかどうかを決めます。
サービス名やトピック名を渡すときには、文字列のポインターではなく文字列ハンドルというものを作って渡します。またこれに限らずDDEでは常にハンドルを用いてデータ交換を行います。

また、DDEでデータを受け渡しするときにも、データを直接渡さずにデータハンドルを作ってそのハンドルを使います。

データをサーバーに送る場合には
(クライアント側)
  データハンドルを作る
  データハンドルを渡す
(サーバー側)
  データハンドルを受け取る
  データハンドルからデータの内容を得る
  処理する
  データハンドルを解放する
  終了
というような流れになります。

DDEでは何が出来るか

DDEを使うと次のようなことが出来ます。

要求トランザクションデータをサーバーに要求する
ポークトランザクションデータをサーバーに送る
アドバイズトランザクションサーバーデータを定期的に受け取る
実行トランザクションコマンドライン文字列を渡してサーバーに何かをさせる

このうち、アドバイズトランザクションを使うと、サーバーとクライアントの間にアドバイズループというものが作られてサーバーからデーターを受け取り続けることが出来ます。
これは、サーバーの処理が並列で行われているとき、その処理が終わるまで待つときなどに使います。

DDEサーバーを作る

それでは、クライアントから受けたトランザクションの種類を表示するようなDDEサーバーを作ってみます。

トランザクションの表示にはリストボックスを使用しますが、使いやすくするためにいくつかの関数を定義します。

#include <windows.h>

#define	ID_LIST	100


extern	HANDLE Instance;
HWND	hlist;
CHAR	listbuf[300],*listadr;

/*画面のクリア*/

void ClearConsole(void)
{
	listadr=listbuf;
	SendMessage(hlist,LB_RESETCONTENT,0,0);
}

/*コンソール画面の作成*/

HWND CreateConsole(HWND hwnd)
{
	hlist = CreateWindowEx( 0,
				  "LISTBOX",
				  NULL,
				  WS_VISIBLE | WS_CHILD |
				  WS_VSCROLL | LBS_NOINTEGRALHEIGHT,
				  0, 0, 0, 0,
				  hwnd,
				  (HMENU)ID_LIST,
				  Instance,
				  NULL);
	ClearConsole();

	return hlist;
}

/*文字列の表示*/

void dispstring(char *p)
{
	while(*p!='\0'){
		if(*p=='\n'){
			*listadr='\0';
			SendMessage(hlist,LB_ADDSTRING,0,(LPARAM)listbuf);
			SendMessage(hlist,WM_VSCROLL,(WPARAM)SB_LINEDOWN,0);
			listadr=listbuf;
		}
		else{
			*listadr++=*p;
		}
		p++;
	}
}

/*printfのかわり*/

void dprintf(char  *format,...)
{
	char	buffer[256];
	va_list argptr;

	va_start( argptr, format );
	wvsprintf( buffer, format, argptr );
	va_end( argptr );
	dispstring(buffer);
}
このプログラムは、console.cという名前で保存しておいて下さい。

次に、DDE関連の関数を定義します。
初めは初期化関数です。

CHAR	szAppName[]	  = "DDE server";
CHAR	szClassName[] = "DDE server class";
CHAR	szServiceName[] = "DDE test";
CHAR	szTopicName[]	= "DDE topic";
CHAR	szItemName[]   = "advise item";
HSZ		hszServiceName,hszTopicName,hszItemName;

DWORD		ddeinst;

DWORD  InitDdeServer(void)
{
	UINT		ret;
	HDDEDATA	ret2;
	DWORD		inst=0;

	ret=DdeInitialize(&inst,DdeCallback,
			CBF_SKIP_REGISTRATIONS,
			0);
	if(ret!= DMLERR_NO_ERROR) return 0;
	hszServiceName=DdeCreateStringHandle(inst,szServiceName,CP_WINANSI);
	hszTopicName=DdeCreateStringHandle(inst,szTopicName,CP_WINANSI);
	hszItemName=DdeCreateStringHandle(inst,szItemName,CP_WINANSI);
	ret2=DdeNameService(inst,hszServiceName,0,DNS_REGISTER);
	if(!ret2){
		UninitDdeServer(inst);
		return 0;
	}
	return inst;
}
この関数では初めにDdeInitialize関数を使ってDDEを初期化しています。このとき第2引数にコールバック関数のポインターを指定します。DDEの初期化に成功すると、使用する文字列のハンドルを作っています。今後このハンドルとコールバック関数に送られてきたハンドルの値を比較することでトピックなどを判断します(同じ文字列は同じハンドルを持つため)。そして、そのハンドルを用いてDDEMLにサーバーとして登録しています。

次は、DDEの終了関数です。

void UninitDdeServer(DWORD inst)
{
	if(inst){
		DdeFreeStringHandle(inst,hszServiceName);
		DdeFreeStringHandle(inst,hszTopicName);
		DdeFreeStringHandle(inst,hszItemName);
		DdeUninitialize(inst);
	}
}
この関数では、全ての文字列ハンドルを解放した後、DdeUninitialize関数でDDEを終了させています。

最後に、DDEコールバック関数です。DDEのトランザクションメッセージは全てこの関数に送られます。

HDDEDATA CALLBACK DdeCallback(UINT uType,UINT uFmt,HCONV hconv,HSZ hsz1,HSZ hsz2,
									HDDEDATA hdata,DWORD dwData1,DWORD dwData2)
{
	switch(uType){
	uTypeにメッセージの種類が入っているのでswitch文を使ってそれぞれの処理に分岐する。
		case XTYP_CONNECT:
			if(hsz1!=hszTopicName){
			hsz1にトピック名のハンドルが入っている。
				dprintf("CONNECT failuer");
				return (HDDEDATA)FALSE;
			}
			dprintf("CONNECT\n");
			return (HDDEDATA)TRUE;

		case XTYP_DISCONNECT:
			dprintf("DISCONNECT\n");
			return (HDDEDATA)TRUE;

		case XTYP_REQUEST:
			if(hsz1!=hszTopicName || hsz2!=hszItemName){
			hsz2には項目名が入っている。この項目名から要求されているデータを識別する
				dprintf("REQUEST failure\n");
				return (HDDEDATA)NULL;
			}
			dprintf("REQUEST\n");
			return DdeCreateDataHandle(ddeinst,advdata,2,0,hszItemName,CF_TEXT,0);

		case XTYP_POKE:
			if(hsz1!=hszTopicName || hsz2!=hszItemName){
			hsz2には項目名が入っている。この項目名から要求されているデータを識別する
				dprintf("POKE failure\n");
				DdeFreeDataHandle(hdata);
				return DDE_FNOTPROCESSED;
			}
			{
				char	*p;

				p=DdeAccessData(hdata,NULL);
				dprintf("POKE:%s\n",p);
				DdeUnaccessData(hdata);
				DdeFreeDataHandle(hdata);
				ハンドルはサーバーが解放する
			}
			return (HDDEDATA)DDE_FACK;

		case XTYP_ADVSTART:
			if(hsz1!=hszTopicName || hsz2!=hszItemName){

				dprintf("AVSTART failure\n");
				return (HDDEDATA)FALSE;
			}
			advdata[0]='0';
			advdataという配列の値をアドバイストランザクションのデータとするのでそれを初期化する。

			dprintf("ADVSTART\n");
			return (HDDEDATA)TRUE;

		case XTYP_ADVSTOP:
			dprintf("ADVSTOP\n");
			return (HDDEDATA)TRUE;

		case XTYP_ADVREQ:
			このメッセージはサーバーがDdePostAdvise関数を呼んだときに送られてくる。
			このメッセージを受けたときにサーバーはクライアントにデーターを送る。
			if(hsz1!=hszTopicName || hsz2!=hszItemName){
				dprintf("ADVREQ failure\n");
				return (HDDEDATA)NULL;
			}
			dprintf("ADVREQ\n");
			return DdeCreateDataHandle(ddeinst,advdata,2,0,hszItemName,CF_TEXT,0);
			advdata配列のハンドルを作ってそれを返す。

		case XTYP_EXECUTE:
			if(hsz1!=hszTopicName){
				dprintf("EXECUTE failure\n");
				DdeFreeDataHandle(hdata);
				return DDE_FNOTPROCESSED;
			}
			{
				char	*p;

				p=DdeAccessData(hdata,NULL);
				ハンドルからデータのアドレスを得る
				dprintf("EXECUTE:%s\n",p);
				DdeFreeDataHandle(hdata);
			}
			return (HDDEDATA)DDE_FACK;


		default:
			return (HDDEDATA) NULL;
	}
	return (HDDEDATA)NULL;
}
DDEに必要な関数はこれで全てです。

それでは、うまく動くか確かめてみます。
プログラム全体は ここ にあります。

DDEのテスト

今回はクライアントプログラムを作っていないのでその代わりに秀丸エディターのマクロを使います。 プログラムを実行した後で、次のマクロを実行して下さい。
ddeinitiate "DDE test","DDE topic";
if( !result ) goto exit;

$data=dderequest("advise item");

ddepoke "advise item","poke data";

ddestartadvice "advise item",$data;
if( !result ){
	message "advice error";
	goto exit;
}
ddewaitadvice  "advice item", 9999;
ddestopadvice  "advise item";

ddeexecute "dde execute";
ddeterminate;
exit:
マクロを実行すると

CONNECT
REQUEST
POKE:poke data
ADVSTART
と表示されます。
このときマクロは、DDEはアドバイスループに入ってサーバーからデータが送られてくるのを待っています。サーバーからデータを送るには、サーバーのウインドウの大きさをマウスで変えます。そうすることでサーバーがデーターを送くります。うまくいくと

ADVREQ
ADVSTOP
EXECUTE:dde execute
DISCONNECT
と続けて表示されます。


to sdk prev next