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

ⅰ亾dé卋堺 提交于 2019-12-03 12:04:35

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

From the documentation:

The data being passed must not contain pointers or other references to objects not accessible to the application receiving the data.

So you need to pack your string into COPYDATASTRUCT.lpData. If you have a max length for each string then you can embed it in a fixed length structure

typedef struct tagMYDATA
{
   char  s1[80];
   char  s2[120];
} MYDATA;

If you have only one variable length string you can put the string at the end and use a header followed by string data

typedef struct tagMYDATA
{
   int value1;
   float value2;
   int stringLen;
} MYDATAHEADER;

MyCDS.cbData = sizeof(MYDATAHEADER)+(int)stringData.size();
MyCDS.lpData = new BYTE[MyCDS.cbData];
memcpy(MyCDS.lpData,&dataHeader,sizeof*(MYDATAHEADER);
StringCbCopyA (
    ((BYTE*)MyCDS.lpData)+sizeof*(MYDATAHEADER)
    ,stringData.size()
    ,stringData.c_str());

If you have multiple variable length strings you can still use a header and allocate more spaces for every strings plus a double null terminator, or serialize everything into one XML string.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!