基本概念 (DDE)

这些概念是了解动态数据交换 (DDE) 和动态数据交换管理库 (DDEML) 的关键。

客户端和服务器交互

DDE 始终发生在客户端应用程序和服务器应用程序之间。 DDE 客户端应用程序会与服务器建立对话,以便将事务发送到服务器,从而启动交换。 事务是对数据或服务的请求。 DDE 服务器应用程序通过向客户端提供数据或服务来响应事务。 例如,图形应用程序可能会包含表示公司季度利润的条形图,但条形图的数据可能包含在电子表格应用程序中。 为了获取最新的利润数据,图形应用程序(客户端)可以与电子表格应用程序(服务器)建立对话。 接着,图形应用程序可以将事务发送到电子表格应用程序,以请求最新的利润数据。

服务器可以同时具有多个客户端,并且客户端可以向多个服务器请求数据。 应用程序也可以是客户端和服务器。 客户端或服务器可以随时终止对话。

事务和 DDE 回调函数

DDEML 会将事务发送给应用程序的 DDE 回调函数,以通知应用程序 DDE 活动会影响应用程序。 DDE 事务类似于消息,这是一个命名常量,附带包含有关事务的其他信息的其他参数。

DDEML 将事务传递给应用程序定义的 DDE 回调函数,该函数又会执行适用于事务类型的操作。 例如,当客户端应用程序尝试与服务器应用程序建立对话时,客户端将调用 DdeConnect 函数。 此函数会导致 DDEML 将 XTYP_CONNECT 事务发送到服务器的 DDE 回调函数。 回调函数可以将 TRUE 返回到 DDEML 来允许对话,也可以返回 FALSE 来拒绝对话。 有关事务的详细讨论,请参阅事务管理

服务名称、主题名称和项名称

DDE 服务器使用三级层次结构服务名称(在以前的 DDE 文档中称为“应用程序名称”)、主题名称和项名称来唯一标识服务器在对话期间可以交换的数据单元。

服务名称是服务器应用程序尝试与服务器建立对话时响应的字符串。 客户端必须指定此服务名称才能与服务器建立对话。 尽管服务器可以响应许多服务名称,但大多数服务器仅响应一个名称。

主题名称是标识逻辑数据上下文的字符串。 对于操作基于文件的文档的服务器,主题名称通常是文件名;对于其他服务器,它们是其他特定于应用程序的字符串。 在尝试与服务器建立对话时,客户端必须指定主题名称以及服务器的服务名称。

项名称是一个字符串,用于标识服务器可以在事务期间传递给客户端的数据单元。 例如,项名称可能标识整数、字符串、多段文本或位图。

服务名称、主题名称和项名称促使客户端能够与服务器建立对话,并从服务器接收数据。

系统主题

系统主题提供了有关任何 DDE 客户端通常感兴趣的信息的上下文。 建议服务器应用程序随时支持系统主题。 系统主题在 DDEML.H 标头文件中定义为 SZDDESYS_TOPIC。

若要确定存在哪些服务器及其可以提供的信息类型,客户端应用程序可以在启动时请求有关系统主题的对话,并将设备名称设置为 NULL。 这种通配符对话在系统性能方面成本高昂,因此应将其保持在最低水平。 有关启动 DDE 对话的详细信息,请参阅对话管理

服务器必须支持系统主题中的以下项名称,以及对客户端有用的任何其他项名称。

说明
SZDDE_ITEM_ITEMLIST 非系统主题下支持的项的列表。 (此列表可能会因时刻和主题而异。)
SZDDESYS_ITEM_FORMATS 以制表符分隔的字符串列表,表示服务应用程序可能支持的所有剪贴板格式。 表示预定义剪贴板格式的字符串等效于删除了“CF_”前缀的 CF_ 值。 例如,CF_TEXT 格式由字符串“TEXT”表示。 这些字符串必须大写,才能进一步将其标识为预定义格式。 格式列表必须按内容最丰富到内容最不丰富的顺序显示。 有关剪贴板格式和呈现数据的详细信息,请参阅剪贴板
SZDDESYS_ITEM_HELP 通常感兴趣的用户可读信息。 此项必须至少包含有关如何使用服务器应用程序的 DDE 功能的信息。 此信息包括但不限于如何指定主题中的项、服务器可以执行的字符串、允许哪些戳击事务,以及如何查找有关其他系统主题项的帮助。
SZDDESYS_ITEM_RTNMSG 支持最近使用的 WM_DDE_ACK 消息的详细信息。 当需要超过 8 位应用程序特定的返回数据时,此项非常有用。
SZDDESYS_ITEM_STATUS 指示服务器的当前状态。 通常,此项仅支持 CF_TEXT 格式,并且包含“就绪”或“忙碌”字符串。
SZDDESYS_ITEM_SYSITEMS 此服务器支持的系统主题下的项列表。
SZDDESYS_ITEM_TOPICS 服务器当前支持的主题列表。 (此列表可能会因时刻而异。)

