I'm trying to create a minimal DDE client & server to validate that I have a correct understanding of how to use Dynamic Data Exchange Management Library (DDEML).
I have two simple Visual C 2019 projects with the following settings:
- Configuration Propertes -> General -> Platform Toolset Visual Studio 2017 -> Windows XP (v141_xp)
- C/C -> Language -> Conformance mode -> No
- Linker -> Subsystem -> 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);
}
However, DdeConnect()
was failing with error 16390, which is DMLERR_INVALIDPARAMETER
.
According to the DdeGetLastError
documentation, this is typically caused by one of the following issues:
- The application used a data handle initialized with a different item name handle than was required by the transaction.
- The application used a data handle that was initialized with a different clipboard data format than was required by the transaction.
- The application used a client-side conversation handle with a server-side function or vice versa.
- The application used a freed data handle or string handle.
- More than one instance of the application used the same object.
Based on feedback in comments this error was caused by pidInst not being initialized to 0. Once this was fixed the error became DMLERR_NO_CONV_ESTABLISHED which means "A client's attempt to establish a conversation has failed."
However, I've been unable to pinpoint exactly where the issue is. I'm trying to work out what I need to change in order for DdeConnect()
to work. I'm not seeing the server get XTYP_CONNECT event.
CodePudding user response:
The answer can be gleaned from reading the documentation:
APPCLASS_MONITOR
Makes it possible for the application to monitor DDE activity in the system. This flag is for use by DDE monitoring applications. The application specifies the types of DDE activity to monitor by combining one or more monitor flags with the APPCLASS_MONITOR flag. For details, see the following Remarks section.
Remarks
A DDE monitoring application should not attempt to perform DDE operations (establish conversations, issue transactions, and so on) within the context of the same application instance.
In other words, APPCLASS_MONITOR is intended only for a DDE monitoring/debugging application. DDE applications that provide DDE services should not specify that flag.
CodePudding user response:
Three items needed to be resolved for DdeConnect to work:
As pointed out in comments ensure pidInst is initialized to 0 before calling DdeInitialize
DWORD pidInst = 0; UINT uResult = 0; uResult = DdeInitializeA( &pidInst, DdeCallback, etc
Modify server DdeInitializeA to only specify APPCMD_FILTERINITS for afCmd
In server callback return DDE_FACK for XTYPE_CONNECT
case XTYP_CONNECT: std::cout << "DDE Callback XTYP_CONNECT" << std::endl; return (HDDEDATA)DDE_FACK;