Home > Blockchain >  DdeConnect Fails with DMLERR_NO_CONV_ESTABLISHED
DdeConnect Fails with DMLERR_NO_CONV_ESTABLISHED

Time:10-26

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:

  1. 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

  2. Modify server DdeInitializeA to only specify APPCMD_FILTERINITS for afCmd

  3. In server callback return DDE_FACK for XTYPE_CONNECT

case XTYP_CONNECT: std::cout << "DDE Callback XTYP_CONNECT" << std::endl; return (HDDEDATA)DDE_FACK;

  • Related