動的データ交換の使用
このセクションには、次のタスクのコード サンプルがあります。
会話の開始
動的データ交換 (DDE) 会話を開始するために、クライアントは WM_DDE_INITIATE メッセージを送信します。 通常、クライアントは SendMessage を呼び出してこのメッセージをブロードキャストします。最初のパラメーターは -1 です。 アプリケーションにサーバー アプリケーションへのウィンドウ ハンドルが既にある場合は、そのウィンドウに直接メッセージを送信できます。 クライアントは、 GlobalAddAtom を呼び出して、アプリケーション名とトピック名のアトムを準備します。 クライアントは、アプリケーションとトピックに NULL (ワイルドカード) アトムを指定することで、潜在的な任意のサーバー アプリケーションおよび潜在的なトピックとの会話を要求できます。
次の例は、アプリケーションとトピックの両方が指定されている場合に、クライアントが会話を開始する方法を示しています。
static BOOL fInInitiate = FALSE;
char *szApplication;
char *szTopic;
atomApplication = *szApplication == 0 ?
NULL : GlobalAddAtom((LPSTR) szApplication);
atomTopic = *szTopic == 0 ?
NULL : GlobalAddAtom((LPSTR) szTopic);
fInInitiate = TRUE;
SendMessage((HWND) HWND_BROADCAST, // broadcasts message
WM_DDE_INITIATE, // initiates conversation
(WPARAM) hwndClientDDE, // handle to client DDE window
MAKELONG(atomApplication, // application-name atom
atomTopic)); // topic-name atom
fInInitiate = FALSE;
if (atomApplication != NULL)
GlobalDeleteAtom(atomApplication);
if (atomTopic != NULL)
GlobalDeleteAtom(atomTopic);
Note
アプリケーションで NULL アトムを使用する場合は、 GlobalAddAtom 関数と GlobalDeleteAtom 関数を使用する必要はありません。 この例では、クライアント アプリケーションは、サーバーの名前とトピックの名前をそれぞれ含む 2 つのグローバル アトムを作成します。
クライアント アプリケーションは、メッセージの lParam パラメーターにこれら 2 つのアトムを含む WM_DDE_INITIATE メッセージを送信します。 SendMessage 関数の呼び出しでは、特別なウィンドウ ハンドル –1 は、システムにこのメッセージを他のすべてのアクティブなアプリケーションに送信するように指示します。 SendMessage は、メッセージを受信するすべてのアプリケーションがシステムに制御を返すまで、クライアント アプリケーションに戻りません。 つまり、サーバー アプリケーションによって応答として送信されるすべての WM_DDE_ACK メッセージは、 SendMessage 呼び出しが返された時点までにクライアントによって処理されていることが保証されます。
SendMessage が返されると、クライアント アプリケーションはグローバルアトムを削除します。
サーバー アプリケーションは、次の図に示すロジックに従って応答します。
1 つ以上のトピックを確認するには、サーバーは会話ごとにアトムを作成し (複数のトピックがある場合は重複するアプリケーション名のアトムを必要とする)、次の例に示すように、各会話に WM_DDE_ACK メッセージを送信する必要があります。
if ((atomApplication = GlobalAddAtom("Server")) != 0)
{
if ((atomTopic = GlobalAddAtom(szTopic)) != 0)
{
SendMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
MAKELONG(atomApplication, atomTopic));
GlobalDeleteAtom(atomTopic);
}
GlobalDeleteAtom(atomApplication);
}
if ((atomApplication == 0) || (atomTopic == 0))
{
// Handle errors.
}
サーバーが WM_DDE_ACK メッセージで応答すると、クライアント アプリケーションはサーバー ウィンドウにハンドルを保存する必要があります。 WM_DDE_ACK メッセージの wParam パラメーターとしてハンドルを受け取ったクライアントは、後続のすべての DDE メッセージを、このハンドルが識別するサーバー ウィンドウに送信します。
クライアント アプリケーションがアプリケーション名またはトピック名に NULL アトムを使用している場合は、アプリケーションが複数のサーバー アプリケーションから受信確認を受け取る必要があります。 クライアント アプリケーションが NULL でアトムを使用しない場合でも、DDE サーバーの複数のインスタンスから複数の受信確認を受け取ることもできます。 サーバーでは、会話ごとに常に一意のウィンドウを使用する必要があります。 クライアントアプリケーションのウィンドウプロシージャは、サーバーウィンドウへのハンドル(WM_DDE_INITIATEの lParam パラメータとして提供される)を使用して、複数の会話を追跡することができます。 これにより、1 つのクライアント ウィンドウで複数の会話を処理でき、各会話の新しいクライアント ウィンドウを終了して再接続する必要はありません。
1 つのアイテムの転送
DDE 会話が確立されると、クライアントは、 WM_DDE_REQUEST メッセージを発行してサーバーからデータ項目の値を取得するか、 WM_DDE_POKE を発行してデータ項目の値をサーバーに送信できます。
サーバーからの項目の取得
次の例に示すように、クライアントはサーバーからアイテムを取得するために、取得するアイテムと形式を指定する WM_DDE_REQUEST メッセージをサーバーに送信します。
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!PostMessage(hwndServerDDE,
WM_DDE_REQUEST,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_REQUEST, CF_TEXT, atomItem)))
{
GlobalDeleteAtom(atomItem);
}
}
if (atomItem == 0)
{
// Handle errors.
}
この例では、クライアントは、要求されたデータ項目の優先形式 として CF_TEXT クリップボード形式を指定します。
通常、 WM_DDE_REQUEST メッセージの受信側 (サーバー) は項目のアトムを削除する必要がありますが、 PostMessage 呼び出しが失敗した場合、クライアントはアトムを削除する必要があります。
サーバーが要求された項目にアクセスでき、要求された形式でレンダリングできる場合、サーバーはアイテムの値を共有メモリ オブジェクトとしてコピーし、次の例に示すようにクライアントに WM_DDE_DATA メッセージを送信します。
// Allocate the size of the DDE data header, plus the data: a
// string,<CR><LF><NULL>. The byte for the string's terminating
// null character is counted by DDEDATA.Value[1].
size_t* pcch;
HRESULT hResult;
hResult = StringCchLength(szItemValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
if (!(hData = GlobalAlloc(GMEM_MOVEABLE,
(LONG) sizeof(DDEDATA) + *pcch + 2)))
{
return;
}
if (!(lpData = (DDEDATA FAR*) GlobalLock(hData)))
{
GlobalFree(hData);
return;
}
lpData->cfFormat = CF_TEXT;
hResult = StringCchCopy((LPSTR) lpData->Value, *pcch +1, (LPCSTR) szItemValue); // copies value to be sent
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
// Each line of CF_TEXT data is terminated by CR/LF.
hResult = StringCchCat((LPSTR) lpData->Value, *pcch + 3, (LPCSTR) "\r\n");
if (FAILED(hResult)
{
// TODO: Write error handler.
return;
}
GlobalUnlock(hData);
if ((atomItem = GlobalAddAtom((LPSTR) szItemName)) != 0)
{
lParam = PackDDElParam(WM_DDE_ACK, (UINT) hData, atomItem);
if (!PostMessage(hwndClientDDE,
WM_DDE_DATA,
(WPARAM) hwndServerDDE,
lParam))
{
GlobalFree(hData);
GlobalDeleteAtom(atomItem);
FreeDDElParam(WM_DDE_ACK, lParam);
}
}
if (atomItem == 0)
{
// Handle errors.
}
この例では、サーバー アプリケーションは、データ項目を格納するメモリ オブジェクトを割り当てます。 データ オブジェクトは DDEDATA 構造体として初期化されます。
次に、サーバー アプリケーションは、データが テキスト形式であることをクライアント アプリケーションに通知するために、構造体の cfFormat メンバーをCF_TEXTに設定します。 クライアントは、要求されたデータの値を DDEDATA 構造体の Value メンバーにコピーすることによって応答します。 サーバーがデータ オブジェクトを入力すると、サーバーはデータのロックを解除し、データ項目の名前を含むグローバル アトムを作成します。
最後に、サーバーは PostMessage を呼び出して WM_DDE_DATA メッセージを発行します。 データ オブジェクトへのハンドルと項目名を含むアトムは、 PackDDElParam 関数によってメッセージの lParam パラメーターにパックされます。
PostMessage が失敗した場合、サーバーは FreeDDElParam 関数を使用してパックされた lParam パラメーターを解放する必要があります。 サーバーは、受信した WM_DDE_REQUEST メッセージのパックされた lParam パラメーターも解放する必要があります。
サーバーが要求を満たすことができない場合は、次の例に示すように、ネガティブ WM_DDE_ACK メッセージをクライアントに送信します。
// Negative acknowledgment.
PostMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_ACK, 0, atomItem));
WM_DDE_DATA メッセージを受信すると、クライアントは必要に応じてデータ項目の値を処理します。 次に、 WM_DDE_DATA メッセージで指している fAckReq メンバーが 1 の場合、クライアントは、次の例に示すように、サーバーにポジティブ WM_DDE_ACK メッセージを送信する必要があります。
UnpackDDElParam(WM_DDE_DATA, lParam, (PUINT) &hData,
(PUINT) &atomItem);
if (!(lpDDEData = (DDEDATA FAR*) GlobalLock(hData))
|| (lpDDEData->cfFormat != CF_TEXT))
{
PostMessage(hwndServerDDE,
WM_DDE_ACK,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_ACK, 0, atomItem)); // Negative ACK.
}
// Copy data from lpDDEData here.
if (lpDDEData->fAckReq)
{
PostMessage(hwndServerDDE,
WM_DDE_ACK,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_ACK, 0x8000,
atomItem)); // Positive ACK
}
bRelease = lpDDEData->fRelease;
GlobalUnlock(hData);
if (bRelease)
GlobalFree(hData);
この例では、クライアントはデータの形式を調べます。 形式が CF_TEXT されていない場合 (またはクライアントがデータのメモリをロックできない場合)、クライアントは、データを処理できないことを示すネガティブ WM_DDE_ACK メッセージを送信します。 ハンドルに fAckReq メンバーが含まれているためにクライアントがデータ ハンドルをロックできない場合、クライアントはネガティブ WM_DDE_ACK メッセージを送信しないでください。 代わりに、クライアントは会話を終了する必要があります。
クライアントが WM_DDE_DATA メッセージに応答して否定受信確認を送信した場合、サーバーは、否定受信確認に関連付けられている WM_DDE_DATA メッセージによって参照されるメモリ (ただし、 lParam パラメーターではない) を解放する役割を担います。
データを処理できる場合、クライアントは DDEDATA 構造体の fAckReq メンバーを調べ、クライアントがデータを正常に受信し処理したことを知らせるようサーバーが要求したかどうかを判断します。 サーバーがこの情報を要求した場合、クライアントはサーバーにポジティブ WM_DDE_ACK メッセージを送信します。
データのロックを解除するとデータへのポインターが無効になるため、クライアントはデータ オブジェクトのロックを解除する前に fRelease メンバーの値を保存します。 値を保存した後、クライアントはそれを調べて、サーバー アプリケーションがデータを含むメモリを解放するようにクライアントに要求したかどうかを判断します。クライアントはそれに応じて動作します。
ネガティブ WM_DDE_ACK メッセージを受信すると、クライアントは、別のクリップボード形式を指定して、同じ項目値を再度要求できます。 通常、クライアントは最初にサポートできる最も複雑な形式を要求し、必要に応じて、サーバーが提供できる形式が見つかるまで段階的に単純な形式でステップダウンします。
サーバーがシステム トピックの [書式] 項目をサポートしている場合、クライアントは、クライアントが項目を要求するたびに決定するのではなく、サーバーがサポートするクリップボードの書式を決定できます。
サーバーへのアイテムの送信
クライアントは、 WM_DDE_POKE メッセージを使用してサーバーにアイテム値を送信できます。 クライアントは、送信するアイテムをレンダリングし、次の 例に示すように、 WM_DDE_POKE メッセージを送信します。
size_t* pcch;
HRESULT hResult;
hResult = StringCchLength(szValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
if (!(hPokeData = GlobalAlloc(GMEM_MOVEABLE,
(LONG) sizeof(DDEPOKE) + *pcch + 2)))
{
return;
}
if (!(lpPokeData = (DDEPOKE *) GlobalLock(hPokeData)))
{
GlobalFree(hPokeData);
return;
}
lpPokeData->fRelease = TRUE;
lpPokeData->cfFormat = CF_TEXT;
hResult = StringCchCopy((LPSTR) lpPokeData->Value, *pcch +1, (LPCSTR) szValue); // copies value to be sent
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
// Each line of CF_TEXT data is terminated by CR/LF.
hResult = StringCchCat((LPSTR) lpPokeData->Value, *pcch + 3, (LPCSTR) "\r\n");
if (FAILED(hResult)
{
// TODO: Write error handler.
return;
}
GlobalUnlock(hPokeData);
if ((atomItem = GlobalAddAtom((LPSTR) szItem)) != 0)
{
if (!PostMessage(hwndServerDDE,
WM_DDE_POKE,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_POKE, (UINT) hPokeData,
atomItem)))
{
GlobalDeleteAtom(atomItem);
GlobalFree(hPokeData);
}
}
if (atomItem == 0)
{
// Handle errors.
}
Note
WM_DDE_POKE メッセージを使用したデータの送信は、 WM_DDE_DATA を使用して送信する場合と基本的に同じですが、 WM_DDE_POKE がクライアントからサーバーに送信される点が異なります。
サーバーがクライアントによってレンダリングされた形式でデータ項目の値を受け入れることができる場合、サーバーは必要に応じて項目の値を処理し、クライアントにポジティブ WM_DDE_ACK メッセージを 送信します。 その形式またはその他の理由により、アイテム値を処理できない場合、サーバーはクライアントにネガティブ WM_DDE_ACK メッセージを送信します。
UnpackDDElParam(WM_DDE_POKE, lParam, (PUINT) &hPokeData,
(PUINT) &atomItem);
GlobalGetAtomName(atomItem, szItemName, ITEM_NAME_MAX_SIZE);
if (!(lpPokeData = (DDEPOKE *) GlobalLock(hPokeData))
|| lpPokeData->cfFormat != CF_TEXT
|| !IsItemSupportedByServer(szItemName))
{
PostMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_ACK, 0, atomItem)); // negative ACK
}
hResult = StringCchLength(szItemValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
hResult = StringCchCopy(szItemValue, *pcch +1, lpPokeData->Value); // copies value
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
bRelease = lpPokeData->fRelease;
GlobalUnlock(hPokeData);
if (bRelease)
{
GlobalFree(hPokeData);
}
PostMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_ACK,
0x8000, atomItem)); // positive ACK.
この例では、サーバーは GlobalGetAtomName を呼び出して、クライアントが送信したアイテムの名前を取得します。 その後、サーバーは、項目がサポートされているかどうか、および項目が正しい形式 (つまり、CF_TEXT) でレンダリングされるかどうかを判断します。 項目がサポートされておらず、正しい形式でレンダリングされていない場合、またはサーバーがデータのメモリをロックできない場合、サーバーは否定受信確認をクライアント アプリケーションに送信します。 この場合、 WM_DDE_POKE メッセージは常に fAckReq メンバーが設定されていると見なされるため、否定受信確認の送信は正しいことに注意してください。 サーバーはメンバーを無視する必要があります。
サーバーが WM_DDE_POKE メッセージに応答して否定受信確認を送信した場合、サーバーは、否定受信確認に関連付けられている WM_DDE_POKE メッセージによって参照されるメモリ (ただし、 lParam パラメーターではない) を解放する役割を担います。
永続的なデータ リンクの確立
クライアント アプリケーションは、DDE を使用して、サーバー アプリケーション内の項目へのリンクを確立できます。 このようなリンクが確立されると、通常、アイテムの値が変更されるたびに、サーバーはリンクされたアイテムの定期的な更新をクライアントに送信します。 したがって、永続的なデータ ストリームは、2 つのアプリケーション間で確立されます。このデータ ストリームは、明示的に切断されるまでメイン配置されます。
データ リンクの開始
クライアントは、次の例に示すように、 WM_DDE_ADVISE メッセージを投稿してデータ リンクを開始します。
if (!(hOptions = GlobalAlloc(GMEM_MOVEABLE,
sizeof(DDEADVISE))))
return;
if (!(lpOptions = (DDEADVISE FAR*) GlobalLock(hOptions)))
{
GlobalFree(hOptions);
return;
}
lpOptions->cfFormat = CF_TEXT;
lpOptions->fAckReq = TRUE;
lpOptions->fDeferUpd = FALSE;
GlobalUnlock(hOptions);
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!(PostMessage(hwndServerDDE,
WM_DDE_ADVISE,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_ADVISE, (UINT) hOptions,
atomItem))))
{
GlobalDeleteAtom(atomItem);
GlobalFree(hOptions);
FreeDDElParam(WM_DDE_ADVISE, lParam);
}
}
if (atomItem == 0)
{
// Handle errors
}
この例では、クライアント アプリケーションは、 WM_DDE_ADVISE メッセージの fDeferUpd フラグを FALSE に設定します。 これにより、データが変更されるたびにサーバー アプリケーションにデータがクライアントに送信されます。
サーバーが WM_DDE_ADVISE リクエストに対応できなければ、クライアントにネガティブ WM_DDE_ACK メッセージを送ります。 ただし、サーバーがアイテムにアクセスでき、要求された形式でレンダリングできる場合、サーバーは新しいリンク (hOptions パラメーターで指定されたフラグを呼び出す) をメモし、クライアントにポジティブ WM_DDE_ACK メッセージを送信します。 それ以降、クライアントが一致する WM_DDE_UNADVISE メッセージを発行するまで、サーバーは、サーバーで項目の値が変更されるたびに新しいデータをクライアントに送信します。
WM_DDE_ADVISE メッセージは、リンク中に交換されるデータの形式を確立します。 クライアントが同じ項目を持つ別のリンクを確立しようとしても、別のデータ形式を使用している場合、サーバーは 2 番目のデータ形式を拒否するか、サポートを試みることができます。 いずれかのデータ項目に対してウォーム リンクが確立されている場合、サーバーは一度に 1 つのデータ形式のみをサポートできます。 これは、ウォーム リンクの WM_DDE_DATA メッセージに NULL データ ハンドルがあり、それ以外の場合は形式情報が含まれているためです。 したがって、サーバーは、既にリンクされているアイテムのすべてのウォーム リンクを拒否し、ウォーム リンクを持つアイテムのすべてのリンクを拒否する必要があります。 別の解釈として、同じデータ項目に対して 2 つ目のリンクが要求されたときに、サーバーがリンクの形式とホットまたはウォーム状態を変更する場合があります。
一般に、クライアント アプリケーションは、データ項目に対して一度に複数のリンクを確立しないでください。
[リンクの貼り付け] コマンドを使用してデータ リンクを開始する
ホット データ リンクまたはウォーム データ リンクをサポートするアプリケーションでは、通常、Link という名前の登録済みのクリップボード形式がサポートされます。 アプリケーションのコピーとリンクの貼り付けコマンドに関連付けられている場合、このクリップボード形式を使用すると、サーバー アプリケーション内のデータ項目をコピーしてクライアント アプリケーションに貼り付けるだけで、アプリケーション間で DDE の会話を確立できます。
サーバーアプリケーションは、 Edit メニューから Copy コマンドを選択したときに、アプリケーション名、トピック名、アイテム名を含む文字列をクリップボードに配置することで、リンククリップボードフォーマットをサポートします。 標準のリンク形式を次に示します。
application**\0topic\0item\0\0**
1 つの null 文字で名前が区切られ、2 つの null 文字によって文字列全体が終了します。
次に示すように、クライアント アプリケーションとサーバー アプリケーションの両方でリンク クリップボード形式を登録する必要があります。
cfLink = RegisterClipboardFormat("Link");
クライアント アプリケーションは、[編集] メニューの [リンクの貼り付け] コマンドを使用して、リンク クリップボード形式をサポートします。 ユーザーがこのコマンドを選択すると、クライアント アプリケーションは、リンク形式のクリップボード データからアプリケーション、トピック、および項目の名前を解析します。 これらの名前を使用して、クライアント アプリケーションはアプリケーションとトピックの会話を開始します (そのような会話がまだ存在しない場合)。 その後、クライアント アプリケーションは、リンク形式のクリップボード データに含まれる項目名を指定して、 WM_DDE_ADVISE メッセージをサーバー アプリケーションに送信します。
ユーザーが [リンクの貼り付け] コマンドを選択したときのクライアント アプリケーションの応答の例を次に示します。
void DoPasteLink(hwndClientDDE)
HWND hwndClientDDE;
{
HANDLE hData;
LPSTR lpData;
HWND hwndServerDDE;
CHAR szApplication[APP_MAX_SIZE + 1];
CHAR szTopic[TOPIC_MAX_SIZE + 1];
CHAR szItem[ITEM_MAX_SIZE + 1];
size_t * nBufLen;
HRESULT hResult;
if (OpenClipboard(hwndClientDDE))
{
if (!(hData = GetClipboardData(cfLink)) ||
!(lpData = GlobalLock(hData)))
{
CloseClipboard();
return;
}
// Parse the clipboard data.
hResult = StringCchLength(lpData, STRSAFE_MAX_CCH, nBufLen);
if (FAILED(hResult) || nBufLen == NULL)
{
// TODO: Write error handler.
return;
}
if (*nBufLen >= APP_MAX_SIZE)
{
CloseClipboard();
GlobalUnlock(hData);
return;
}
hResult = StringCchCopy(szApplication, APP_MAX_SIZE +1, lpData);
if (FAILED(hResult)
{
// TODO: Write error handler.
return;
}
lpData += (*nBufLen + 1); // skips over null
hResult = StringCchLength(lpData, STRSAFE_MAX_CCH, nBufLen);
if (FAILED(hResult) || nBufLen == NULL)
{
// TODO: Write error handler.
return;
}
if (*nBufLen >= TOPIC_MAX_SIZE)
{
CloseClipboard();
GlobalUnlock(hData);
return;
}
hResult = StringCchCopy(szTopic, TOPIC_MAX_SIZE +1, lpData);
if (FAILED(hResult)
{
// TODO: Write error handler.
return;
}
lpData += (nBufLen + 1); // skips over null
hResult = StringCchLength(lpData, STRSAFE_MAX_CCH, nBufLen);
if (FAILED(hResult) || nBufLen == NULL)
{
// TODO: Write error handler.
return;
}
if (*nBufLen >= ITEM_MAX_SIZE)
{
CloseClipboard();
GlobalUnlock(hData);
return;
}
hResult = StringCchCopy(szItem, ITEM_MAX_SIZE +1, lpData);
if (FAILED(hResult)
{
// TODO: Write error handler.
return;
}
GlobalUnlock(hData);
CloseClipboard();
if (hwndServerDDE =
FindServerGivenAppTopic(szApplication, szTopic))
{
// App/topic conversation is already started.
if (DoesAdviseAlreadyExist(hwndServerDDE, szItem))
{
MessageBox(hwndMain,
"Advisory already established",
"Client", MB_ICONEXCLAMATION | MB_OK);
}
else SendAdvise(hwndClientDDE, hwndServerDDE, szItem);
}
else
{
// Client must initiate a new conversation first.
SendInitiate(szApplication, szTopic);
if (hwndServerDDE =
FindServerGivenAppTopic(szApplication,
szTopic))
{
SendAdvise(hwndClientDDE, hwndServerDDE, szItem);
}
}
}
return;
}
この例では、クライアント アプリケーションはクリップボードを開き、以前に登録したリンク形式 (cfLink) のデータが含まれているかどうかを判断します。 ロックされていない場合、またはクリップボードのデータをロックできない場合は、クライアントが返します。
クライアント アプリケーションは、クリップボード データへのポインターを取得した後、データを解析して、アプリケーション、トピック、および項目の名前を抽出します。
クライアント アプリケーションは、トピックとサーバー アプリケーションの間にトピックの会話が既に存在するかどうかを判断します。 会話が存在する場合、クライアントはデータ項目のリンクが既に存在するかどうかをチェックします。 このようなリンクが存在する場合、クライアントはユーザーにメッセージ ボックスを表示します。それ以外の場合は、独自の SendAdvise 関数を呼び出して、項目の WM_DDE_ADVISE メッセージをサーバーに送信します。
トピックの会話がクライアントとサーバーの間にまだ存在しない場合、クライアントは最初に独自の SendInitiate 関数を呼び出して WM_DDE_INITIATE メッセージをブロードキャストして会話を要求し、次に、独自の FindServerGivenAppTopic 関数を呼び出して、サーバー アプリケーションの代わりに応答するウィンドウとの会話を確立します。 会話が開始されると、クライアント アプリケーションは SendAdvise を呼び出してリンクを要求します。
データが変更されたことをクライアントに通知する
クライアントが DDEDATA 構造体で fDeferUpd メンバーが設定されていない (つまり 0 に等しい) WM_DDE_ADVISE メッセージを使用してリンクを確立すると、クライアントはアイテムの値が変更されるたびにサーバーにデータ項目の送信を要求しました。 このような場合、サーバーは、前に指定した形式でデータ項目の新しい値をレンダリングし、次の例に示すように、クライアントに WM_DDE_DATA メッセージを送信します。
// Allocate the size of a DDE data header, plus data (a string),
// plus a <CR><LF><NULL>
size_t* pcch;
HRESULT hResult;
hResult = StringCchLength(szItemValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
if (!(hData = GlobalAlloc(GMEM_MOVEABLE,
sizeof(DDEDATA) + *pcch + 3)))
{
return;
}
if (!(lpData = (DDEDATA FAR*) GlobalLock(hData)))
{
GlobalFree(hData);
return;
}
lpData->fAckReq = bAckRequest; // as in original WM_DDE_ADVISE
lpData->cfFormat = CF_TEXT;
hResult = StringCchCopy(lpData->Value, *pcch +1, szItemValue); // copies value to be sent
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
// add CR/LF for CF_TEXT format
hResult = StringCchCat(lpData->Value, *pcch + 3, "\r\n");
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
GlobalUnlock(hData);
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!PostMessage(hwndClientDDE,
WM_DDE_DATA,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_DATA, (UINT) hData, atomItem)))
{
GlobalFree(hData);
GlobalDeleteAtom(atomItem);
FreeDDElParam(WM_DDE_DATA, lParam);
}
}
if (atomItem == 0)
{
// Handle errors.
}
この例では、クライアントは必要に応じて項目の値を処理します。 項目の fAckReq フラグが設定されている場合、クライアントはサーバーにポジティブ WM_DDE_ACK メッセージを送信します。
クライアントが fDeferUpd メンバー セット (つまり 1) を使用してリンクを確立すると、クライアントは、データ自体ではなく通知のみをデータが変更されるたびに送信するように要求しました。 このような場合、項目の値が変更されると、サーバーは値をレンダリングせず、次の例に示すように、null データ ハンドルを持つ WM_DDE_DATA メッセージをクライアントに送信します。
if (bDeferUpd) // check whether flag was set in WM_DDE_ADVISE
{
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!PostMessage(hwndClientDDE,
WM_DDE_DATA,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_DATA, 0,
atomItem))) // NULL data
{
GlobalDeleteAtom(atomItem);
FreeDDElParam(WM_DDE_DATA, lParam);
}
}
}
if (atomItem == 0)
{
// Handle errors.
}
必要に応じて、クライアントは通常の WM_DDE_REQUEST メッセージを発行してデータ項目の最新の値を要求できます。または、データが変更されたことを示すサーバーからの通知を無視するだけです。 いずれの場合も、 fAckReq が 1 の場合、クライアントはサーバーにポジティブ WM_DDE_ACK メッセージを送信することが期待されます。
データ リンクの終了
クライアントが特定のデータ リンクの終了を要求した場合、次の例に示すように、クライアントはサーバーに WM_DDE_UNADVISE メッセージを送信します。
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!PostMessage(hwndServerDDE,
WM_DDE_UNADVISE,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_UNADVISE, 0, atomItem)))
{
GlobalDeleteAtom(atomItem);
FreeDDElParam(WM_DDE_UNADVISE, lParam);
}
}
if (atomItem == 0)
{
// Handle errors.
}
サーバーは、クライアントがこの会話内の特定のアイテムへのリンクを現在持っているかどうかをチェックします。 リンクが存在する場合、サーバーはクライアントにポジティブ WM_DDE_ACK メッセージを送信します。その後、サーバーはアイテムに関する更新プログラムを送信する必要がなくなります。 リンクが存在しない場合、サーバーはクライアントに負 のWM_DDE_ACKメッセージを 送信します。
WM_DDE_UNADVISE メッセージは、データ形式を指定します。 0 の形式は、複数のホット リンクが確立され、それぞれが異なる形式を使用している場合でも、指定された項目のすべてのリンクを停止するようにサーバーに通知します。
メッセージ交換のすべてのリンクを終了するために、クライアント アプリケーションは、null 項目のアトムを含む WM_DDE_UNADVISE メッセージをサーバーに送信します。 サーバーは、会話に現在少なくとも 1 つのリンクが確立されているかどうかを判断します。 リンクが存在する場合、サーバーはクライアントにポジティブ WM_DDE_ACK メッセージを送信します。その後、サーバーは会話の更新を送信する必要がなくなります。 リンクが存在しない場合、サーバーはクライアントに負 のWM_DDE_ACKメッセージを 送信します。
サーバー アプリケーションでのコマンドの実行
アプリケーションは、 WM_DDE_EXECUTE メッセージを使用して、特定のコマンドまたは一連のコマンドを別のアプリケーションで実行できます。 これを行うために、クライアントは、次の例に示すように、コマンド文字列へのハンドルを含む WM_DDE_EXECUTE メッセージをサーバーに送信します。
HRESULT hResult;
if (!(hCommand = GlobalAlloc(GMEM_MOVEABLE,
sizeof(szCommandString) + 1)))
{
return;
}
if (!(lpCommand = GlobalLock(hCommand)))
{
GlobalFree(hCommand);
return;
}
hResult = StringCbCopy(lpCommand, sizeof(szCommandString), szCommandString);
if (hResult != S_OK)
{
// TODO: Write error handler.
return;
}
GlobalUnlock(hCommand);
if (!PostMessage(hwndServerDDE,
WM_DDE_EXECUTE,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_EXECUTE, 0, (UINT) hCommand)))
{
GlobalFree(hCommand);
FreeDDElParam(WM_DDE_EXECUTE, lParam);
}
この例では、サーバーは指定されたコマンド文字列の実行を試みます。 成功すれば、サーバーはクライアントにポジティブ WM_DDE_ACK メッセージを送り、そうでなければネガティブ WM_DDE_ACK メッセージを送ります。 この WM_DDE_ACK メッセージは、元の WM_DDE_EXECUTE メッセージで渡された hCommand ハンドルを再利用します。
クライアントのコマンド実行文字列がサーバーの終了を要求した場合、サーバーはポジティブ WM_DDE_ACK メッセージを送信することで応答し、終了する前に WM_DDE_TERMINATE メッセージを投稿する必要があります。 WM_DDE_EXECUTE メッセージと共に送信されるその他のすべてのコマンドは同期的に実行する必要があります。つまり、サーバーはコマンドが正常に完了した後にのみ、 WM_DDE_ACK メッセージを送信する必要があります。
会話の終了
クライアントまたはサーバーは、 WM_DDE_TERMINATE メッセージを発行して、いつでも会話を終了できます。 同様に、クライアント アプリケーションとサーバー アプリケーションは、いつでもこのメッセージを受信できるように準備する必要があります。 アプリケーションは、シャットダウンする前に、すべての会話を終了する必要があります。
次の例では、メッセージ交換を終了するアプリケーションは、 WM_DDE_TERMINATE メッセージを投稿します。
PostMessage(hwndServerDDE, WM_DDE_TERMINATE,
(WPARAM) hwndClientDDE, 0);
これにより、送信アプリケーションはそれ以上メッセージを送信せず、受信者はそのウィンドウを閉じることができることを他のアプリケーションに通知します。 受信者は、すべてのケースで、 WM_DDE_TERMINATE メッセージを送信して迅速に応答することが求められます。 受信者は、否定的なメッセージ、ビジーメッセージ、またはポジティブ WM_DDE_ACK メッセージを送信してはなりません。
アプリケーションが DDE 会話で WM_DDE_TERMINATE メッセージをパートナーに送信した後、パートナーが応答の送信先となるウィンドウを破棄した可能性があるため、そのパートナーからのメッセージに応答しないようにする必要があります。
アプリケーションが WM_DDE_TERMINATE をポストした後に WM_DDE_TERMINATE 以外の DDE メッセージを受信した場合は、 fRelease メンバーが 設定されていない WM_DDE_DATA または WM_DDE_POKE メッセージのデータ ハンドルを除き、受信したメッセージに関連付けられているすべてのオブジェクトを解放する必要があります。
アプリケーションが終了しようとしているときに、 WM_DESTROY メッセージの処理を完了する前に、すべてのアクティブな DDE 会話を終了する必要があります。 ただし、アプリケーションがアクティブな DDE 会話を終了しない場合、システムは、ウィンドウが破棄されるときに、ウィンドウに関連付けられている DDE 会話を終了します。 次の例は、サーバー アプリケーションがすべての DDE 会話を終了する方法を示しています。
void TerminateConversations(hwndServerDDE)
HWND hwndServerDDE;
{
HWND hwndClientDDE;
// Terminate each active conversation.
while (hwndClientDDE = GetNextLink(hwndClientDDE))
{
SendTerminate(hwndServerDDE, hwndClientDDE);
}
return;
}
BOOL AtLeastOneLinkActive(VOID)
{
return TRUE;
}
HWND GetNextLink(hwndDummy)
HWND hwndDummy;
{
return (HWND) 1;
}
VOID SendTerminate(HWND hwndServerDDE, HWND hwndClientDDE)
{
return;
}