Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Interprocess Communication Between .NET and MFC Using WM_COPYDATA

4.58/5 (6 votes)
23 Dec 2007CPOL4 min read 1   2.5K  
A simple method to achieve interprocess communication between the .NET framework and MFC.

Screenshot - DownloadSource

Introduction

I was recently assigned a task where I wanted to use some of the functionality built into the Microsoft .NET Framework with an existing MFC file viewer application. More specifically, I wanted to utilize the FileSystemWatcher class to notify the MFC Viewer app when a new file was created so it could display it automatically. Since MFC could not directly access this service, I had two options:

  1. Implement my own FileSystemWatcher class in unmanaged Visual C++.
  2. Provide some kind of hosting mechanism or linkage between .NET and MFC.

The first option would have been an interesting feature to create, but it would have taken time that I did not have. The idea of hosting the MFC viewer had the advantage that I could implement the file detection in .NET and call the MFC application from within the hosting mechanism. There is a very good article on MFC hosting, written by Alexey Shalnov, see: Hosting of MFC MDI Applications from Within WinForms. This solution, however, would require a lot of recoding of the MFC application.

My hosting solution only required that the .NET application send the MFC program the file path of the new program when a new file appeared. Thus, I needed a simple interprocess communication mechanism between the running applications. I, therefore, used the WM_COPYDATA to send the file name to the MFC application. When the MFC application received the COPYDATA message, it took the file path and called its own display function.

The .NET Application

Obtaining a Handle to the Target Process

The Init function starts the MFC application as a child process, and saves the main window handle which is needed to transmit the data. For my purposes, this method is much simpler than the traditional way of “finding the process” by using the Win32 functions FindWindow and EnumWindows. In addition, it guarantees the handle of the correct instance of the target application.

C#
private void Init(string processName, string cmdParams)
{
    // start the MFC application as a process in the .NET app
    Process mfcApp = new Process();

    mfcApp.StartInfo.FileName = processName;
    mfcApp.StartInfo.Arguments = cmdParams;

    // run the MFC app
    mfcApp.Start();

    // the line below is very important to ensure getting 
    // the main window handle of the MFC application
    if (mfcApp.WaitForInputIdle(5000))
    {
        destMainHandle = mfcApp.MainWindowHandle;
    }
}

In order to utilize the COPY_DATA messaging mechanism to send out data to another process, the following Win32 structure and function must be used:

C#
public struct COPYDATASTRUCT
{
    public IntPtr dwData;
    public int cbData;
    public IntPtr lpData;
}

[System.Runtime.InteropServices.DllImport("user32.dll",
CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hwnd, int msg,
IntPtr wparam, IntPtr lparam);

const int WM_COPYDATA = 0x4A;

Sending Data to the Target Process

The function below sends data from the .NET application to the MFC application. Because the MFC program was not built to be Unicode compatible, the string must be converted to ASCII format before it is to be sent. AStringToCoTaskMemAnsi converts the Unicode string in the .NET program into the ASCII format needed by MFC.

The next thing that has to be done is to allocate memory from the COPYDATASTRUCT structure. Since data is being sent out to the unmanaged world, one of two Marshal functions can be used to allocate the unmanaged memory block, AllocCoTaskMem or AllocHGlobal. System.Runtime.InteropServices is then used to perform a SendMessage to the MFC application.

The function SendMessageWithData allocates unmanaged memory which contains the data to be sent to the MFC program. The string to be sent must be converted into ASCII format.

The allocation of memory and the string conversion can be done in one of two ways:

  • The first method uses the Marshal functions AllocCoTaskMem and StringToCoTaskMemAnsi to perform the memory allocation and string conversion.
  • The second method uses the functions AllocHGlobal and StringToHGlobalAnsi to accomplish the same tasks.

The difference between these two sets of functions is that AllocCoTaskMem is essentially a wrapper for the COM function CoTaskMem, and allocates memory using the COM Task memory allocator. CoTaskMemAlloc calls the COM API IMalloc::Alloc. According to Microsoft, this function allocates memory in essentially the same way as the C function Alloc, whereas AllocHGlobal exposes the Win32 LocalAlloc (which is the same as GlobalAlloc in Win32) API, which is found in kernel32.dll, for the memory allocation.

C#
public static void SendMessageWithData(IntPtr destHandle, 
              string str, IntPtr srcHandle)
{
    COPYDATASTRUCT cds;

    cds.dwData = srcHandle;
    str = str + '\0'; 

    cds.cbData  = str.Length + 1;
    cds.lpData  = Marshal.AllocCoTaskMem(str.Length);
    cds.lpData  = Marshal.StringToCoTaskMemAnsi(str);
    IntPtr iPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf( cds));
    Marshal.StructureToPtr(cds,iPtr, true);

    // send to the MFC app
    SendMessage(destHandle, WM_COPYDATA, IntPtr.Zero, iPtr);

    // Don't forget to free the allocated memory 
    Marshal.FreeCoTaskMem(cds.lpData);
    Marshal.FreeCoTaskMem(iPtr);
}

