Маршалинг байтового массива для PostMessage - PullRequest
4 голосов
/ 31 мая 2011

Я пытаюсь перенести некоторый код C ++ в C #, и одна из вещей, которые мне нужно сделать, это использовать PostMessage для передачи байтового массива в окно другого процесса. Я пытаюсь получить исходный код для другой программы, чтобы я мог точно увидеть, чего он ожидает, но пока что вот как выглядит оригинальный код C ++:

unsigned long result[5] = {0};
//Put some data in the array
unsigned int res = result[0];
Text winName = "window name";
HWND hWnd = FindWindow(winName.getConstPtr(), NULL);
BOOL result = PostMessage(hWnd, WM_COMMAND, 10, res);

А вот что у меня сейчас:

[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 int SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);

[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
    public int dwData;
    public int cbData;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]
    public byte[] lpData;
}

public const int WM_COPYDATA = 0x4A;

public static int sendWindowsByteMessage(IntPtr hWnd, int wParam, byte[] data)
{
    int result = 0;

    if (hWnd != IntPtr.Zero)
    {
        int len = data.Length;
        COPYDATASTRUCT cds;
        cds.dwData = wParam;
        cds.lpData = data;
        cds.cbData = len;
        result = SendMessage(hWnd, WM_COPYDATA, wParam, ref cds);
    }

    return result;
}

//*****//

IntPtr hWnd = MessageHelper.FindWindow(null, windowName);
if (hWnd != IntPtr.Zero)
{
    int result = MessageHelper.sendWindowsByteMessage(hWnd, wParam, lParam);
    if (result == 0)
    {
        int errCode = Marshal.GetLastWin32Error();
    }
}

Обратите внимание, что мне пришлось переключиться с использования PostMessage в C ++ на SendMessage в C #.

Итак, что происходит сейчас, так это то, что я получаю и результат, и errCode равным 0, что, как я считаю, означает, что сообщение не было обработано - и действительно, глядя на другое приложение, я не вижу ожидаемого ответа. Я подтвердил, что hWnd != IntPtr.Zero, поэтому я думаю, что сообщение отправляется в правильное окно, но данные сообщения неверны. Есть идеи, что я делаю не так?

Обновление

Мне все еще не повезло, попробовав предложения в комментариях. Вот что у меня сейчас есть:

[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
    public IntPtr dwData;
    public int cbData;
    public IntPtr lpData;
}

public struct BYTEARRDATA
{
    public byte[] data;
}

public static IntPtr IntPtrAlloc<T>(T param)
{
    IntPtr retval = Marshal.AllocHGlobal(Marshal.SizeOf(param));
    Marshal.StructureToPtr(param, retval, false);
    return (retval);
}

public static void IntPtrFree(IntPtr preAllocated)
{
    //Ignores errors if preAllocated is IntPtr.Zero!
    if (IntPtr.Zero != preAllocated)
    {
        Marshal.FreeHGlobal(preAllocated); 
        preAllocated = IntPtr.Zero;
    }
}

BYTEARRDATA d;
d.data = data;
IntPtr buffer = IntPtrAlloc(d);

COPYDATASTRUCT cds;
cds.dwData = new IntPtr(wParam);
cds.lpData = buffer;
cds.cbData = Marshal.SizeOf(d);

IntPtr copyDataBuff = IntPtrAlloc(cds);
IntPtr r = SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, copyDataBuff);
if (r != IntPtr.Zero)
{
    result = r.ToInt32();
}

IntPtrFree(copyDataBuff);
copyDataBuff = IntPtr.Zero;
IntPtrFree(buffer);
buffer = IntPtr.Zero;

Это 64-битный процесс, пытающийся связаться с 32-битным процессом, так что может быть что-то есть, но я не уверен, что.

Ответы [ 4 ]

7 голосов
/ 31 мая 2011

Проблема в том, что COPYDATASTRUCT должен содержать указатель в качестве последнего члена, и вы передаете весь массив.

Посмотрите на пример на pinvoke.net: http://www.pinvoke.net/default.aspx/Structures/COPYDATASTRUCT.html

Больше информации после комментариев:

С учетом этих определений:

const int WM_COPYDATA = 0x004A;

[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
    public IntPtr dwData;
    public int cbData;
    public IntPtr lpData;
}
[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);

Я могу создать две .NET программы для проверки WM_COPYDATA. Вот процедура окна для получателя:

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case WM_COPYDATA:
            label3.Text = "WM_COPYDATA received!";
            COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT)); 
            byte[] buff = new byte[cds.cbData];
            Marshal.Copy(cds.lpData, buff, 0, cds.cbData);
            string msg = Encoding.ASCII.GetString(buff, 0, cds.cbData);
            label4.Text = msg;
            m.Result = (IntPtr)1234;
            return;
    }
    base.WndProc(ref m);
}

