进程间传递数据的方法

在进程间传递数据也就意味着两个不同的应用程序之间的通讯,大家可能会想到使用消息队列(Message Queue)来作为解决方案,当然这可能是最优解,然而这里我要讲的是另外一种方法,通过Windows的消息机制来传递数据,内容比较硬核。

依旧用到了两个Windows的API,FindWindowSendMessage,以及WPF如何和MFC窗口通讯,可以参考上一篇文章

传递数据的方式

这里需要先知道一个Window消息WM_COPYDATA 它在WinUser.h中的定义如下

1
#define WM_COPYDATA 0x004A

这个消息就是这次要讲的内容,通过这个消息就可以在不同的窗口间传递数据了,它有两个参数,WPARAM是发送消息窗口的句柄,LPARAM是一个结构体的指针,这个结构体在WinUser.h里定义如下:

1
2
3
4
5
6
7
8
/*
 * lParam of WM_COPYDATA message points to...
 */
typedef struct tagCOPYDATASTRUCT {
    ULONG_PTR dwData;
    DWORD cbData;
    _Field_size_bytes_(cbData) PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;

嗯…看起来也不是很复杂,为了能够正确处理这个指针,需要在C#中也定义一个相同的结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/// <summary>
/// COPYDATASTRUCT
/// 对应 C++ 里的 COPYDATASTRUCT
/// 不能更改
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
    public IntPtr dwData; //可以是任意值
    public int cbData;    //指定lpData内存区域的字节数
    public IntPtr lpData; //发送给目录窗口所在进程的数据
}

这样就可以了,其中最重要的就是lpData,它可以是任意对象的指针,只要C++和C#两边定义了一个相同的结构体,我们就可以用它来直接传递结构体对象😮,这种方式比通过消息队列传递数据要快得多。

那么先来定义一个简单的结构体吧:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// C++
struct StructUser
{
	char UserName[32];
	char Password[32];

	StructUser()
	{
		ZeroMemory(UserName, 32);
		ZeroMemory(Password, 32);
	}
};
1
2
3
4
5
6
7
8
9
// C#
[StructLayout(LayoutKind.Sequential)]
public struct StructUser
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string UserName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string Password;
}

这里定义了一个User的结构体,UserNamePassword都是长度为32的字符串,注意C#中结构体的定义方式,这里指定了字符串的长度,就是为了和C++的结构体保持一致。

先来看看C++中如何发送和处理WM_COPYDATA消息的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 发送
StructUser user;
char userName[32];
char password[32];
// GetDlgItem(IDC_EDIT_USERNAME)->GetWindowTextA(userName, 32);
// GetDlgItem(IDC_EDIT_PASSWORD)->GetWindowTextA(password, 32);
memcpy(user.UserName, userName, 32);
memcpy(user.Password, password, 32);

COPYDATASTRUCT copyData;
copyData.dwData = 0;
copyData.lpData = &user;
copyData.cbData = sizeof(StructUser);

HWND hWnd = nullptr;
if (m_pTargerWnd == nullptr)
{
	hWnd = ::FindWindow(nullptr, "WpfWindow");
}
else
{
	hWnd = m_pTargerWnd->GetSafeHwnd();
}
::SendMessage(hWnd, WM_COPYDATA, (WPARAM)GetSafeHwnd(), (LPARAM)& copyData);

// 接收
/* C++ 中相关代码
 * 处理 WM_COPYDATA 消息
 * Header File(.h)
---------------------------------------------------------------------
...
afx_msg BOOL OnCopyData(CWnd *pWnd, COPYDATASTRUCT *pCopyDataStruct);
...
DECLARE_MESSAGE_MAP()
---------------------------------------------------------------------
* Source File(.cpp)
BEGIN_MESSAGE_MAP(CxxxDlg, CDialogEx)
    ...
    ON_WM_COPYDATA()
    ...
    END_MESSAGE_MAP()
...
 */

BOOL CxxDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
	if (pWnd != nullptr)
	{
		m_pTargerWnd = pWnd;
	}
	if (pCopyDataStruct != nullptr)
	{
		StructUser* pUser = (StructUser*)(pCopyDataStruct->lpData);
		// DWORD dwLen = pCopyDataStruct->cbData;

		// GetDlgItem(IDC_EDIT_USERNAME)->SetWindowTextA(pUser->UserName);
		// GetDlgItem(IDC_EDIT_PASSWORD)->SetWindowTextA(pUser->Password);
	}

	return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

C#会比较复杂一些

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 消息处理函数
private IntPtr WndProcFunc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case WM_COPYDATA:  // public const int WM_COPYDATA = 0x004A;
            IntPrt hWnd_Target = wParam;
            COPYDATASTRUCT param = Marshal.PtrToStructure<COPYDATASTRUCT>(lParam);
            StructUser user = Marshal.PtrToStructure<StructUser>(param.lpData);
            // UserName.Text = user.UserName;
            // Password.Text = user.Password;
            break;
        default:
            break;
    }

    return IntPtr.Zero;
}

// 发送消息
StructUser sctUser = new StructUser()
{
    UserName = UserName.Text,
    Password = Password.Text
};

IntPtr userPtr = Marshal.AllocHGlobal(Marshal.SizeOf<StructUser>());
Marshal.StructureToPtr<StructUser>(sctUser, userPtr, true);

COPYDATASTRUCT copyData = new COPYDATASTRUCT()
{
    dwData = IntPtr.Zero,
    cbData = Marshal.SizeOf<StructUser>(),
    lpData = userPtr,
};

IntPtr copyDataPtr = Marshal.AllocHGlobal(Marshal.SizeOf<COPYDATASTRUCT>());
Marshal.StructureToPtr<COPYDATASTRUCT>(copyData, copyDataPtr, true);

if (hWnd_Target == IntPtr.Zero)
    hWnd_Target = Win32Api.FindWindow(null, "MfcWindow");

hWnd_MainWnd = new WindowInteropHelper(this).Handle;

if (hWnd_Target != IntPtr.Zero)
    Win32Api.SendMessage(hWnd_Target, WM_COPYDATA, hWnd_MainWnd, copyDataPtr);

Marshal.FreeHGlobal(userPtr);     //
Marshal.FreeHGlobal(copyDataPtr); // 最后一定要释放掉非托管内存

处理消息的地方和上一篇一样,发送的地方比较复杂,总体来讲就是用Marshal开辟了两块非托管的内存来存放两个结构体的数据,等到消息被处理之后再将非托管内存释放掉,避免内存泄漏。

那么,这次的内容就到这里了,主要内容都是代码,但其实也不复杂,只是需要慢慢消化。