C# to C++ CopyData API

笑着哭i 提交于 2021-01-07 02:38:07

问题


I am developing an automation interface program and I looking to enhance capability with a machine software which uses a COPYDATA API aimed at C++. The goal is to control and report status of the machine through my own software.

The method uses pointers and memory allocation which I have not had any experience with thus far.

I have looked at a number of other sources, such as this with no luck at the moment. I have tried the following code to try and run a program on the machine software.

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();
    }

}

Running this code returns 0 for both hwnd and ret and the machine software does not react.

Sending a command is the first step, the next will be to try and get a response so I can monitor machine statuses etc.


回答1:


As a sidenote to what Alejandro wrote (and that I think is correct), you can simplify a little the code, removing a copy of the data. You can directly "pin" your byte[]. It is important that you remember to "unpin" it (for this reason the try/finally block)

There is another potential problem in your code (a problem that I saw only on a second pass of the code): C strings must be \0 terminated (so "Foo" must be "Foo\0"). Your Encoding.ASCII doesn't guarantee a \0 termination. The classical way to do it is to make the byte[] "a little larger" than necessary. I've done the changes necessary.

[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;
}

Note even that instead of ASCII you can use the Default encoding, that is a little better.

If you want to receive the messages, in your Winforms do:

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);
}

Note that if you control both the sender AND the receiver, it is better much better to use Unicode for the string parameter. You'll have to modify both the sender and the receiver: Encoding.Unicode.GetByteCount/Encoding.Unicode.GetBytes, the +2 instead of +1 and Marshal.PtrToStringUni.



来源:https://stackoverflow.com/questions/65497215/c-sharp-to-c-copydata-api

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!