C# 到 C++ CopyData API

问题描述

我正在开发一个自动化接口程序,我希望通过使用针对 C++ 的 COPYDATA API 的机器软件来增强功能。目标是通过我自己的软件控制和报告机器的状态。

方法使用了迄今为止我没有任何经验的指针和内存分配。

我查看了许多其他来源,例如 this,但目前没有运气。我尝试了以下代码来尝试在机器软件上运行程序。

class Program
{
    [DllImport("User32.dll",SetLastError = true,EntryPoint = "FindWindow")]
    public static extern IntPtr FindWindow(String lpClassName,String lpWindowName);

    [DllImport("User32.dll",EntryPoint = "SendMessage")]
    public static extern IntPtr SendMessage(IntPtr hWnd,int Msg,int wParam,ref copYDATASTRUCT lParam);

    [StructLayout(LayoutKind.Sequential)]
    public struct copYDATASTRUCT
    {
        public IntPtr dwData;    // Any value the sender chooses.  Perhaps its main window handle?
        public int cbData;       // The count of bytes in the message.
        public IntPtr lpData;    // The address of the message.
    }

    const int WM_copYDATA = 0x004A;
    const int EXTERNAL_CD_COMMAND_RUN_ASYNC = 0x8001;

    static void Main(string[] args)
    {
        Console.WriteLine("{0} bit process.",(IntPtr.Size == 4) ? "32" : "64");
        Console.Write("Press ENTER to run test.");
        Console.ReadLine();
        IntPtr hwnd = FindWindow(null,"InSpecAppFrame");
        Console.WriteLine("hwnd = {0:X}",hwnd.ToInt64());
        var cds = new copYDATASTRUCT();
        byte[] buff = Encoding.ASCII.GetBytes("C:\\Users\\Desktop\\copYDATATEST.iwp");
        cds.dwData = (IntPtr)EXTERNAL_CD_COMMAND_RUN_ASYNC;
        cds.lpData = Marshal.AllocHGlobal(buff.Length);
        Marshal.copy(buff,cds.lpData,buff.Length);
        cds.cbData = buff.Length;
        var ret = SendMessage(hwnd,WM_copYDATA,ref cds);
        Console.WriteLine("Return value is {0}",ret);
        Marshal.FreeHGlobal(cds.lpData);
        Console.ReadLine();
    }

}

运行此代码会为 hwndret 返回 0,并且机器软件没有反应。

发送命令是第一步,下一步是尝试获得响应,以便我可以监控机器状态等。

解决方法

作为 Alejandro 所写内容的旁注(我认为这是正确的),您可以稍微简化代码,删除数据的副本。您可以直接“固定”您的 byte[]。请务必记住“取消固定”它(因此,try/finally 块)

您的代码中存在另一个潜在问题(我仅在第二遍代码时看到的问题):C 字符串必须以 \0 终止(因此 "Foo" 必须为 {{1} })。您的 "Foo\0" 不保证 Encoding.ASCII 终止。这样做的经典方法是使 \0 比需​​要的“大一点”。我已经完成了必要的更改。

byte[]

