С# с использованием SendMessage, проблема с WM_COPYDATA

Я потратил несколько дней (или больше), пытаясь заставить это работать.

Под рукой находится приложение FTPRush, и я знаю, что есть приложение строки cmd под названием rush_cmdline.exe, которое использует SendMessage для отправки запросов FTPRush.

При отладке rush_cmdline.exe я вижу lParam, wParam, Message и hWnd.

Мой код выглядит следующим образом (используя SendMessage, а не SendMessageW):

[DllImport("User32.dll", EntryPoint = "FindWindow")]
public static extern Int32 FindWindow(String lpClassName, String lpWindowName);
[DllImport("USER32.DLL", EntryPoint= "SendMessage")]
public static extern IntPtr SendMessage(int hWnd, int Msg, int wParam, IntPtr lParam);

И я также попробовал другую спецификацию:

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

Ручка (hWnd) не проблема, так как это работает:

int ftprush = FindWindow("TfmRush", null);
ShowWindow(ftprush, 8);

Который (я не вставлял dllimport, так как он здесь не важен. Дайте мне знать, если хотите его увидеть) выводит окно на передний план. Также я проверил отладкой rush_cmdline.exe. Так что ручка такая же.

Две попытки, которые обе терпят неудачу (молча):

public const Int32 WM_COPYDATA = 0x4A;
string msg = "RushApp.FTP.Login('backup','',0); ";
// 1
byte[] array = Encoding.UTF8.GetBytes((string)msg);
int size = Marshal.SizeOf(array[0]) * array.Length + Marshal.SizeOf(array[0]);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(array, 0, ptr, array.Length);
Marshal.WriteByte(ptr, size - 1, 0);
SendMessage(ftprush, WM_COPYDATA, 0, ptr);

// 2
public struct COPYDATASTRUCT
{
   public IntPtr dwData;
   public int cbData;
   [MarshalAs(UnmanagedType.LPStr)]
   public string lpData;
}

COPYDATASTRUCT cds;
cds.dwData = (IntPtr)100;
cds.lpData = msg;
cds.cbData = sarr.Length + 1;
SendMessage(ftprush, WM_COPYDATA, 0, ref cds);

Я ожидаю, что по крайней мере второе решение сработает, так как оно очень хорошо сочетается с этим: perl пример

Любое просветление ОЧЕНЬ ценится!

Спасибо,

  • откровенный

ОБНОВИТЬ:

string msg = "RushApp.FTP.Login('backup','',0);\0";
var cds = new COPYDATASTRUCT
{
            dwData = new IntPtr(3),
            cbData = msg.Length + 1,
            lpData = msg
};
IntPtr ftprush = FindWindow("TfmRush", null);
SendMessage(ftprush, WM_COPYDATA, IntPtr.Zero, ref cds);

person Frank    schedule 21.07.2011    source источник
comment
Вы уверены, что FTP Rush поддерживает WM_COPYDATA? Что говорится в документации FTPRush об этом сообщении? Если FTPRush не обрабатывает WM_COPYDATA, он просто проигнорирует сообщение и ничего не сделает.   -  person shf301    schedule 21.07.2011
comment
@ sh301: он поддерживает WM_COPYDATA. Это тот, который используется в примере Perl, и тот, который используется rush_cmdline.exe.   -  person Frank    schedule 21.07.2011


Ответы (3)


Мои определения имеют

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, ref COPYDATASTRUCT lParam);

public struct COPYDATASTRUCT {
  public int cbData;
  public IntPtr dwData;
  [MarshalAs(UnmanagedType.LPStr)] public string lpData;
}

var cds = new Win32.COPYDATASTRUCT {
                                           dwData = new IntPtr(3),
                                           cbData = str.Length + 1,
                                           lpData = str
                                         };
Win32.SendMessage(ftprush, Win32.WM_COPYDATA, IntPtr.Zero, ref cds);

Конечно, убедитесь, что str завершается нулем "\0"

В качестве альтернативы определение, данное PInvoke.NET,

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, StringBuilder lParam);
//If you use '[Out] StringBuilder', initialize the string builder with proper length first.
person Bob Vale    schedule 21.07.2011
comment
Спасибо за ответ, Боб. Вы имеете в виду Win32.SendMessage(ftprush, Win32.WM_COPYDATA, IntPtr.Zero, ref cds); (ссылка)? Я обновил свой вопрос. Мне пришлось добавить ref, чтобы он скомпилировался. Или я что-то здесь упускаю? - person Frank; 21.07.2011
comment
Да, я не имел в виду реф, я сделал опечатку! - person Bob Vale; 21.07.2011
comment
Я не могу заставить его работать с FTPRush, но ваш подход соответствует тому, что я ожидал, поэтому я предполагаю, что это глупая ошибка с моей стороны. Ответ принят. У вас случайно нет примера SendMessage, который я могу попробовать? (какое приложение, какой lParam) - person Frank; 21.07.2011
comment
Я использовал его для связи между двумя моими собственными программами, одно из отличий в том, что я установил третий параметр в hwnd окна в исходном приложении. Я так понимаю, что использование альтернативы с stringbuilder не помогает - person Bob Vale; 21.07.2011
comment
Я посмотрю сегодня вечером на ftp rush и посмотрю, смогу ли я сделать пример - person Bob Vale; 21.07.2011
comment
Спасибо за терпеливость. Я попытался использовать перегрузку pinvoke.net для SendMessageW aswel (соответствует тому, что использует rush_cmdline.exe). - person Frank; 21.07.2011
comment
Я сам попробовал и не могу заставить его работать. Действительно ли работает Perl-скрипт, который вы использовали? Существуют проблемы с безопасностью и ограничениями WM_COPYDATA в UAC, которые могут мешать. - person Bob Vale; 26.07.2011
comment
разве cbData не должно быть (str.Length + 1) * sizeof(char)? - person Sebastian; 10.03.2017
comment
@SebastianGodelet Строка упорядочена как UnmanagedType.LPStr, которая представляет собой массив символов ANSI, заканчивающийся нулем. Это будет размер байта, а не 2 байта для Unicode. - person Bob Vale; 10.03.2017

