0

我正在开发一个自动化接口程序,并希望通过使用针对 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();
    }

}

运行此代码对两者都返回 0hwnd并且ret机器软件没有反应。

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

4

1 回答 1

2

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

您的代码中还有另一个潜在问题(我仅在第二遍代码中看到的问题):必须终止 C 字符串\0(因此"Foo"必须终止"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, 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;
}

请注意,即使ASCII您可以使用Default编码,也可以更好一些。

如果您想接收消息,请在您的 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.

于 2020-12-29T19:39:52.723 回答