C# 到 C++ CopyData API

C# to 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", SetLastError = true, 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, 0, cds.lpData, buff.Length);
        cds.cbData = buff.Length;
        var ret = SendMessage(hwnd, WM_COPYDATA, 0, ref cds);
        Console.WriteLine("Return value is {0}", ret);
        Marshal.FreeHGlobal(cds.lpData);
        Console.ReadLine();
    }

}

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

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

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

您的代码中还有另一个潜在问题(我只在第二遍代码中看到的问题):C 字符串必须 [=15=] 终止(因此 "Foo" 必须 "Foo[= 17=]").您的 Encoding.ASCII 不保证 [=15=] 终止。经典的做法是使 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 [=10=] 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, 0, 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, 0, 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, WM_COPYDATA, 0, ref cds);
    }
    finally
    {
        if (h.IsAllocated)
        {
            h.Free();
        }
    }

    return ret;
}

请注意,您甚至可以使用 Default 编码代替 ASCII,这样会好一点。

如果你想接收消息,在你的Winforms中做:

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}, Y = {position.Y}, Z = {position.Z}, W = {position.W}");

                m.Result = (IntPtr)300;
            }
            else
            {
                m.Result = (IntPtr)0;
            }
        }

        return;
    }

    base.WndProc(ref m);
}

请注意,如果您同时控制发送方和接收方,则最好对字符串参数使用 Unicode。您必须同时修改发送方和接收方:Encoding.Unicode.GetByteCount/Encoding.Unicode.GetBytes、+2 而不是 +1 和 Marshal.PtrToStringUni.