Между двумя ответами выше я собрал рабочий пример. Класс Брайса Вагнера работает, поэтому я добавил метод использования SendMessageTimeout для отправки данных. это статический метод, поэтому вы просто вызываете его для отправки данных. На самом деле это не моя работа, просто склеивание и совместное использование.

    [StructLayout(LayoutKind.Sequential)]
    public struct CopyData: IDisposable {
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
        static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, ref CopyData target,
                                                SendMessageTimeoutFlags fuFlags, uint uTimeout, out UIntPtr lpdwResult);

        [Flags]
        enum SendMessageTimeoutFlags: uint {
            SMTO_NORMAL             = 0x0,
            SMTO_BLOCK              = 0x1,
            SMTO_ABORTIFHUNG        = 0x2,
            SMTO_NOTIMEOUTIFNOTHUNG = 0x8
        }
        const uint WM_COPYDATA = 0x4A;

        public IntPtr dwData;
        public int cbData;
        public IntPtr lpData;

        public void Dispose() {
            if (lpData != IntPtr.Zero) {
                Marshal.FreeCoTaskMem(lpData);
                lpData = IntPtr.Zero;
                cbData = 0;
            }
        }
        public string AsAnsiString {
            get { return Marshal.PtrToStringAnsi(lpData, cbData); }
        }
        public string AsUnicodeString {
            get { return Marshal.PtrToStringUni(lpData); }
        }
        public static CopyData CreateForString(int dwData, string value, bool Unicode = false) {
            var result = new CopyData();
            result.dwData = (IntPtr) dwData;
            result.lpData = Unicode ? Marshal.StringToCoTaskMemUni(value) : Marshal.StringToCoTaskMemAnsi(value);
            result.cbData = value.Length + 1;
            return result;
        }

        public static UIntPtr Send(IntPtr targetHandle, int dwData, string value, uint timeoutMs = 1000, bool Unicode = false) {
            var cds = CopyData.CreateForString(dwData, value, Unicode);
            UIntPtr result;
            SendMessageTimeout(targetHandle, WM_COPYDATA, IntPtr.Zero, ref cds, SendMessageTimeoutFlags.SMTO_NORMAL, timeoutMs, out result);
            cds.Dispose();
            return result;
        }
    }

Чтобы использовать его:

CopyData.Send(targetHandle, 1234, "This is a test");

Это использует тайм-аут по умолчанию 1 секунду.

person Wade Hatler    schedule 25.01.2015
comment
Большое спасибо за этот пример! Это сэкономило мне дни исследований! - person Andreas Reitberger; 10.06.2020

Порядок аргументов в COPYDATASTRUCT критически важен, и в ответе Боба Вейла они расположены в неправильном порядке. http://msdn.microsoft.com/en-us/library/windows/desktop/ms649010(v=vs.85).aspx Это должно быть в следующем порядке:

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

У меня тоже не работает MarshalAs(UnmanagedType.LPStr)] public string lpData. Я только заставил это работать, выполнив сортировку самостоятельно:

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

    /// <summary>
    /// Only dispose COPYDATASTRUCT if you were the one who allocated it
    /// </summary>
    public void Dispose()
    {
        if (lpData != IntPtr.Zero)
        {
            Marshal.FreeCoTaskMem(lpData);
            lpData = IntPtr.Zero;
            cbData = 0;
        }
    }
    public string AsAnsiString { get { return Marshal.PtrToStringAnsi(lpData, cbData); } }
    public string AsUnicodeString { get { return Marshal.PtrToStringUni(lpData); } }
    public static COPYDATASTRUCT CreateForString(int dwData, string value, bool Unicode = false)
    {
        var result = new COPYDATASTRUCT();
        result.dwData = (IntPtr)dwData;
        result.lpData = Unicode ? Marshal.StringToCoTaskMemUni(value) : Marshal.StringToCoTaskMemAnsi(value);
        result.cbData = value.Length + 1;
        return result;
    }
}
person Bryce Wagner    schedule 11.07.2014