EternalWindows
DDE / サーバーの作成

これまでExcelを対象にDDEクライアントを作成してきましたが、今回はDDEサーバーを作成することにします。 DDEサーバーの役割は、クライアントから送られるDDEメッセージ(WM_DDE_INITIATEなど)を適切に処理することであり、 アプリケーション名やトピック名、アイテム名なども決定しなければなりません。 アプリケーション名については基本的に自由に決めて構いませんが、 トピック名については必ずsystemという名前のトピックをサポートしなければなりません。 こうした既知のトピック名があれば、それに対して既知のアイテム名を指定することができ、 統一した方法でサーバーの情報を取得できる利点が生まれます。 次に、systemトピックがサポートできるアイテム名を指定します。

アイテム名 意味
Formats サーバーがサポートするクリップボードフォーマットのリスト。
Help サーバーの使い方を簡単に示したテキスト。
ReturnMessage 最近使われたWM_DDE_ACKの詳細。
Status サーバーの現在の状態を示す文字列。
SysItems サーバーがサポートするsystemトピックのアイテム名のリスト。
TopicItemList systemトピックの以外のトピックがサポートするアイテム名のリスト。
Topics トピック名として指定可能な文字列のリスト。

クライアントがsystemトピックを指定してサーバーとの対話を確立した場合は、 WM_DDE_REQUESTに上記のアイテム名を指定してデータを取得できる可能性があります。 systemトピックをサポートするといっても、全てのアイテム名をサポートする必要はありませんから、 まずはSysItemsでサポートしているアイテム名を知るようにすればよいでしょう。 今回作成するサーバーは、上記したアイテム名の全てをサポートしていないため、 データを返す際の形式については既存のアプリケーションを参考にしてください。 たとえば、Excelに対してSysItemsを要求すると、サポートするアイテム名がタブで区切られて返されます。 アイテム名を定数として扱いたい場合は、ddeml.hに定義されたSZDDESYS_TOPICなどを使用します。

今回のプログラムは、ウインドウにチェックボックスを表示しており、 このチェックボックスの状態の取得や設定をサポートします。 また、ユーザーによってチェックされた際には、それをクライアントに通知します。

#include <windows.h>

#define ID_CHECKBOX 100

HWND g_hwndCheck = NULL;