这些项名称是在 DDEML.H 标头文件中定义的值。 若要获取这些字符串的字符串句柄,应用程序必须使用 DDEML 字符串管理函数,就像 DDEML 应用程序中的任何其他字符串一样。 有关管理字符串的详细信息,请参阅字符串管理

初始化

在调用任何其他 DDEML 函数之前,应用程序必须先调用 DdeInitialize 函数。 DdeInitialize 会获取应用程序的实例标识符,向 DDE 注册应用程序的 DDE 回调函数,并指定回调函数的事务筛选器标志。

应用程序或 DLL 的每个实例都必须将其实例标识符作为 idInst 参数传递给任何其他需要它的 DDEML 函数。 多个 DDEML 实例的目的是支持在应用程序使用时必须同时使用 DDEML 的 DLL。 应用程序不得使用 DDEML 的多个实例。

事务筛选器会阻止 DDEML 将不需要的事务传递到应用程序的 DDE 回调函数,以便优化系统性能。 应用程序在 DdeInitializeufCmd 参数中设置事务筛选器。 应用程序必须为其回调函数中未处理的每种类型的事务指定事务筛选器标志。 应用程序可以使用对 DdeInitialize 的后续调用来更改其事务筛选器。 有关事务的详细信息,请参阅事务管理

下面的示例演示如何初始化应用程序,以便使用 DDEML。

DWORD idInst = 0; 
HINSTANCE hinst; 
 
DdeInitialize(&idInst,         // receives instance identifier 
    (PFNCALLBACK) DdeCallback, // pointer to callback function 
    CBF_FAIL_EXECUTES |        // filter XTYPE_EXECUTE 
    CBF_SKIP_ALLNOTIFICATIONS, // filter notifications 
    0); 

当应用程序不再使用 DDEML 时,应用程序必须调用 DdeUninitialize 函数。 此函数会终止当前为应用程序打开的任何对话,并释放为应用程序分配的系统 DDEML 资源。

回调函数

使用 DDEML 的应用程序必须提供处理影响应用程序的 DDE 事件的回调函数。 DDEML 会向应用程序的 DDE 回调函数发送事务,以将此类事件通知给应用程序。 回调函数接收的事务取决于哪些回调筛选器标记 DdeInitialize 中指定的应用程序,以及应用程序是客户端、服务器还是两者。 有关详细信息,请参阅 DdeCallback

以下示例演示典型客户端应用程序的回调函数的一般结构。

HDDEDATA CALLBACK DdeCallback(uType, uFmt, hconv, hsz1, 
    hsz2, hdata, dwData1, dwData2) 
UINT uType;       // transaction type 
UINT uFmt;        // clipboard data format 
HCONV hconv;      // handle to conversation 
HSZ hsz1;         // handle to string 
HSZ hsz2;         // handle to string 
HDDEDATA hdata;   // handle to global memory object 
DWORD dwData1;    // transaction-specific data 
DWORD dwData2;    // transaction-specific data 
{ 
    switch (uType) 
    { 
        case XTYP_REGISTER: 
        case XTYP_UNREGISTER: 
            . 
            . 
            . 
            return (HDDEDATA) NULL; 
 
        case XTYP_ADVDATA: 
            . 
            . 
            . 
            return (HDDEDATA) DDE_FACK; 
 
        case XTYP_XACT_COMPLETE: 
            
            // 
            
            return (HDDEDATA) NULL; 
 
        case XTYP_DISCONNECT: 
            
            // 
            
            return (HDDEDATA) NULL; 
 
        default: 
            return (HDDEDATA) NULL; 
    } 
} 

uType 参数指定由 DDEML 发送到回调函数的事务类型。 其余参数的值取决于事务类型。 以下几个主题介绍了事务类型以及生成它们的事件。 有关每种事务类型的详细信息,请参阅事务管理

字符串管理

