DDEには同期リンクと非同期リンクという対話方法があります。
前者はクライアントのほうでサーバーより回答が来るのを待つ方式です。
これに対して後者はクライアントの要求に対してサーバーからまだ
回答が来ないうちに次の動作を実行します。
また、DDEにはコールドリンク、ウォームリンク、ホットリンクと いうのがあります。コールドリンクとはクライアントがデータを 要求するために確立するリンクです。ウォームとかホットリンクは サーバー側からデータ転送を開始します。送られてきたデータを 常にクライアントで処理するのがホットリンクです。処理しないことも あるのがウォームリンクです。
今回はもっとも単純なものを作ります。同期・コールドリンクです。
という手順でやります。複数のデータを取得するには3.4.を繰り返します。1.DdeInitialize関数でDDEを初期化します。 2.DdeConnect関数で通信開始。 3.DdeClientTransaction関数でトランザクションをサーバーに転送 4.DdeGetData関数などでデータを取得 5.DdeDisconnect関数で通信終了 6.DdeUninitialize関数でDDEアプリケーションではなくなったことを サーバーに通知
pidInstはインスタンスIDのポインタです。関数が成功すると ここにIDのポインタが入ります。UINT DdeInitialize( LPDWORD pidInst, // インスタンスIDへのポインタ PFNCALLBACK pfnCallback, // コールバック関数へのポインタ DWORD afCmd, // コマンド,フィルターなど DWORD ulRes // 予約済み );
pfnCallbackはDDEコールバック関数のポインタです。 これは、アプリケーション定義のコールバック関数で トランザクションを処理します。トランザクションとはメッセージみたい なものですが、通常のプロシージャには流れません。
afCmdはコールバック関数の呼び出しにフィルターをかけることができます。
APPCMD_CLIENTONLYはクライアントに徹することを指定します。
あと、CBF_FAIL_***というのがたくさんありますがこれは、コールバック関数が
ある種のトランザクションを受け取らないフィルタをかけます。
今回はクライアントに徹するためAPPCMD_CLIENTONLYを指定します。 実際試すとわかりますがコールバック関数は一度も呼ばれません。 (pfnCallbackをNULLにしてコールバック関数をかかなくても動作します。 しかし、ヘルプによると必ずコールバック関数を登録しろと書いてあります)
idInstはDdeInitialize関数で取得したIDです。HCONV DdeConnect( DWORD idInst, // インスタンスID HSZ hszService, // サーバーのサービス名へのハンドル HSZ hszTopic, // トピックの名前へのハンドル PCONVCONTEXT pCC // CONVCONTEXT構造体へのポインタ );
hszServiceはサーバーのサービス名へのハンドルです。0にすると 有効ないずれかのサーバーとの通信となります。
hszTopicは通信を確立するトピック名へのハンドルです。0にする と有効ないずれかのトピックとなります。
pCCはCONVCONTEXT構造体へのポインタです。 CONVCONTEXT構造体は通信コンテキスト情報を格納します。 これをNULLにするとデフォルトの情報と成ります。 多分自分でCONVCONTEXT構造体を書くことはあまりないのではないか と思います。
pDataはwTypeがXTYPE_EXECUTEの時のみ必要。他の場合はNULL。HDDEDATA DdeClientTransaction( LPBYTE pData, // サーバーに渡すデータの先頭バイトのポインタ DWORD cbData, // データの長さ HCONV hConv, // 通信ハンドル HSZ hszItem, // データ項目のハンドル UINT wFmt, // クリップボードフォーマット UINT wType, // トランザクションタイプ DWORD dwTimeout, // 待ち時間 LPDWORD pdwResult // トランザクションの結果へのポインタ );
cbDataはpDataが指し示すデータの長さ。
hConvは通信ハンドルを指定します。通信ハンドルはDdeConnect 関数で取得したものです。
hszItemはデータ項目のハンドルです。(DdeCreateStringHandle関数で作る)
wFmtには、この通信でのクリップボードフォーマットを指定します。 テキストでするならCF_TEXTとなります。
wTypeはトランザクションタイプを指定します。
XTYPE_REQUESTは要求トランザクション、XTYPE_EXECUTEは実行トランザクションです。
また、XTYPE_ADVSTART, XTYPE_ADVSTOPでアドバイズループを作ることもできます。
これは、XTYPE_ACKREQ, XTYPE_NODATAと組み合わせて指定できます。
XTYPE_ACKREQはクライアントが確認するまで次のデータを送りません。
XTYPE_NODATAはデータを送らずにデータの変更をクライアントに通知します。
dwTimeoutは同期トランザクションでクライアントがサーバーからの返事を 待つときの最大ミリセコンドを指定します。
pdwResultはトランザクションの結果へのポインタです。結果が不要ならNULLにします。
戻り値は同期トランザクションのときはデータを識別するハンドルが返されます。 非同期トランザクションのときは成功したときは0以外、失敗したときは0が返されます。
idInstはインスタンスハンドルです。HSZ DdeCreateStringHandle( DWORD idInst, // インスタンスID LPTSTR psz, // 文字列 int iCodePage // コードページ識別子 );
pszはハンドルを作成したい文字列です。
iCodePageはANSI版であればCP_WINANSI, Unicode版であればCP_WINUNICODEとなります。
ストリングハンドルが不要になったらDdeFreeStringHandle関数で開放します。
開放したいストリングハンドルを指定します。BOOL DdeFreeStringHandle( DWORD idInst, // インスタンスID HSZ hsz // ストリングハンドル );
DDEオブジェクトハンドルが不要になったら DdeFreeDataHandle関数で開放します。DWORD DdeGetData( HDDEDATA hData, // DDEオブジェクトハンドル LPBYTE pDst, // 取得するデータバッファへのポインタ DWORD cbMax, // コピーするデータ量 DWORD cbOff // データ開始のオフセット );
通信が終わったらBOOL DdeFreeDataHandle( HDDEDATA hData // DDEオブジェクトハンドル );
で通信ハンドルを無効にします。BOOL DdeDisconnect( HCONV hConv // 通信ハンドル );
DdeUninitialize関数はオープンしているすべての通信を終了します。
これで,大体必要な関数が出揃いました。 プログラムを作る前にエクセルで、セルに適当なデータを入力した 「TESTDDE.XLS」というファイルを作っておいて下さい。 このデータを読み出すことにします。BOOL DdeUninitialize( DWORD idInst // インスタンスID );
エクセルで左のような適当なデータを作っておきます。 各セルの書式は何でも良いです。また、R1C1形式にしておいて下さい。
これから作るdde01.exeを実行してメニューから「DDE開始」を
選択して、次に「データの取得」を選択します。
すると左のようなダイアログボックスが出ますので、
取得したいセルを指定します。
ありふれたリソース・スクリプトです。「キャンセル」は 実際には半角かなです。// dde01.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "DDE(&D)" BEGIN MENUITEM "DDE開始(&S)", IDM_START MENUITEM "DDE終了(&E)", IDM_STOP MENUITEM "データの取得(&G)", IDM_GETDATA END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOG DISCARDABLE 0, 0, 119, 63 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "DDE利用のデータ取得" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,7,42,50,14 PUSHBUTTON "キャンセル",IDCANCEL,61,42,50,14 LTEXT "取得したいデータ",IDC_STATIC,7,7,51,8 EDITTEXT IDC_EDIT1,32,19,24,12,ES_AUTOHSCROLL LTEXT "R",IDC_STATIC,18,19,8,8 LTEXT "C",IDC_STATIC,62,19,8,8 EDITTEXT IDC_EDIT2,76,19,24,12,ES_AUTOHSCROLL END
DDEコールバック関数については解説をしませんでしたが 引数は上のプロトタイプのようにしておいて下さい。// dde01.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); HDDEDATA CALLBACK DdemlCallback(UINT, UINT, HCONV, HSZ, HSZ, HDDEDATA, DWORD, DWORD); LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); HINSTANCE hInst; HWND hParent; int DDE_Start(HWND); int DDE_END(HWND); int DDE_GetData(HWND); char szClassName[] = "dde01"; //ウィンドウクラス DWORD ddeInst; HSZ hszService; HSZ hszTopic; HCONV hConv; char szBuf[64];
このへんはいつもと同じです。int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; hWnd = CreateWindow(szClassName, "猫でもわかるDDE",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInstance, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); hParent = hWnd; return TRUE; }
メニューから「DDE開始」「DDE終了」「データの取得」を 選択するとそれぞれの自作関数が呼ばれるようにしてみました。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_START: if (!hConv) DDE_Start(hWnd); else MessageBox(hWnd, "すでに開始されています", "OK", MB_OK); break; case IDM_GETDATA: DDE_GetData(hWnd); break; case IDM_STOP: DDE_END(hWnd); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: DDE_END(hWnd); if(DdeUninitialize(ddeInst) != 0) MessageBox(hWnd, "正常にDDE終了", "ok", MB_OK); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
DDEをスタートさせる関数です。hszServiceは"Excel"、hszTopicは"DDETEST.xls"に 対するハンドルにしておきます。int DDE_Start(HWND hWnd) { if(DdeInitialize(&ddeInst, DdemlCallback, APPCMD_CLIENTONLY, 0) != DMLERR_NO_ERROR) { MessageBox(hWnd, "DdeInitialize失敗です", "Error", MB_OK); return -1; }else { MessageBox(hWnd, "初期化成功です", "OK", MB_OK); } hszService = DdeCreateStringHandle(ddeInst, "Excel", CP_WINANSI); hszTopic = DdeCreateStringHandle(ddeInst, "DDETEST.xls", CP_WINANSI); hConv = DdeConnect(ddeInst, hszService, hszTopic, NULL); if (DdeGetLastError(ddeInst) != DMLERR_NO_ERROR) { MessageBox(hWnd, "サーバーに接続失敗", "ok", MB_OK); return -1; } return 0; }
これで、エクセルに接続できます。
DDE接続を終了するときの後始末です。この関数の中で DdeUninitialize関数を呼ぶと次にDdeInitialize関数が失敗します。 そこで、DdeUninitialize関数はアプリケーション終了時に呼び出すことにしました。int DDE_END(HWND hWnd) { if (hConv) { if(DdeFreeStringHandle(ddeInst, hszService) == 0) MessageBox(hWnd, "DdeFreeStringHandle失敗", "Error", MB_OK); if(DdeFreeStringHandle(ddeInst, hszTopic) == 0) MessageBox(hWnd, "DdeFreeStringHandle失敗", "Error", MB_OK); if(DdeDisconnect(hConv) != 0) MessageBox(hWnd, "DdeDisconnect成功", "OK", MB_OK); hConv = 0; } else { MessageBox(hWnd, "終了すべきDDEがありません", "OK", MB_OK); return -1; } return 0; }
これも、順番に見ていくとたいして難しくはないですね。int DDE_GetData(HWND hWnd) { HDDEDATA hRet; HSZ hszMyTopic; if (!hConv) { MessageBox(hWnd, "DDE開始されていません", "OK", MB_OK); return -1; } DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); hszMyTopic = DdeCreateStringHandle(ddeInst, szBuf, CP_WINANSI); hRet = DdeClientTransaction( NULL, 0, hConv, hszMyTopic, CF_TEXT, XTYP_REQUEST, 1000, NULL); if (!hRet && DdeGetLastError(ddeInst) != DMLERR_NO_ERROR) { MessageBox(hWnd, "失敗です", "OK", MB_OK); return -2; } else if (hRet) { DdeGetData(hRet, (LPBYTE)szBuf, sizeof(szBuf), 0); DdeFreeStringHandle(ddeInst, hszMyTopic); DdeFreeDataHandle(hRet); MessageBox(hWnd, szBuf, "DATA", MB_OK); } return 0; }
ダイアログボックスのプロシージャです。LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { char szRC[64]; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: strcpy(szBuf, "R"); Edit_GetText(GetDlgItem(hDlg, IDC_EDIT1), szRC, sizeof(szRC)); strcat(szBuf, szRC); Edit_GetText(GetDlgItem(hDlg, IDC_EDIT2), szRC, sizeof(szRC)); strcat(szBuf, "C"); strcat(szBuf, szRC); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } break; } return FALSE; }
DDEコールバック関数です。ここでは何もしていません。呼ばれたら わかるようにメッセージボックスを入れておきましたが一度も呼ばれません。HDDEDATA CALLBACK DdemlCallback(UINT uType, UINT uFmt, HCONV hconv, HSZ hsz1, HSZ hsz2, HDDEDATA hdata, DWORD dwData1, DWORD dwData2) { MessageBox(NULL, "DDEコールバック関数が呼ばれました", "DDECALLBACK", MB_OK); return (HDDEDATA)NULL; }
さて、これでエクセルから何とかデータを呼び出すプログラムができました。
Update 19/Oct/1998 By Y.Kumei