BOOL PostData(HWND hwndClientDDE, HWND hwndSeverDDE, ATOM atomItem, BOOL bAckReq);
BOOL SetData(char *p);
BOOL IsSupportItem(UINT uFormat, ATOM atomItem);
BOOL PostDDEMessage(HWND hwndClientDDE, UINT uMsg, HWND hwndServerDDE, UINT uLow, UINT uHigh);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndClientDDE = NULL;
	static BOOL bAdvise = NULL;
	static BOOL bWarmLink = FALSE;
	static BOOL bAdviseAckReq = FALSE;

	switch (uMsg) {

	case WM_CREATE:
		g_hwndCheck = CreateWindowEx(0, TEXT("BUTTON"), TEXT("チェック"), WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 30, 80, 170, 40, hwnd, (HMENU)ID_CHECKBOX, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		return 0;

	case WM_DDE_INITIATE: {
		ATOM atomApp = GlobalAddAtom(TEXT("sample"));
		ATOM atomTopic = GlobalAddAtom(TEXT("my-topic"));
		BOOL bResult = FALSE;

		if (LOWORD(lParam) == atomApp && HIWORD(lParam) == atomTopic)
			bResult = TRUE;
		else if (lParam == 0)
			bResult = TRUE;
		else
			;

		if (bResult) {
			hwndClientDDE = (HWND)wParam;
			SendMessage(hwndClientDDE, WM_DDE_ACK, (WPARAM)hwnd, MAKELONG(atomApp, atomTopic));
		}
		
		GlobalDeleteAtom(atomTopic);
		GlobalDeleteAtom(atomApp);

		return 0;
	}

	case WM_DDE_REQUEST: {
		ATOM atomItem;
		UINT uFormat;

		UnpackDDElParam(WM_DDE_REQUEST, lParam, (PUINT)&uFormat, (PUINT)&atomItem);

		if (IsSupportItem(uFormat, atomItem))
			PostData(hwndClientDDE, hwnd, atomItem, FALSE);
		else
			PostDDEMessage(hwndClientDDE, WM_DDE_ACK, hwnd, 0, atomItem);
		
		FreeDDElParam(WM_DDE_REQUEST, lParam);
		
		return 0;
	}

	case WM_DDE_POKE: {
		HANDLE  hData;
		DDEPOKE *lpPoke;
		ATOM    atomItem;
		BOOL    bResult;
		BOOL    bRelease;
		
		UnpackDDElParam(WM_DDE_POKE, lParam, (PUINT)&hData, (PUINT)&atomItem);

		lpPoke = (DDEPOKE *)GlobalLock(hData);
		if (IsSupportItem(lpPoke->cfFormat, atomItem))
			bResult = SetData((char *)lpPoke->Value);
		else
			bResult = FALSE;
		bRelease = lpPoke->fRelease;
		GlobalUnlock(hData);
		
		PostDDEMessage(hwndClientDDE, WM_DDE_ACK, hwnd, bResult ? 0x8000 : 0, atomItem);

		if (bRelease)
			GlobalFree(hData);

		FreeDDElParam(WM_DDE_POKE, lParam);

		return 0;
	}
					  
	case WM_DDE_ADVISE: {
		HANDLE    hData;
		DDEADVISE *lpAdvise;
		ATOM      atomItem;

		UnpackDDElParam(WM_DDE_ADVISE, lParam, (PUINT)&hData, (PUINT)&atomItem);

		lpAdvise = (DDEADVISE *)GlobalLock(hData);
		if (IsSupportItem(lpAdvise->cfFormat, atomItem)) {
			bAdvise = TRUE;
			bWarmLink = lpAdvise->fDeferUpd;
		}
		else
			bAdvise = FALSE;
		bAdviseAckReq = lpAdvise->fAckReq;
		GlobalUnlock(hData);
		
		PostDDEMessage(hwndClientDDE, WM_DDE_ACK, hwnd, bAdvise ? 0x8000 : 0, atomItem);

		GlobalFree(hData);

		return 0;
	}
						
	case WM_DDE_UNADVISE: {
		ATOM atomItem;

		UnpackDDElParam(WM_DDE_UNADVISE, lParam, NULL, (PUINT)&atomItem);
		GlobalDeleteAtom(atomItem);
		bAdvise = FALSE;
		return 0;
	}
						  
	case WM_DDE_TERMINATE:
		hwndClientDDE = NULL;
		return 0;
	
	case WM_COMMAND: {
		int nId = LOWORD(wParam);
		if (nId == ID_CHECKBOX && bAdvise) {
			ATOM atomItem;

			atomItem = GlobalAddAtom(TEXT("check"));
			if (bWarmLink)
				PostDDEMessage(hwndClientDDE, WM_DDE_DATA, hwnd, 0, atomItem);
			else
				PostData(hwndClientDDE, hwnd, atomItem, bAdviseAckReq);
		}
		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL PostData(HWND hwndClientDDE, HWND hwndSeverDDE, ATOM atomItem, BOOL bAckReq)
{
	DDEDATA *lpData;
	HANDLE  hData;
	CHAR    szData[256];
	DWORD   dwSize;
	BOOL    bCheck;
	
	bCheck = SendMessage(g_hwndCheck, BM_GETCHECK, 0, 0) == BST_CHECKED;
	if (bCheck)
		lstrcpyA(szData, "true");
	else
		lstrcpyA(szData, "false");
	dwSize = lstrlenA(szData) + 1;
			
	hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, sizeof(DDEDATA) + dwSize);
	lpData = (DDEDATA *)GlobalLock(hData);
	lpData->cfFormat = CF_TEXT;
	lpData->fAckReq = bAckReq;
	lpData->fRelease = TRUE;
	CopyMemory(lpData->Value, szData, dwSize);
	GlobalUnlock(hData);
	
	return PostDDEMessage(hwndClientDDE, WM_DDE_DATA, hwndSeverDDE, (UINT)hData, atomItem);
}

BOOL SetData(char *p)
{
	BOOL bCheck;

	if (lstrcmpA(p, "true") == 0)
		bCheck = TRUE;
	else if (lstrcmpA(p, "false") == 0)
		bCheck = FALSE;
	else
		return FALSE;

	SendMessage(g_hwndCheck, BM_SETCHECK, bCheck ? BST_CHECKED : BST_UNCHECKED, 0);

	return TRUE;
}

BOOL IsSupportItem(UINT uFormat, ATOM atomItem)
{
	TCHAR szItem[256];

	GlobalGetAtomName(atomItem, szItem, sizeof(szItem) / sizeof(TCHAR));
	
	return uFormat == CF_TEXT && lstrcmp(szItem, TEXT("check")) == 0;
}

BOOL PostDDEMessage(HWND hwndClientDDE, UINT uMsg, HWND hwndServerDDE, UINT uLow, UINT uHigh)
{
	LPARAM lParam = PackDDElParam(uMsg, uLow, uHigh);
	BOOL   bResult;

	bResult = PostMessage(hwndClientDDE, uMsg, (WPARAM)hwndServerDDE, lParam);
	if (!bResult)
		FreeDDElParam(uMsg, lParam);

	return bResult;
}

サーバーが何らかの動作を行うようになるのは、クライアントからのDDEメッセージを受信したときです。 各メッセージの処理を順に見ていきます。

case WM_DDE_INITIATE: {
	ATOM atomApp = GlobalAddAtom(TEXT("sample"));
	ATOM atomTopic = GlobalAddAtom(TEXT("my-topic"));
	BOOL bResult = FALSE;

	if (LOWORD(lParam) == atomApp && HIWORD(lParam) == atomTopic)
		bResult = TRUE;
	else if (lParam == 0)
		bResult = TRUE;
	else
		;

	if (bResult) {
		hwndClientDDE = (HWND)wParam;
		SendMessage(hwndClientDDE, WM_DDE_ACK, (WPARAM)hwnd, MAKELONG(atomApp, atomTopic));
	}
	
	GlobalDeleteAtom(atomTopic);
	GlobalDeleteAtom(atomApp);

	return 0;
}

WM_DDE_INITIATEは、クライアントがサーバーとの対話を望んでいる場合に送られます。 LPARAMの下位ワードにはサーバーのアプリケーション名が格納されており、 上位ワードにはトピック名が格納されているはずですから、 これらがサーバーがサポートするものと一致する場合は、 WM_DDE_ACKをクライアントに返すようにします。 サーバーはトピック名としてmy-topicをサポートしているため、my-topicを指定しています。 lParamが0である場合は応答するかどうか自由ですが、今回は応答するようにしています。 このWM_DDE_INITIATEの設計では、hwndClientDDEを常に上書きするようになっているため、 サーバーと対話するクライアントは1つでなければなりません。

WM_DDE_INITIATEでGlobalAddAtomとGlobalDeleteAtomを呼び出すことは、 MSDNにも掲載されている正しい方法であると思います。 しかし、既存のDDEサーバー(Excelなど)と通信してみて分かったことですが、 どうやら既存のDDEサーバーはGlobalDeleteAtomを呼び出す設計になっていないようです。 つまり、グローバルアトムテーブルに格納されたアプリケーション名やトピック名が削除されない問題があります。 こうした問題は実際にDDE通信を行わない場合でも発生することがあり、 たとえばExcelを起動するとExcelという文字列がグローバルアトムテーブルに格納されますが、 これはExcelの終了時でも削除されないようです。

WM_DDE_REQUESTの処理は次のようになっています。

case WM_DDE_REQUEST: {
	ATOM atomItem;
	UINT uFormat;

	UnpackDDElParam(WM_DDE_REQUEST, lParam, (PUINT)&uFormat, (PUINT)&atomItem);

	if (IsSupportItem(uFormat, atomItem))
		PostData(hwndClientDDE, hwnd, atomItem, FALSE);
	else
		PostDDEMessage(hwndClientDDE, WM_DDE_ACK, hwnd, 0, atomItem);
	
	FreeDDElParam(WM_DDE_REQUEST, lParam);
	
	return 0;
}

WM_DDE_REQUESTは、クライアントがデータを要求する際に送られます。 UnpackDDElParamを呼び出せば、データのフォーマットとデータを識別するグローバルアトムを取得でき、 これらがサーバーのアイテムと一致するかをIsSupportItemで確認します。 一致する場合はPostData経由でWM_DDE_DATAを返すようにし、 一致しない場合はWPARAMに0を指定してWM_DDE_ACKを返します。 通常、グローバルアトムを受け取った側はそれを削除しなければなりませんが、 グローバルアトムはWM_DDE_DATAやWM_DDE_ACKでも必要になるため、削除せず使いまわすようにしています。 IsSupportItemの内部は次のようになっています。

BOOL IsSupportItem(UINT uFormat, ATOM atomItem)
{
	TCHAR szItem[256];

	GlobalGetAtomName(atomItem, szItem, sizeof(szItem) / sizeof(TCHAR));
	
	return uFormat == CF_TEXT && lstrcmp(szItem, TEXT("check")) == 0;
}

今回のサーバーにおけるアイテムとは、チェックボックスのことであり、check"という名前を持つものとします。 このアイテムは現在のチェックの状態をtrueまたはfalseという文字列で表すため、 フォーマットはCF_TEXTでなければなりません。 続いて、PostDataの内部を確認します。

BOOL PostData(HWND hwndClientDDE, HWND hwndSeverDDE, ATOM atomItem, BOOL bAckReq)
{
	DDEDATA *lpData;
	HANDLE  hData;
	CHAR    szData[256];
	DWORD   dwSize;
	BOOL    bCheck;
	
	bCheck = SendMessage(g_hwndCheck, BM_GETCHECK, 0, 0) == BST_CHECKED;
	if (bCheck)
		lstrcpyA(szData, "true");
	else
		lstrcpyA(szData, "false");
	dwSize = lstrlenA(szData) + 1;
			
	hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, sizeof(DDEDATA) + dwSize);
	lpData = (DDEDATA *)GlobalLock(hData);
	lpData->cfFormat = CF_TEXT;
	lpData->fAckReq = bAckReq;
	lpData->fRelease = TRUE;
	CopyMemory(lpData->Value, szData, dwSize);
	GlobalUnlock(hData);
	
	return PostDDEMessage(hwndClientDDE, WM_DDE_DATA, hwndSeverDDE, (UINT)hData, atomItem);
}

WM_DDE_DATAはDDEDATA構造体を返すことになっており、 今回の場合Valueメンバにはtrueまたはfalseという文字列を指定することになります。 この判定は現在チェックボックスにチェックが付いているかどうかで行うべきであるため、 BM_GETCHECKをチェックボックスに送信しています。

case WM_DDE_POKE: {
	HANDLE  hData;
	DDEPOKE *lpPoke;
	ATOM    atomItem;
	BOOL    bResult;
	BOOL    bRelease;
	
	UnpackDDElParam(WM_DDE_POKE, lParam, (PUINT)&hData, (PUINT)&atomItem);

	lpPoke = (DDEPOKE *)GlobalLock(hData);
	if (IsSupportItem(lpPoke->cfFormat, atomItem))
		bResult = SetData((char *)lpPoke->Value);
	else
		bResult = FALSE;
	bRelease = lpPoke->fRelease;
	GlobalUnlock(hData);
	
	PostDDEMessage(hwndClientDDE, WM_DDE_ACK, hwnd, bResult ? 0x8000 : 0, atomItem);

	if (bRelease)
		GlobalFree(hData);

	FreeDDElParam(WM_DDE_POKE, lParam);

	return 0;
}

WM_DDE_POKEは、クライアントがデータを設定する際に送られます。 設定の対象となるアイテムをサーバーがサポートしている場合は、 SetDataという自作関数でデータを設定するようになります。 データの設定が成功したかどうかはWM_DDE_ACKで返すことになっており、 成功した場合は0x8000、失敗した場合は0をWPARAMに指定します。 SetDataの内部は次のようになっています。

BOOL SetData(char *p)
{
	BOOL bCheck;

	if (lstrcmpA(p, "true") == 0)
		bCheck = TRUE;
	else if (lstrcmpA(p, "false") == 0)
		bCheck = FALSE;
	else
		return FALSE;

	SendMessage(g_hwndCheck, BM_SETCHECK, bCheck ? BST_CHECKED : BST_UNCHECKED, 0);

	return TRUE;
}

渡された文字列がtrueである場合はチェックを付けるようにし、 falseである場合はチェックを外すようにします。 trueでもfalseでもない場合は、データの設定が失敗したとみなします。

case WM_DDE_ADVISE: {
	HANDLE    hData;
	DDEADVISE *lpAdvise;
	ATOM      atomItem;

	UnpackDDElParam(WM_DDE_ADVISE, lParam, (PUINT)&hData, (PUINT)&atomItem);

	lpAdvise = (DDEADVISE *)GlobalLock(hData);
	if (IsSupportItem(lpAdvise->cfFormat, atomItem)) {
		bAdvise = TRUE;
		bWarmLink = lpAdvise->fDeferUpd;
	}
	else
		bAdvise = FALSE;
	bAdviseAckReq = lpAdvise->fAckReq;
	GlobalUnlock(hData);
	
	PostDDEMessage(hwndClientDDE, WM_DDE_ACK, hwnd, bAdvise ? 0x8000 : 0, atomItem);

	GlobalFree(hData);

	return 0;
}

WM_DDE_ADVISEは、クライアントがデータの変更を検出したい場合に送られます。 サポートしているアイテムを確認したら、データリンクの確立を示すbAdviseをTRUEにします。 この状態でチェックボックスの状態が変化したら、データの変更をクライアントに通知するようにします。 fDeferUpdメンバにはウォームリンクかどうかの値が格納されており、 これは通知の際に必要になるため保存しておきます。 また、データの変更をWM_DDE_DATAで通知する際には、fAckReqの値をDDEDATA構造体に指定しなければならないため、 fAckReqの値も保存しておくようにします。

case WM_COMMAND: {
	int nId = LOWORD(wParam);
	if (nId == ID_CHECKBOX && bAdvise) {
		ATOM atomItem;

		atomItem = GlobalAddAtom(TEXT("check"));
		if (bWarmLink)
			PostDDEMessage(hwndClientDDE, WM_DDE_DATA, hwnd, 0, atomItem);
		else
			PostData(hwndClientDDE, hwnd, atomItem, bAdviseAckReq);
	}
	return 0;
}

チェックボックスの状態の変化した場合は、WM_COMMANDが通知されます。 bAdviseがTRUEの場合は、データの変更をクライアントに通知しなければならないため、 WM_DDE_DATAを送ることになります。 ウォームリンクの場合はデータ自体を送る必要はありませんが、 ホットリンクの場合はデータそのものを送ることになります。

case WM_DDE_UNADVISE: {
	ATOM atomItem;

	UnpackDDElParam(WM_DDE_UNADVISE, lParam, NULL, (PUINT)&atomItem);
	GlobalDeleteAtom(atomItem);
	bAdvise = FALSE;
	return 0;
}

WM_DDE_UNADVISEは、クライアントがデータの通知が不要になった際に送られます。 本来ならばここではWM_DDE_ACKを返すべきなのですが、 クライアントが終了時にWM_DDE_UNADVISEを送った場合は、 WM_DDE_ACKを処理できない可能性もあります。 よって、今回はWM_DDE_ACKを返さないようにしています。

case WM_DDE_TERMINATE:
	hwndClientDDE = NULL;
	return 0;

WM_DDE_TERMINATEは、クライアントがサーバーとの対話を終了する際に送られます。 これが送られてくるということは、hwndClientDDEからこれ以上メッセージが送られてくることはないため、 hwndClientDDEをNULLで初期化しても問題ありません。

クライアントの作成

今回作成したサーバーに対するクライアントを次に示します。 マウスの左ボタンが押された場合はチェックボックスの状態を取得し、 右ボタンが押された場合はチェックボックスにチェックを付けます。 また、ユーザーによってサーバーのチェックボックスが変更された場合は、それを検出します。

#include <windows.h>

BOOL PostDDEMessage(HWND hwndServerDDE, UINT uMsg, HWND hwndClientDDE, UINT uLow, UINT uHigh);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndServerDDE = NULL;
	static BOOL bWarmLink = FALSE;
	
	switch (uMsg) {

	case WM_CREATE: {
		ATOM      atomApp, atomTopic, atomItem;
		HANDLE    hData;
		DDEADVISE *lpAdvise;

		atomApp = GlobalAddAtom(TEXT("sample"));
		atomTopic = GlobalAddAtom(TEXT("my-topic"));
		SendMessage((HWND)HWND_BROADCAST, WM_DDE_INITIATE, (WPARAM)hwnd, MAKELONG(atomApp, atomTopic));
		GlobalDeleteAtom(atomApp);
		GlobalDeleteAtom(atomTopic);

		if (hwndServerDDE == NULL) {
			MessageBox(NULL, TEXT("DDE対話の確立に失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}

		atomItem = GlobalAddAtom(TEXT("check"));

		hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, sizeof(DDEADVISE));
		lpAdvise = (DDEADVISE *)GlobalLock(hData);
		lpAdvise->cfFormat  = CF_TEXT;
		lpAdvise->fAckReq   = TRUE;
		lpAdvise->fDeferUpd = bWarmLink;
		GlobalUnlock(hData);

		if (!PostDDEMessage(hwndServerDDE, WM_DDE_ADVISE, hwnd, (UINT)hData, atomItem)) {
			GlobalDeleteAtom(atomItem);
			GlobalFree(hData);
		}

		return 0;
	}
	
	case WM_LBUTTONDOWN: {
		ATOM atomItem;

		atomItem = GlobalAddAtom(TEXT("check"));
		if (!PostDDEMessage(hwndServerDDE, WM_DDE_REQUEST, hwnd, CF_TEXT, atomItem))
			GlobalDeleteAtom(atomItem);
		return 0;
	}
	
	case WM_RBUTTONDOWN: {
		ATOM    atomItem;
		DDEPOKE *pPoke;
		HANDLE  hData;
		CHAR    szData[] = "true";
		DWORD   dwSize = lstrlenA(szData) + 1;

		hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, sizeof(DDEPOKE) + dwSize);
		pPoke = (DDEPOKE *)GlobalLock(hData);
		pPoke->fRelease = TRUE;
		pPoke->cfFormat = CF_TEXT;
		CopyMemory(pPoke->Value, szData, dwSize);
		GlobalUnlock(hData);

		atomItem = GlobalAddAtom(TEXT("check"));
		if (!PostDDEMessage(hwndServerDDE, WM_DDE_POKE, hwnd, (UINT)hData, atomItem)) {
			GlobalDeleteAtom(atomItem);
			GlobalFree(hData);
		}

		return 0;
	}

	case WM_DDE_ACK: {
		if (hwndServerDDE == NULL)
			hwndServerDDE = (HWND)wParam;
		else {
			ULONG uData;
			ATOM  atomItem;
			TCHAR szBuf[256];

			UnpackDDElParam(WM_DDE_ACK, lParam, (PUINT)&uData, (PUINT)&atomItem);
			wsprintf(szBuf, TEXT("%x"), uData);
		//	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);

			GlobalDeleteAtom(atomItem);
			FreeDDElParam(WM_DDE_ACK, lParam);
		}
		return 0;
	}
	
	case WM_DDE_DATA: {
		ATOM    atomItem;
		HANDLE  hData;
		DDEDATA *lpData;
		BOOL    bRelease;
		BOOL    bDeleteAtom = TRUE;
		
		UnpackDDElParam(WM_DDE_DATA, lParam, (PUINT)&hData, (PUINT)&atomItem);
		if (hData == NULL) {
			MessageBox(hwnd, TEXT("データの変更が通知されました。"), TEXT("OK"), MB_OK);
			GlobalDeleteAtom(atomItem);
			FreeDDElParam(WM_DDE_DATA, lParam);
			return 0;
		}

		lpData = (DDEDATA *)GlobalLock(hData);

		if (lpData->cfFormat == CF_TEXT) {
			MessageBoxA(hwnd, (LPSTR)lpData->Value, "OK", MB_OK);
			if (lpData->fAckReq) {
				bDeleteAtom = FALSE;
				PostDDEMessage(hwndServerDDE, WM_DDE_ACK, hwnd, 0x8000, atomItem);
			}
		}
		else {
			bDeleteAtom = FALSE;
			PostDDEMessage(hwndServerDDE, WM_DDE_ACK, hwnd, 0, atomItem);
		}
		
		bRelease = lpData->fRelease;
		GlobalUnlock(hData);
		if (bRelease)
			GlobalFree(hData);
		if (bDeleteAtom)
			GlobalDeleteAtom(atomItem);

		FreeDDElParam(WM_DDE_DATA, lParam);

		return 0;
	}

	case WM_DESTROY:
		if (hwndServerDDE != NULL) {
			ATOM atomItem;
			atomItem = GlobalAddAtom(TEXT("check"));
			if (!PostDDEMessage(hwndServerDDE, WM_DDE_UNADVISE, hwnd, 0, atomItem))
				GlobalDeleteAtom(atomItem);
			PostMessage(hwndServerDDE, WM_DDE_TERMINATE, (WPARAM)hwnd, 0);
		}
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL PostDDEMessage(HWND hwndServerDDE, UINT uMsg, HWND hwndClientDDE, UINT uLow, UINT uHigh)
{
	LPARAM lParam = PackDDElParam(uMsg, uLow, uHigh);
	BOOL   bResult;

	bResult = PostMessage(hwndServerDDE, uMsg, (WPARAM)hwndClientDDE, lParam);
	if (!bResult)
		FreeDDElParam(uMsg, lParam);

	return bResult;
}

コード量は多いですが、基本的にはこれまでのプログラムのコードを混ぜ合わしているだけです。 たとえば、WM_CREATEではWM_DDE_INITIATEでサーバーとの対話を開始し、 さらにWM_DDE_ADVISEでデータリンクを確立していますが、 この流れは前節と全く同じです。 サーバーのアプリケーション名やトピック名、アイテム名などは、今回のサーバーと同じ値を指定するようにします。 マウスの左ボタンが押された場合は、データを要求するためにWM_DDE_REQUESTをポストし、 データをWM_DDE_DATAで受け取っていますが、この処理も以前に示した通りです。 マウスの右ボタンが押された場合は、データを設定するためにWM_DDE_POKEをポストしていますが、 この処理も以前に示した通りです。



戻る