И код, который вызывает его, используя SendMessage:

Console.WriteLine("{0} bit process.", (IntPtr.Size == 4) ? "32" : "64");
Console.Write("Press ENTER to run test.");
Console.ReadLine();
IntPtr hwnd = FindWindow(null, "JimsForm");
Console.WriteLine("hwnd = {0:X}", hwnd.ToInt64());
var cds = new COPYDATASTRUCT();
byte[] buff = Encoding.ASCII.GetBytes(TestMessage);
cds.dwData = (IntPtr)42;
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);

Это работает, как ожидается, когда отправитель и получатель являются 32-битными процессами и когда они 64-битные процессы. Не будет работать , если "битность" двух процессов не совпадает.

Есть несколько причин, по которым это не сработает для 32/64 или 64/32. Представьте, что ваша 64-битная программа хочет отправить это сообщение 32-битной программе. Значение lParam, передаваемое 64-битной программой, будет иметь длину 8 байт. Но 32-битная программа видит только 4 байта. Так что эта программа не будет знать, откуда взять данные!

Даже если это сработало, размер структуры COPYDATASTRUCT будет другим. В 32-битных программах он содержит два указателя и DWORD для общего размера 12 байтов. В 64-битных программах COPYDATASTRUCT имеет длину 20 байт: два указателя по 8 байт каждая и значение длины 4 байта.

У вас есть похожие проблемы, идущие в другую сторону.

Я серьезно сомневаюсь, что вы получите WM_COPYDATA для работы на 32/64 или на 64/32.

2 голосов
/ 24 октября 2011

Это будет работать с 32-битным отправителем на 64-битный получатель, 64-битным отправителем на 32-битный получатель.Также работайте с 32 до 32 и с 64 до 64. Вам даже не нужно объявлять COPYDATASTRUCT.Очень просто:

    const int WM_COPYDATA = 0x004A;

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(String lpClassName, String lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

    static IntPtr SendMessage(IntPtr hWnd, byte[] array, int startIndex, int length)
    {
        IntPtr ptr = Marshal.AllocHGlobal(IntPtr.Size * 3 + length);
        Marshal.WriteIntPtr(ptr, 0, IntPtr.Zero);
        Marshal.WriteIntPtr(ptr, IntPtr.Size, (IntPtr)length);
        IntPtr dataPtr = new IntPtr(ptr.ToInt64() + IntPtr.Size * 3);
        Marshal.WriteIntPtr(ptr, IntPtr.Size * 2, dataPtr);
        Marshal.Copy(array, startIndex, dataPtr, length);
        IntPtr result = SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ptr);
        Marshal.FreeHGlobal(ptr);
        return result;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        IntPtr hWnd = FindWindow(null, "Target Window Tittle");
        byte[] data = System.Text.Encoding.ASCII.GetBytes("this is the sample text");
        SendMessage(hWnd, data, 0, data.Length);
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_COPYDATA:
                byte[] b = new Byte[Marshal.ReadInt32(m.LParam, IntPtr.Size)];
                IntPtr dataPtr = Marshal.ReadIntPtr(m.LParam, IntPtr.Size * 2);
                Marshal.Copy(dataPtr, b, 0, b.Length);
                string str = System.Text.Encoding.ASCII.GetString(b);
                MessageBox.Show(str);
                // m.Result = put result here;
                return;
        }
        base.WndProc(ref m);
    }
0 голосов
/ 01 июня 2011

В вашей функции IntPtrAlloc, что дает SizeOf(param)? Я думаю, что это будет размер ссылки на массив, а не размер содержимого массива. И поэтому Windows скопирует ссылку на массив .NET в другой процесс, что совершенно бессмысленно.

Прикрепите массив и используйте Marshal.UnsafeAddrOfPinnedArrayElement, чтобы получить правильное значение lpData.

0 голосов
/ 31 мая 2011

Может ли это быть 32-битной или 64-битной проблемой?

Попробуйте установить член dwData в COPYDATASTRUCT вместо Int.1006 *http://www.vistax64.com/net-general/156538-apparent-marshalling-related-problem-x64-but-works-x86.html

См. Оригинальное определение COPYDATASTRUCT:

http://msdn.microsoft.com/en-us/library/ms649010(VS.85).aspx

Вот значение ULONG_PTR для x64:

http://msdn.microsoft.com/en-us/library/aa384255(VS.85).aspx

Чтобы сохранить значение 64-битного указателя, используйте ULONG_PTR.Значение ULONG_PTR составляет 32 бита при компиляции 32-битным компилятором и 64 бита при компиляции 64-битным компилятором.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...