Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# to C++ process with WM_COPYDATA passing struct with strings

From a c# program I want to use WM_COPYDATA with SendMessage to communicate with a legacy c++/cli MFC application.

I want to pass a managed struct containing string objects.

I can find the handle to the c++ application for use with SendMessage fine.

The bit I don't know about is how the struct and it's strings can be marshalled and read at the other end. Especially as it contains non-blittables.

Do people think this is feasible? I'll continue to work on it, but would apprecite someone who's done this sort of thing telling me if it just isn't going to work.

Here is some demo code if it was a c++/cli program and it's not difficult to get it working. However, I'd like this to be in a .Net class library so it can easily be re-used.

//Quick demonstation code only, not correctly styled
int WINAPI WinMain(HINSTANCE hInstance,
                 HINSTANCE hPrevInstance,
                 LPSTR lpCmdLine,
                 int nCmdShow)
{               
    struct MessageInfo
    {
        int     nVersion;
        char   szTest[ 10 ];        
    };

    MessageInfo sMessageInfo;

    sMessageInfo.nVersion = 100;
    strcpy( sMessageInfo.szTest, "TEST");   

    COPYDATASTRUCT CDS;

    CDS.dwData = 1; //just for test
    CDS.cbData = sizeof( sMessageInfo );
    CDS.lpData = &sMessageInfo;

    //find running processes and send them a message
    //can't just search for "MYAPP.exe" as will be called "MYAPP.exe *32" on a 64bit machine
    array<System::Diagnostics::Process^>^allProcesses = System::Diagnostics::Process::GetProcesses();

    for each (System::Diagnostics::Process^ targetProcess in allProcesses)
    {        
        if (targetProcess->ProcessName->StartsWith("MYAPP", System::StringComparison::OrdinalIgnoreCase))
        {
            HWND handle = static_cast<HWND>(targetProcess->MainWindowHandle.ToPointer());

            BOOL bReturnValue = SendMessage( handle, WM_COPYDATA, (WPARAM)0, (LPARAM)&CDS ) == TRUE;
        }
    }

    return 0;
}
like image 717
James_UK_DEV Avatar asked Oct 05 '12 10:10

James_UK_DEV


1 Answers

I have it working.

A simple approach is to serialize the struct to a single string and transfer a string. The swhistlesoft blog was helpful http://www.swhistlesoft.com/blog/2011/11/19/1636-wm_copydata-with-net-and-c

This may be enough to provide the simple messaging. The struct can be re-constructed at the other end if necessary.

If a struct with any number of strings is to be marshalled as-is then it must be a fixed size, that's the main thing I wasn't getting. The

MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 9)

basically sets the size to match the c++ size which in our case is a TCHAR szTest[ 9 ];

In order to transfer a .Net struct via WM_COPYDATA from c# to c++(/cli) I had to do as follows:

[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

    [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    static extern bool SetForegroundWindow(IntPtr hWnd);

public static uint WM_COPYDATA = 74;

//from swhistlesoft
public static IntPtr IntPtrAlloc<T>(T param)
    { 
        IntPtr retval = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(param)); 
        System.Runtime.InteropServices.Marshal.StructureToPtr(param, retval, false); 
        return (retval); 
    }

//from swhistlesoft
    public static void IntPtrFree(IntPtr preAllocated) 
    { 
        if (IntPtr.Zero == preAllocated) throw (new Exception("Go Home")); 
        System.Runtime.InteropServices.Marshal.FreeHGlobal(preAllocated); 
        preAllocated = IntPtr.Zero; 
    }

    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
    struct COPYDATASTRUCT
    {
        public uint dwData;
        public int cbData;
        public IntPtr lpData;
    }

    /// <summary>
    /// Dot net version of AppInfo structure. Any changes to the structure needs reflecting here.
    /// struct must be a fixed size for marshalling to work, hence the SizeConst entries
    /// </summary>
    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
    struct AppInfoDotNet
    {
        public int   nVersion;            

        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 9)]
        public string test;
    };

To send a string:

    COPYDATASTRUCT cd = new COPYDATASTRUCT();
    cd.dwData = 2;

    cd.cbData = parameters.Length + 1;
    cd.lpData = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(parameters);

    IntPtr cdBuffer = IntPtrAlloc(cd);

    messageReceived = ((int)SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, cdBuffer)) != 0;

To receive string in c++:

else if(pCDS->dwData == 2)
    {
        //copydata message
        CString csMessage = (LPCTSTR)pCDS->lpData;
        OutputDebugString("Copydata message received: " + csMessage);
    }

To send struct:

            AppInfoDotNet appInfo = new AppInfoDotNet();
            appInfo.test = "a test";

            COPYDATASTRUCT cds3;
            cds3.dwData = 1;
            cds3.cbData = System.Runtime.InteropServices.Marshal.SizeOf(appInfo);

            IntPtr structPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(System.Runtime.InteropServices.Marshal.SizeOf(appInfo));
            System.Runtime.InteropServices.Marshal.StructureToPtr(appInfo, structPtr, false);

            cds3.lpData = structPtr;

            IntPtr iPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(System.Runtime.InteropServices.Marshal.SizeOf(cds3));
            System.Runtime.InteropServices.Marshal.StructureToPtr(cds3, iPtr, false);

            messageReceived = ((int)SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, iPtr)) != 0;

            System.Runtime.InteropServices.Marshal.FreeCoTaskMem(iPtr);
            System.Runtime.InteropServices.Marshal.FreeCoTaskMem(structPtr);

To receive struct in c++:

LRESULT CMainFrame::OnCopyData( WPARAM wParam, LPARAM lParam )
{
    LRESULT lResult = FALSE;

    COPYDATASTRUCT *pCDS = (COPYDATASTRUCT*)lParam;

    //Matching message type for struct
    if(pCDS->dwData == 1)
    {
        AppInfo *pAppInfo = (AppInfo*)pCDS->lpData
        lResult = true;
    }

Please note this is demo code and needs work in terms of styling, exception handling etc, etc...

like image 94
James_UK_DEV Avatar answered Sep 18 '22 04:09

James_UK_DEV