第150章 DDEでエクセルよりデータを読む


今回からDDE(Dynamic Data Exchange)について少しだけやります。 データを要求するする方をクライアント,それに答える方をサーバーと いいます。両方を一度に作るのはめんどうなので、今回はエクセル (Microsoft(C)Execl 97)をサーバにして、クライアントのほうを 作成します。



DDEには同期リンクと非同期リンクという対話方法があります。 前者はクライアントのほうでサーバーより回答が来るのを待つ方式です。 これに対して後者はクライアントの要求に対してサーバーからまだ 回答が来ないうちに次の動作を実行します。

また、DDEにはコールドリンク、ウォームリンク、ホットリンクと いうのがあります。コールドリンクとはクライアントがデータを 要求するために確立するリンクです。ウォームとかホットリンクは サーバー側からデータ転送を開始します。送られてきたデータを 常にクライアントで処理するのがホットリンクです。処理しないことも あるのがウォームリンクです。

今回はもっとも単純なものを作ります。同期・コールドリンクです。

1.DdeInitialize関数でDDEを初期化します。 2.DdeConnect関数で通信開始。 3.DdeClientTransaction関数でトランザクションをサーバーに転送 4.DdeGetData関数などでデータを取得 5.DdeDisconnect関数で通信終了 6.DdeUninitialize関数でDDEアプリケーションではなくなったことを   サーバーに通知

という手順でやります。複数のデータを取得するには3.4.を繰り返します。

UINT DdeInitialize( LPDWORD pidInst, // インスタンスIDへのポインタ PFNCALLBACK pfnCallback, // コールバック関数へのポインタ DWORD afCmd, // コマンド,フィルターなど DWORD ulRes // 予約済み );

pidInstはインスタンスIDのポインタです。関数が成功すると ここにIDのポインタが入ります。

pfnCallbackはDDEコールバック関数のポインタです。 これは、アプリケーション定義のコールバック関数で トランザクションを処理します。トランザクションとはメッセージみたい なものですが、通常のプロシージャには流れません。

afCmdはコールバック関数の呼び出しにフィルターをかけることができます。
APPCMD_CLIENTONLYはクライアントに徹することを指定します。
あと、CBF_FAIL_***というのがたくさんありますがこれは、コールバック関数が ある種のトランザクションを受け取らないフィルタをかけます。

今回はクライアントに徹するためAPPCMD_CLIENTONLYを指定します。 実際試すとわかりますがコールバック関数は一度も呼ばれません。 (pfnCallbackをNULLにしてコールバック関数をかかなくても動作します。 しかし、ヘルプによると必ずコールバック関数を登録しろと書いてあります)

HCONV DdeConnect( DWORD idInst, // インスタンスID HSZ hszService, // サーバーのサービス名へのハンドル HSZ hszTopic, // トピックの名前へのハンドル PCONVCONTEXT pCC // CONVCONTEXT構造体へのポインタ );

idInstはDdeInitialize関数で取得したIDです。

hszServiceはサーバーのサービス名へのハンドルです。0にすると 有効ないずれかのサーバーとの通信となります。

hszTopicは通信を確立するトピック名へのハンドルです。0にする と有効ないずれかのトピックとなります。

pCCはCONVCONTEXT構造体へのポインタです。 CONVCONTEXT構造体は通信コンテキスト情報を格納します。 これをNULLにするとデフォルトの情報と成ります。 多分自分でCONVCONTEXT構造体を書くことはあまりないのではないか と思います。

HDDEDATA DdeClientTransaction( LPBYTE pData, // サーバーに渡すデータの先頭バイトのポインタ DWORD cbData, // データの長さ HCONV hConv, // 通信ハンドル HSZ hszItem, // データ項目のハンドル UINT wFmt, // クリップボードフォーマット UINT wType, // トランザクションタイプ DWORD dwTimeout, // 待ち時間 LPDWORD pdwResult // トランザクションの結果へのポインタ );

pDataはwTypeがXTYPE_EXECUTEの時のみ必要。他の場合はNULL。

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が返されます。

HSZ DdeCreateStringHandle( DWORD idInst, // インスタンスID LPTSTR psz, // 文字列 int iCodePage // コードページ識別子 );

idInstはインスタンスハンドルです。

pszはハンドルを作成したい文字列です。

iCodePageはANSI版であればCP_WINANSI, Unicode版であればCP_WINUNICODEとなります。

ストリングハンドルが不要になったらDdeFreeStringHandle関数で開放します。

BOOL DdeFreeStringHandle( DWORD idInst, // インスタンスID HSZ hsz // ストリングハンドル );

開放したいストリングハンドルを指定します。

DWORD DdeGetData( HDDEDATA hData, // DDEオブジェクトハンドル LPBYTE pDst, // 取得するデータバッファへのポインタ DWORD cbMax, // コピーするデータ量 DWORD cbOff // データ開始のオフセット );

DDEオブジェクトハンドルが不要になったら DdeFreeDataHandle関数で開放します。

BOOL DdeFreeDataHandle( HDDEDATA hData // DDEオブジェクトハンドル );

通信が終わったら

BOOL DdeDisconnect( HCONV hConv // 通信ハンドル );

で通信ハンドルを無効にします。

DdeUninitialize関数はオープンしているすべての通信を終了します。

BOOL DdeUninitialize( DWORD idInst // インスタンスID );

これで,大体必要な関数が出揃いました。 プログラムを作る前にエクセルで、セルに適当なデータを入力した 「TESTDDE.XLS」というファイルを作っておいて下さい。 このデータを読み出すことにします。

エクセルで左のような適当なデータを作っておきます。 各セルの書式は何でも良いです。また、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

ありふれたリソース・スクリプトです。「キャンセル」は 実際には半角かなです。

// 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];

DDEコールバック関数については解説をしませんでしたが 引数は上のプロトタイプのようにしておいて下さい。

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; }

このへんはいつもと同じです。

//ウィンドウプロシージャ 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開始」「DDE終了」「データの取得」を 選択するとそれぞれの自作関数が呼ばれるようにしてみました。

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をスタートさせる関数です。hszServiceは"Excel"、hszTopicは"DDETEST.xls"に 対するハンドルにしておきます。

これで、エクセルに接続できます。

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; }

DDE接続を終了するときの後始末です。この関数の中で DdeUninitialize関数を呼ぶと次にDdeInitialize関数が失敗します。 そこで、DdeUninitialize関数はアプリケーション終了時に呼び出すことにしました。

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; }

ダイアログボックスのプロシージャです。

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; }

DDEコールバック関数です。ここでは何もしていません。呼ばれたら わかるようにメッセージボックスを入れておきましたが一度も呼ばれません。

さて、これでエクセルから何とかデータを呼び出すプログラムができました。


[SDK第2部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 19/Oct/1998 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。