EternalWindows
DDE / データの取得

クライアントがWM_DDE_INITIATEをブロードキャストして、サーバーからWM_DDE_ACKが返った場合、 クライアントとサーバーの間にはDDE対話が確立されたことになります。 前節では、この対話をWM_DDE_TERMINATEで直ちに終了させていましたが、 今回はサーバーから特定のデータを取得するようにします。 データの取得にはWM_DDE_REQUESTを使用します。

ATOM   atomItem;
BOOL   bResult;
LPARAM lParamDDE;

atomItem = GlobalAddAtom(TEXT("R2C1"));
lParamDDE = PackDDElParam(WM_DDE_REQUEST, CF_TEXT, atomItem);

bResult = PostMessage(hwndServerDDE, WM_DDE_REQUEST, (WPARAM)hwnd, lParamDDE);
if (!bResult) {
	GlobalDeleteAtom(atomItem);
	FreeDDElParam(WM_DDE_REQUEST, lParamDDE);
}

WM_DDE_REQUESTは、PostMessageを使用してポストすることになります。 WPARAMはクライアントのウインドウハンドルを指定し、 LPARAMは取得したいデータの種類とそのデータのフォーマットを含んだ値を指定します。 この値はPackDDElParamという関数で作成可能であり、 第2引数がLPARAMの下位ワード、第3引数がLPARAMの上位ワードに相当します。 下位ワードに含むべきはデータのフォーマットであり、 テキスト形式で受け取りたい場合はCF_TEXTを指定します。 一方、上位ワードに含むべきはグローバルアトムで識別されたデータの種類であり、 これはGlobalAddAtomで作成するようにします。 通常、PostMessageを使用するDDEメッセージでは、 データを送るために確保したメモリ(LPARAMやグローバルアトム、グローバルメモリなど)は、 データを受け取った側が開放することになります。 ただし、PostMessageが失敗した場合は相手がメモリを開放することはありませんから、 この場合は自分でメモリを開放することになります。 PostMessageが失敗する理由としては、サーバーのウインドウが既に閉じられている場合などが挙げられます。

今回のプログラムは、マウスの左ボタンが押された際にExcelの特定のセルからデータを取得します。 プログラムを起動する前に、sample.xlsxを開いたExcelを起動しておいてください。

#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;
	
	switch (uMsg) {

	case WM_CREATE: {
		ATOM atomApp, atomTopic;

		atomApp = GlobalAddAtom(TEXT("Excel"));
		atomTopic = GlobalAddAtom(TEXT("[sample.xlsx]Sheet1"));
		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;
		}

		return 0;
	}
	
	case WM_LBUTTONDOWN: {
		ATOM atomItem;

		atomItem = GlobalAddAtom(TEXT("R2C1"));
		if (!PostDDEMessage(hwndServerDDE, WM_DDE_REQUEST, hwnd, CF_TEXT, atomItem))
			GlobalDeleteAtom(atomItem);
		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, NULL, MB_ICONWARNING);

			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);
		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)
			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_DDE_INITIATEを送信してサーバーとDDE対話を開始しなければなりません。 今回は対話の対象とするサーバーをExcelに定めているため、 アプリケーション名としてExcelを指定し、トピック名としてExcelのファイル(及びシート)を指定しています。 これらの名前が何故分かったかについては、前節のプログラムを参照してください。 sample.xlsxを開いているExcelが起動されている場合、サーバーからWM_DDE_ACKが送られてくることになります。 WM_DDE_ACKは様々な理由で送られるメッセージですが、 hwndServerDDEがNULLである場合は、WM_DDE_INITIATEによる応答であると判断できます。 この場合は、WPARAMにサーバーのウインドウハンドルが格納されているため、必ず保存するようにします。

ウインドウ上でマウスの左ボタンが押された場合は、サーバーにWM_DDE_REQUESTを送ります。

case WM_LBUTTONDOWN: {
	ATOM atomItem;

	atomItem = GlobalAddAtom(TEXT("R2C1"));
	if (!PostDDEMessage(hwndServerDDE, WM_DDE_REQUEST, hwnd, CF_TEXT, atomItem))
		GlobalDeleteAtom(atomItem);
	return 0;
}

GlobalAddAtomに指定している"R2C1"は2行1列を表しており、この位置に相当するセルのデータを取得することを意味します。 PostDDEMessageという自作関数は、PostMessageの呼び出しとLPARAMの操作をラッピングします。 メッセージを送る度に、PackDDElParamとFreeDDElParamの呼び出しを記述するのは煩わしいため、 このような関数を使用しています。

サーバーにWM_DDE_REQUESTを送った場合、サーバーがデータをサポートする場合はWM_DDE_DATAが返され、 データをサポートできない場合はWM_DDE_ACKが返されます。 まず、WM_DDE_DATAの処理を確認します。

case WM_DDE_DATA: {
	ATOM    atomItem;
	HANDLE  hData;
	DDEDATA *lpData;
	BOOL    bRelease;
	BOOL    bDeleteAtom = TRUE;

	UnpackDDElParam(WM_DDE_DATA, lParam, (PUINT)&hData, (PUINT)&atomItem);
	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;
}

WM_DDE_DATAのLPARAMには、サーバーから返されたデータとグローバルアトムが含まれているため、 これをUnpackDDElParamで取り出します。 データはGlobalAllocで確保されているため、アクセスする場合はGlobalLockでロックするようにし、 DDEDATA構造体として識別するようにします。 cfFormatがクライアントの望む形式である場合は、このデータを処理することができますから、 Valueメンバの中身をMessageBoxで表示するようにしています。 fAckReqがTRUEである場合は、クライアントがデータを処理できたかをサーバーが知りたがっているため、 サーバーにWM_DDE_ACKを送ります。 このとき、WPARAMには成功を示す0x8000を指定します。 LPARAMはグローバルアトムであり、これはサーバーから渡されたものを使い回すため、 bDeleteAtomにFALSEを指定して削除しないようにしています。 クライアントがサーバーから返されたデータをサポートできない場合は、 fAckReqの値を問わずにWM_DDE_ACKを返すようにします。 このとき、WPARAMには失敗を意味する0を指定します。 fReleaseがTRUEである場合は、データをGlobalFreeで開放することになります。 通常、データやグローバルメモリ、LPARAMはメッセージを受け取った側が開放するため、 fReleaseは基本的にTRUEになるでしょう。

クライアントからのWM_DDE_REQUESTをサーバーがサポートできない場合は、 WM_DDE_DATAではなくWM_DDE_ACKが返されます。 これはたとえば、無効なセルを指定した場合に起こります。

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, NULL, MB_ICONWARNING);

		GlobalDeleteAtom(atomItem);
		FreeDDElParam(WM_DDE_ACK, lParam);
	}
	return 0;
}

WM_DDE_REQUESTの応答によるWM_DDE_ACKでは、LPARAMには応答の結果を示す値が含まれています。 UnpackDDElParamでこれを取得してMessageBoxで表示していますが、 その値は失敗を意味する0になっているはずです。 ちなみに、atomItemが識別している文字列は、WM_DDE_REQUESTの際にGlobalAddAtomに指定した文字列です。


戻る