【发布时间】:2021-12-10 14:04:17
【问题描述】:
我正在尝试创建一个最小的 DDE 客户端和服务器,以验证我对如何使用 Dynamic Data Exchange Management Library (DDEML) 有正确的理解。
我有两个简单的 Visual C++ 2019 项目,设置如下:
- 配置属性 -> 常规 -> 平台工具集 Visual Studio 2017 -> Windows XP (v141_xp)
- C/C++ -> 语言 -> 一致性模式 -> 否
- 链接器 -> 子系统 -> Windows
DdeServer.cpp
#define _WIN32_WINNT 0x0501
#include <iostream>
#include <Windows.h>
HDDEDATA CALLBACK DdeCallback(
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:
std::cout << "DDE Callback XTYP_REGISTER" << std::endl;
return (HDDEDATA)NULL;
case XTYP_UNREGISTER:
std::cout << "DDE Callback XTYP_UNREGISTER" << std::endl;
return (HDDEDATA)NULL;
case XTYP_ADVDATA:
std::cout << "DDE Callback XTYP_ADVDATA" << std::endl;
return (HDDEDATA)DDE_FACK;
case XTYP_XACT_COMPLETE:
std::cout << "DDE Callback XTYP_XACT_COMPLETE" << std::endl;
return (HDDEDATA)NULL;
case XTYP_CONNECT:
std::cout << "DDE Callback XTYP_CONNECT" << std::endl;
return (HDDEDATA)NULL;
case XTYP_DISCONNECT:
std::cout << "DDE Callback XTYP_DISCONNECT" << std::endl;
return (HDDEDATA)NULL;
default:
return (HDDEDATA)NULL;
}
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
case WM_DESTROY:
case WM_QUERYENDSESSION:
default:
{
return(DefWindowProc(hWnd, uMsg, wParam, lParam));
}
}
return(0L);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
AllocConsole();
FILE* new_stdout;
freopen_s(&new_stdout, "CONOUT$", "w", stdout);
DWORD pidInst = 0;
UINT uResult = 0;
uResult = DdeInitializeA(
&pidInst,
DdeCallback,
APPCLASS_MONITOR | // this is a monitoring application
MF_CALLBACKS | // monitor callback functions
MF_CONV | // monitor conversation data
MF_ERRORS | // monitor DDEML errors
MF_HSZ_INFO | // monitor data handle activity
MF_LINKS | // monitor advise loops
MF_POSTMSGS | // monitor posted DDE messages
MF_SENDMSGS, // monitor sent DDE messages
0); // reserved
HSZ ddeService = DdeCreateStringHandleA(pidInst, "MyDdeServer", CP_WINANSI);
DdeNameService(pidInst, ddeService, 0, DNS_REGISTER);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return(msg.wParam);
}
DdeClient.cpp
#define _WIN32_WINNT 0x0501
#include <iostream>
#include <Windows.h>
HDDEDATA CALLBACK DdeCallback(
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:
std::cout << "DDE Callback XTYP_REGISTER" << std::endl;
return (HDDEDATA)NULL;
case XTYP_UNREGISTER:
std::cout << "DDE Callback XTYP_UNREGISTER" << std::endl;
return (HDDEDATA)NULL;
case XTYP_ADVDATA:
std::cout << "DDE Callback XTYP_ADVDATA" << std::endl;
return (HDDEDATA)DDE_FACK;
case XTYP_XACT_COMPLETE:
std::cout << "DDE Callback XTYP_XACT_COMPLETE" << std::endl;
return (HDDEDATA)NULL;
case XTYP_CONNECT:
std::cout << "DDE Callback XTYP_CONNECT" << std::endl;
return (HDDEDATA)NULL;
case XTYP_DISCONNECT:
std::cout << "DDE Callback XTYP_DISCONNECT" << std::endl;
return (HDDEDATA)NULL;
default:
return (HDDEDATA)NULL;
}
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
case WM_DESTROY:
case WM_QUERYENDSESSION:
default:
{
return(DefWindowProc(hWnd, uMsg, wParam, lParam));
}
}
return(0L);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
AllocConsole(); // Allocs a console if you do not have one.
FILE* new_stdout;
freopen_s(&new_stdout, "CONOUT$", "w", stdout);
DWORD pidInst = 0;
UINT uResult = 0;
uResult = DdeInitializeA(
&pidInst,
DdeCallback,
APPCLASS_MONITOR | // this is a monitoring application
MF_CALLBACKS | // monitor callback functions
MF_CONV | // monitor conversation data
MF_ERRORS | // monitor DDEML errors
MF_HSZ_INFO | // monitor data handle activity
MF_LINKS | // monitor advise loops
MF_POSTMSGS | // monitor posted DDE messages
MF_SENDMSGS, // monitor sent DDE messages
0);
HSZ stringHandle = DdeCreateStringHandleA(pidInst, "MyDdeClient", CP_WINANSI);
DdeNameService(pidInst, stringHandle, 0, DNS_REGISTER);
HSZ ddeService = DdeCreateStringHandleA(pidInst, "MyDdeServer", CP_WINANSI);
HSZ ddeTopic = DdeCreateStringHandleA(pidInst, "MyDdeTopic", CP_WINANSI);
HCONV hConv = DdeConnect(pidInst, ddeService, ddeTopic, NULL);
if (hConv == 0L)
{
UINT ddeLastError = DdeGetLastError(pidInst);
std::cout << "DDE connect failed error#" << ddeLastError << std::endl;
}
std::cout << "Wating..." << std::endl;
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return(msg.wParam);
}
但是,DdeConnect() 失败并出现错误 16390,即 DMLERR_INVALIDPARAMETER。
根据DdeGetLastError documentation,这通常是由以下问题之一引起的:
- 应用程序使用了一个数据句柄,其初始化的项目名称句柄与事务所需的句柄不同。
- 应用程序使用的数据句柄使用不同于事务所需的剪贴板数据格式进行初始化。
- 应用程序使用客户端会话句柄和服务器端函数,反之亦然。
- 应用程序使用了释放的数据句柄或字符串句柄。
- 应用程序的多个实例使用同一个对象。
根据 cmets 中的反馈,此错误是由 pidInst 未初始化为 0 引起的。修复此错误后,错误变为 DMLERR_NO_CONV_ESTABLISHED,这意味着“客户端建立对话的尝试失败。”
但是,我无法准确指出问题出在哪里。我正在尝试找出我需要更改的内容以使DdeConnect() 工作。我没有看到服务器获取 XTYP_CONNECT 事件。
【问题讨论】:
-
您在玩 DDEML 是因为您需要与另一个使用 DDE 的组件进行交互吗?这真的是 DDE 唯一有效的用例了。你不应该尝试将它用于任何新事物。
-
我不想使用 DDE,但我正在调试我的 16 位 Windows 仿真层,以尝试在 64 位 Windows 10 上运行具有丢失源代码的关键业务 Visual Basic 3 应用程序。它使用 DDE 通过 VB3 运行时在两个组件之间进行通信。我正在模拟 16 位 CPU 指令并拦截 Win16 API 调用并试图让它们与 Win32 API 一起工作,除了 DDE 之外一切都正常。
-
这可能是不相关的,尽管你的 DdeServer.cpp 设置
_WIN32_WINNTafter#include <Windows.h>很奇怪。 -
也和这个问题无关,但是你考虑过反编译VB3可执行文件吗?据我了解,应该可以得到非常接近原始源代码的东西。
-
您必须在调用 DdeInitialize 之前将客户端的 pidInst 初始化为 0,而不是让它未初始化。此外 DdeCallback 应该使用 UINT_PTR dwData args 而不是 DWORD (不是一个大问题,与您的实际问题无关)。然后您显然必须在服务器中处理传入的 XTYP_CONNECT。