请注意,您甚至可以使用 [DllImport("User32.dll",SetLastError = true)] public static extern IntPtr SendMessage(IntPtr hWnd,int Msg,int wParam,ref COPYDATASTRUCT lParam); [DllImport("user32.dll",SetLastError = true)] static extern IntPtr FindWindow(string lpClassName,string lpWindowName); [StructLayout(LayoutKind.Sequential)] public struct COPYDATASTRUCT { public IntPtr dwData; // Any value the sender chooses. Perhaps its main window handle? public int cbData; // The count of bytes in the message. public IntPtr lpData; // The address of the message. } [StructLayout(LayoutKind.Sequential)] public struct ExternalGetPositionType { public double X; public double Y; public double Z; public double W; } const int WM_COPYDATA = 0x004A; const int EXTERNAL_CD_COMMAND_RUN_ASYNC = 0x8001; const int EXTERNAL_CD_GET_POSITION_PCS = 0x8011; const int EXTERNAL_CD_GET_POSITION_MCS = 0x8012; static void Main(string[] args) { Console.WriteLine("{0} bit process.",(IntPtr.Size == 4) ? "32" : "64"); Console.Write("Press ENTER to run test."); Console.ReadLine(); IntPtr hwnd = FindWindow(null,"Form1"); Console.WriteLine("hwnd = {0:X}",hwnd.ToInt64()); if (hwnd == IntPtr.Zero) { throw new Exception("hwnd not found"); } IntPtr ret = RunAsync(hwnd,@"C:\Users\Desktop\COPYDATATEST.iwp"); Console.WriteLine($"Return value for EXTERNAL_CD_COMMAND_RUN_ASYNC is {ret}"); ret = GetPosition(hwnd,true,new ExternalGetPositionType { X = 1,Y = 2,Z = 3,W = 4 }); Console.WriteLine($"Return value for EXTERNAL_CD_GET_POSITION_PCS is {ret}"); ret = GetPosition(hwnd,false,new ExternalGetPositionType { X = 10,Y = 20,Z = 30,W = 40 }); Console.WriteLine($"Return value for EXTERNAL_CD_GET_POSITION_MCS is {ret}"); Console.ReadLine(); } public static IntPtr RunAsync(IntPtr hwnd,string str) { // We have to add a \0 terminator,so len + 1 / len + 2 for Unicode int len = Encoding.Default.GetByteCount(str); var buff = new byte[len + 1]; // len + 2 for Unicode Encoding.Default.GetBytes(str,str.Length,buff,0); IntPtr ret; GCHandle h = default(GCHandle); try { h = GCHandle.Alloc(buff,GCHandleType.Pinned); var cds = new COPYDATASTRUCT(); cds.dwData = (IntPtr)EXTERNAL_CD_COMMAND_RUN_ASYNC; cds.lpData = h.AddrOfPinnedObject(); cds.cbData = buff.Length; ret = SendMessage(hwnd,WM_COPYDATA,ref cds); } finally { if (h.IsAllocated) { h.Free(); } } return ret; } public static IntPtr GetPosition(IntPtr hwnd,bool pcs,ExternalGetPositionType position) { // We cheat here... It is much easier to pin an array than to copy around a struct var positions = new[] { position }; IntPtr ret; GCHandle h = default(GCHandle); try { h = GCHandle.Alloc(positions,GCHandleType.Pinned); var cds = new COPYDATASTRUCT(); cds.dwData = pcs ? (IntPtr)EXTERNAL_CD_GET_POSITION_PCS : (IntPtr)EXTERNAL_CD_GET_POSITION_MCS; cds.lpData = h.AddrOfPinnedObject(); cds.cbData = Marshal.SizeOf<ExternalGetPositionType>(); ret = SendMessage(hwnd,ref cds); } finally { if (h.IsAllocated) { h.Free(); } } return ret; } 编码代替 ASCII,这样会更好一些。

如果您想接收消息,请在您的 Winforms 中执行:

Default

请注意,如果您同时控制发送方和接收方,最好将 Unicode 用于字符串参数。您必须同时修改发送方和接收方:protected override void WndProc(ref Message m) { if (m.Msg == WM_COPYDATA) { COPYDATASTRUCT cds = Marshal.PtrToStructure<COPYDATASTRUCT>(m.LParam); if (cds.dwData == (IntPtr)EXTERNAL_CD_COMMAND_RUN_ASYNC) { string str = Marshal.PtrToStringAnsi(cds.lpData); Debug.WriteLine($"EXTERNAL_CD_COMMAND_RUN_ASYNC: {str}"); m.Result = (IntPtr)100; // If you want to return a value } else if (cds.dwData == (IntPtr)EXTERNAL_CD_GET_POSITION_PCS) { if (cds.cbData >= Marshal.SizeOf<ExternalGetPositionType>()) { var position = Marshal.PtrToStructure<ExternalGetPositionType>(cds.lpData); Debug.WriteLine($"EXTERNAL_CD_GET_POSITION_PCS: X = {position.X},Y = {position.Y},Z = {position.Z},W = {position.W}"); m.Result = (IntPtr)200; } else { m.Result = (IntPtr)0; } } else if (cds.dwData == (IntPtr)EXTERNAL_CD_GET_POSITION_MCS) { if (cds.cbData >= Marshal.SizeOf<ExternalGetPositionType>()) { var position = Marshal.PtrToStructure<ExternalGetPositionType>(cds.lpData); Debug.WriteLine($"EXTERNAL_CD_GET_POSITION_MCS: X = {position.X},W = {position.W}"); m.Result = (IntPtr)300; } else { m.Result = (IntPtr)0; } } return; } base.WndProc(ref m); } /Encoding.Unicode.GetByteCount,+2 而不是 +1 和 Encoding.Unicode.GetBytes