public static void SendMessageWithDataUsingHGlobal(IntPtr destHandle, 
                   string str, IntPtr srcHandle)
{
    COPYDATASTRUCT cds;

    cds.dwData = srcHandle;
    str = str + '\0';

    cds.cbData = str.Length + 1;
    cds.lpData = Marshal.AllocHGlobal(str.Length);
    cds.lpData = Marshal.StringToHGlobalAnsi(str);
    IntPtr iPtr = Marshal.AllocHGlobal(Marshal.SizeOf(cds));
    Marshal.StructureToPtr(cds, iPtr, true);

    // send to the MFC app
    SendMessage(destHandle, WM_COPYDATA, IntPtr.Zero, iPtr);

    // Don't forget to free the allocated memory 
    Marshal.FreeHGlobal(cds.lpData);
    Marshal.FreeHGlobal(iPtr);
}

The MFC Application

On the MFC side of things, an OnCopyData message handler has to be created in the MainFrame class. (Note that the destination handle to be used in the SendMessage call in the C# program is the MainWindow handle of the MFC program’s mainframe window.)

The file path string that was sent from the managed .NET world is then easily translated from the COPYDATASRUCT block, and can be used by the MFC application. After the MFC program receives a message from the C# application, it sends an acknowledgement back to the caller.

See below:

C++
// CMainFrame message handlers
BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT *pCopyDataStruct)
{     
    char szbuf[256];

    ::ZeroMemory(szbuf,256);

    // get the file path from the CopyData structure
    strncpy(szbuf,(char *)pCopyDataStruct->lpData,pCopyDataStruct->cbData);
    CString filePath = CString(szbuf);

    // import the file into the MFC application
    CMFCTestProgramDoc* pDocument = 
             (CMFCTestProgramDoc *) GetActiveView()->GetDocument(); 
    pDocument->FileImportAutomatic(filePath);

    // send ack message back to the caller
    ::SendMessage((HWND)pCopyDataStruct->dwData,WM_COPYDATA,0,0);

    return TRUE;
}

Receiving the Acknowledgement

To receive the acknowledgement message from the MFC application, the WndProc handler of the C# application must be overridden. The code below shows how to decode the COPYDATASTRUCT structure from the message variable, and how to convert the ASCII string into UNICODE format.

C#
protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case WM_COPYDATA:
            if (m.Msg == WM_COPYDATA)
            {
                COPYDATASTRUCT cds = new COPYDATASTRUCT();
                cds = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam,
                 typeof(COPYDATASTRUCT));
                if (cds.cbData > 0)
                {
                    byte[] data = new byte[cds.cbData];
                    Marshal.Copy(cds.lpData, data, 0, cds.cbData);
                    Encoding unicodeStr = Encoding.ASCII;
                    char[] myString = unicodeStr.GetChars(data);
                    string returnText = new string(myString);
                    MessageBox.Show("ACK Received: " + returnText);


                    m.Result = (IntPtr)1;

                }
            } 
            break;
    }  
    base.WndProc(ref m);
}

Points of Interest

For further investigation into the subject of interprocess communication using WM_COPY, please refer to the following links:

History

  • December 24, completed version 1.0.0.0.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)