若要执行 DDE 任务,许多 DDEML 函数需要访问字符串。 例如,客户端在调用 DdeConnect 函数以请求与服务器建立对话时,必须指定服务名称和主题名称。 应用程序通过传递字符串句柄 (HSZ) 而不是 DDEML 函数中的指针来指定字符串。 字符串句柄是由系统分配的 DWORD 值,用于标识字符串。

应用程序可以调用 DdeCreateStringHandle 函数来获取特定字符串的字符串句柄。 此函数会向系统注册字符串,并向应用程序返回字符串句柄。 应用程序可以将句柄传递给必须访问字符串的 DDEML 函数。 以下示例将获取系统主题字符串和服务名称字符串的字符串句柄。

HSZ hszServName; 
HSZ hszSysTopic; 
hszServName = DdeCreateStringHandle( 
    idInst,         // instance identifier 
    "MyServer",     // string to register 
    CP_WINANSI);    // Windows ANSI code page 
 
hszSysTopic = DdeCreateStringHandle( 
    idInst,         // instance identifier 
    SZDDESYS_TOPIC, // System topic 
    CP_WINANSI);    // Windows ANSI code page 
    

前面示例中的 idInst 参数指定 DdeInitialize 函数获取的实例标识符。

应用程序 DDE 回调函数会在大多数 DDE 事务期间接收一个或多个字符串句柄。 例如,服务器会在 XTYP_REQUEST 事务期间收到两个字符串句柄:一个标识指定主题名称的字符串,另一个标识指定项名称的字符串。 应用程序可以调用 DdeQueryString 函数获取与字符串句柄对应的字符串的长度,并将该字符串复制到应用程序定义的缓冲区,如以下示例所示。

DWORD idInst; 
DWORD cb; 
HSZ hszServ; 
PSTR pszServName; 
cb = DdeQueryString(idInst, hszServ, (LPSTR) NULL, 0, 
    CP_WINANSI) + 1; 
pszServName = (PSTR) LocalAlloc(LPTR, (UINT) cb); 
DdeQueryString(idInst, hszServ, pszServName, cb, CP_WINANSI); 

无法将特定于实例的字符串句柄从字符串句柄映射到字符串,也不能映射回字符串句柄。 例如,尽管 DdeQueryString 会根据字符串句柄创建字符串,然后 DdeCreateStringHandle 根据该字符串创建字符串句柄,但两个句柄不同,如以下示例所示。

DWORD idInst; 
DWORD cb; 
HSZ hszInst, hszNew; 
PSZ pszInst; 
DdeQueryString(idInst, hszInst, pszInst, cb, CP_WINANSI); 
hszNew = DdeCreateStringHandle(idInst, pszInst, CP_WINANSI); 
// hszNew != hszInst ! 

若要比较两个字符串句柄的值,请使用 DdeCmpStringHandles 函数。

当回调函数返回时,传递给应用程序 DDE 回调函数的字符串句柄将变为无效。 应用程序可以使用 DdeKeepStringHandle 函数保存字符串句柄,以便在回调函数返回后使用。

当应用程序调用 DdeCreateStringHandle 时,系统会将指定的字符串输入字符串表中,并生成用于访问字符串的句柄。 系统还会维护字符串表中每个字符串的使用计数。

当应用程序调用 DdeCreateStringHandle,并指定表中已存在的字符串时,系统会递增使用计数,而不是添加字符串的另一个匹配项。 (应用程序还可以使用 DdeKeepStringHandle 递增使用计数。)当应用程序调用 DdeFreeStringHandle 函数时,系统会递减使用计数。

当表的使用计数等于零时,会将字符串从表中删除。 由于多个应用程序可以获取特定字符串的句柄,因此应用程序释放字符串句柄的次数不能超过创建或保留句柄的次数。 否则,应用程序可能会导致从表中删除字符串,从而拒绝其他应用程序访问该字符串。

DDEML 字符串管理函数基于原子管理器,并且受到与原子相同的大小限制的约束。

DDEML 和线程

DdeInitialize 函数会向 DDEML 注册应用程序,从而创建 DDEML 实例。 DDEML 实例基于线程,与调用 DdeInitialize 的线程相关联。

必须从调用 DdeInitialize 来创建实例的同一线程发出属于 DDEML 实例的对象的所有 DDEML 函数调用。 如果从其他线程调用 DDEML 函数,该函数将失败。 不能从分配对话的线程以外的线程访问 DDEML 对话。