在现代软件开发中,经常需要将现有的应用程序与新技术或框架集成。本文将介绍如何在一个现有的MFC文件查看器应用程序中集成.NETFramework的功能。特别是,将使用.NET的FileSystemWatcher类来监控文件系统的变动,并在新文件创建时自动显示。由于MFC不能直接访问这个功能,有两种选择:一是在非托管的Visual C++中实现自己的FileSystemWatcher类;二是在.NET和MFC之间提供某种托管机制或链接。
第一种选择虽然有趣,但耗时较长,因此选择了第二种方案。目标是.NET应用程序在检测到新文件时,能够通知MFC应用程序并自动显示。为了实现这一点,需要在运行的应用程序之间建立一个简单的进程间通信机制。选择了使用WM_COPYDATA消息来传递文件名给MFC应用程序。当MFC应用程序接收到COPYDATA消息时,它会获取文件路径并调用自己的显示函数。
在.NET应用程序中,首先需要获取目标进程的句柄。通过Init函数启动MFC应用程序作为子进程,并保存主窗口句柄,这是传输数据所需的。这种方法比传统的“查找进程”方法简单得多,它使用Win32函数FindWindow和EnumWindows。此外,它确保了目标应用程序的正确实例句柄。
private void Init(string processName, string cmdParams)
{
// 启动MFC应用程序作为.NET应用程序中的进程
Process mfcApp = new Process();
mfcApp.StartInfo.FileName = processName;
mfcApp.StartInfo.Arguments = cmdParams;
// 运行MFC应用程序
mfcApp.Start();
// 下面这行代码非常重要,确保获取
// MFC应用程序的主窗口句柄
if (mfcApp.WaitForInputIdle(5000))
{
destMainHandle = mfcApp.MainWindowHandle;
}
}
为了使用COPY_DATA消息机制向另一个进程发送数据,必须使用以下Win32结构和函数:
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;
下面的函数从.NET应用程序向MFC应用程序发送数据。由于MFC程序不是为Unicode兼容而构建的,因此在发送之前必须将字符串转换为ASCII格式。AStringToCoTaskMemAnsi将.NET程序中的Unicode字符串转换为MFC所需的ASCII格式。
接下来需要做的是分配COPYDATASTRUCT结构的内存。由于数据被发送到非托管世界,可以使用两种Marshal函数之一来分配非托管内存块,AllocCoTaskMem或AllocHGlobal。然后使用System.Runtime.InteropServices来执行SendMessage到MFC应用程序。
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);
// 发送到MFC应用程序
SendMessage(destHandle, WM_COPYDATA, IntPtr.Zero, iPtr);
// 不要忘记释放分配的内存
Marshal.FreeCoTaskMem(cds.lpData);
Marshal.FreeCoTaskMem(iPtr);
}
MFC应用程序在MainFrame类中创建一个OnCopyData消息处理程序。从COPYDATASRUCT块中轻松地将发送的文件路径字符串转换出来,并可以由MFC应用程序使用。MFC程序接收到来自C#应用程序的消息后,会向调用者发送确认消息。
// CMainFrame消息处理程序
BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT *pCopyDataStruct)
{
char szbuf[256];
::ZeroMemory(szbuf, 256);
// 从CopyData结构中获取文件路径
strncpy(szbuf, (char*)pCopyDataStruct->lpData, pCopyDataStruct->cbData);
CString filePath = CString(szbuf);
// 将文件导入MFC应用程序
CMFCTestProgramDoc* pDocument = (CMFCTestProgramDoc *) GetActiveView()->GetDocument();
pDocument->FileImportAutomatic(filePath);
// 向调用者发送确认消息
::SendMessage((HWND)pCopyDataStruct->dwData, WM_COPYDATA, 0, 0);
return TRUE;
}
为了从MFC应用程序接收确认消息,必须覆盖C#应用程序的WndProc处理程序。下面的代码展示了如何从消息变量中解码COPYDATASTRUCT结构,以及如何将ASCII字符串转换为UNICODE格式。
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);
}