Конференция "WinAPI" » WM_COPYDATA. Не получается отправить данные
 
  • neversleep (20.11.11 12:21) [0]
    Хочу отправить командную строку программы в виде массива в другой экземпляр своего же приложения(т.е сделал запрет на запуск более одной копии):

    type
     PCmdArgs = ^TCmdArgs;
     TCmdArgs = array [0..$FF] of PWideChar;

    procedure SendArgs(argc: Integer; argv: PCmdArgs);
    var
     I, Offset: Integer;
     Buf: PWideChar;
     cds: TCopyDataStruct;
     hMem: THandle;
    begin
     if (argc <= 0) then
       Exit;

     Offset := SizeOf(PWideChar) * argc;
     cds.cbData := Offset;

     for I := 0 to argc - 1 do
       Inc(cds.cbData, (lstrlenW(argv[I]) + 1) * SizeOf(WideChar));

     hMem := GlobalAlloc(GHND, cds.cbData);
     if (hMem <> 0) then
     begin
       cds.dwData := 12345;
       //cds.cbData := argc;
       cds.lpData := GlobalLock(hMem);

       for I := 0 to argc - 1 do
       begin
         Buf := PWideChar(Integer(cds.lpData) + Offset);

         lstrcpyW(Buf, argv[I]);

         Inc(Offset, (lstrlenW(argv[I]) + 1) * SizeOf(WideChar));
         PCmdArgs(cds.lpData)^[I] := Buf;
       end;

       SendMessageW(Handle, WM_COPYDATA, 0, Integer(@cds));

       GlobalUnlock(hMem);
       GlobalFree(hMem);
     end;
    end;


    Сообщение приходит, но данные нет.... в отладке показывает что массив формируется нормально(при отправке), а получаю мусор. Если отправляю и получаю внутри одной и той же программы, то всё ок. В чём проблема то? %)

    Delphi7. Win7.
  • clickmaker © (20.11.11 12:42) [1]
    > В чём проблема то? %)

    возможно, в этом
    GlobalUnlock(hMem);
    GlobalFree(hMem);

    как принимающая сторона получает данные?
  • neversleep (20.11.11 12:48) [2]

    > как принимающая сторона получает данные?

    procedure OnCopyData(pcds: PCopyDataStruct);
    var
     Args: PCmdArgs;
    begin
     if (pcds^.dwData <> 12345) then
       Exit;

     Args := pcds^.lpData;
     ...
    end;

  • MBo © (20.11.11 13:24) [3]
    Очевидно, проблемы связаны с ошибками или тонкостями в выделении памяти.
    Может, не мудритьс GlobalXXX, а просто сформировать одну строку, в которой отдельные аргументы будут разделены нулями, и передать ее тело.
    Собственно, командная строка в таком виде уже, видимо, и была до разделения на кусочки...
  • DVM © (20.11.11 13:38) [4]

    > neversleep  

    Версия Windows какая?
  • clickmaker © (20.11.11 13:59) [5]
  • neversleep (20.11.11 14:44) [6]

    > Версия Windows какая?

    В конце первого поста.


    > Может, не мудритьс GlobalXXX, а просто сформировать одну
    > строку, в которой отдельные аргументы будут разделены нулями,
    >  и передать ее тело.

    На самом деле с массивом работать мне будет гораздо удобней.


    > http://msdn.microsoft.com/en-us/library/ms649011%28v=vs.
    > 85%29.aspx#5

    Дело не в этом, но спасибо, не знал про UIPI и про то что он может блокировать сообщения.
    ----

    До конца не понял в чём дело(видимо всё таки в моей криворукости), но как выяснилось данные приходили, но массив содержал не верные указатели, поэтому в отладчике я видел мусор... продолжаю разбираться...
  • neversleep (20.11.11 15:40) [7]
    Действительно, указатели на стороне приёма оказались не верными, поэтому пришлось делать вот такой костыль:

    Отправка:

    procedure SendArgs(argc: Integer; argv: PCmdArgs);
    var
    I, Offset: Integer;
    Buf: PWideChar;
    cds: TCopyDataStruct;
    hMem: THandle;
    begin
     if (argc <= 0) then
       Exit;

     // Резервируем место под кол-во элементов в массиве + под сами элементы.
     Offset := SizeOf(Integer) + SizeOf(PWideChar) * argc;
     cds.cbData := Offset;

     for I := 0 to argc - 1 do
       Inc(cds.cbData, (lstrlenW(argv[I]) + 1) * SizeOf(WideChar));

     hMem := GlobalAlloc(GHND, cds.cbData);
     if (hMem <> 0) then
     begin
       cds.dwData := 12345;
       cds.lpData := GlobalLock(hMem);

       PInteger(cds.lpData)^ := argc; // Заносим кол-во эл.

       for I := 0 to argc - 1 do
       begin
         Buf := PWideChar(Integer(cds.lpData) + Offset);

         lstrcpyW(Buf, argv[I]);
         PCmdArgs(Integer(cds.lpData) + SizeOf(Integer))^[I] := Buf;

         Inc(Offset, (lstrlenW(argv[I]) + 1) * SizeOf(WideChar));
       end;

       SendMessageW(Handle, WM_COPYDATA, 0, Integer(@cds));

       GlobalUnlock(hMem);
       GlobalFree(hMem);
     end;
    end;



    Приём:

    procedure OnCopyData(pcds: PCopyDataStruct);
    var
     argc, I, Offset: Integer;
     argv: PCmdArgs;
    begin
     if (pcds^.dwData <> 12345) then
       Exit;

     argc := PInteger(pcds^.lpData)^; // кол-во эл. в массиве
     argv := PCmdArgs(Integer(pcds^.lpData) + SizeOf(Integer));

     Offset := SizeOf(Integer) + SizeOf(PWideChar) * argc;

     for I := 0 to argc - 1 do // КОСТЫЛЬ, правим указатели
     begin
       argv^[I] := PWideChar(Integer(pcds^.lpData) + Offset);
       Inc(Offset, (lstrlenW(argv[I]) + 1) * SizeOf(WideChar));
     end;
    end;


    Не нравится мне это... Но всем спасибо за помощь и если есть какие-то идеи - велком :)
  • DVM © (20.11.11 22:18) [8]

    > Хочу отправить командную строку программы в виде массива
    > в другой экземпляр своего же приложения

    Вот можно еще так (код старый, поправить надо под новые Delphi):


    function MakeDrop(const FileNames: array of string): THandle;
    var
     I, Size: Integer;
     Data: PDragInfoA;
     P: PChar;
    begin
     Size := SizeOf(TDragInfoA) + 1;
     for I := 0 to High(FileNames) do
       Inc(Size, Length(FileNames[I]) + 1);
     Result := GlobalAlloc(GHND or GMEM_SHARE, Size);
     if Result <> 0 then
     begin
       Data := GlobalLock(Result);
       if Data <> nil then
         try
           Data.uSize := SizeOf(TDragInfoA);
           P  := PChar(@Data.grfKeyState) + 4;
           Data.lpFileList := P;
           for I := 0 to High(FileNames) do
           begin
             Size := Length(FileNames[I]);
             Move(Pointer(FileNames[I])^, P^, Size);
             Inc(P, Size + 1);
           end;
         finally
           GlobalUnlock(Result);
         end
       else
       begin
         GlobalFree(Result);
         Result := 0;
       end;
     end;
    end;

    Drop := MakeDrop([ParamStr(1)]);
    if Drop <> 0 then PostMessage(hMainWin, wm_DropFiles, Drop, 0);
    GlobalFree(Drop);

  • Dimka Maslov © (21.11.11 10:30) [9]
    Гораздо удобнее для передачи информации пользоваться атомами, а не мучаться с глобальной кучей.
  • Leonid Troyanovsky © (21.11.11 11:22) [10]

    > Dimka Maslov ©   (21.11.11 10:30) [9]

    > Гораздо удобнее для передачи информации пользоваться атомами

    Т.к. разницы между локальной и глобальной кучей нет, проще
    пользоваться дельфийским менеджером памяти, бо пересылаемое
    живо до выхода из обработчика WM_COPYDATA (обрабатываемого
    синхронно).

    Да и, во-ще, атомы для передачи информации не очень хороши, т.к.
    время их жизни ограничено сессией, а у того же mmf - процессом.

    --
    Regards, LVT.
  • neversleep (21.11.11 12:04) [11]
    ...В итоге забил на WM_COPYDATA, и использовал WM_DROPFILES :) Спасибо DVM за подсказку, так даже лучше, ибо PostMessage, да и на приёме удобно...

    Получилось примерно так:

    procedure DropArgs(hWnd: HWND; argc: Integer; argv: PCmdArgs);
    var
     pdf: PDropFiles;
     hMem: THandle;
     I, Offset, Len: Integer;
    begin
     if (argc <= 0) then
       Exit;

     Len := 0;
     for I := 0 to argc - 1 do
       Inc(Len, lstrlenW(argv[I]) + 1);

     Inc(Len, Len + SizeOf(WideChar) + SizeOf(TDropFiles));

     hMem := GlobalAlloc(GHND, Len);
     if (hMem <> 0) then
     begin
       pdf := GlobalLock(hMem);
       pdf^.pFiles := SizeOf(TDropFiles);
       pdf^.fWide := True;

       Offset := pdf^.pFiles;
       for I := 0 to argc - 1 do
       begin
         Len := lstrlenW(argv[I]) + 1;
         lstrcpynW(PWideChar(Integer(pdf) + Offset), argv[I], Len);
         Inc(Offset, Len * SizeOf(WideChar));
       end;

       GlobalUnlock(hMem);

       if (not PostMessageW(hWnd, WM_DROPFILES, hMem, 0)) then
         GlobalFree(hMem);
     end;
    end;

  • Leonid Troyanovsky © (21.11.11 16:27) [12]

    > DVM ©   (20.11.11 22:18) [8]

    > Вот можно еще так

    Это вовсе неочевидно, что можно.
    Особенно смущает

    if Drop <> 0 then PostMessage(hMainWin, wm_DropFiles, Drop, 0);
    GlobalFree(Drop);


    Оно должно быть так: WM_DROPFILES message is sent ..
    & An application should return zero if it processes this message

    Да и эти GHND or GMEM_SHARE мне не нравятся.

    --
    Regards, LVT.
  • Leonid Troyanovsky © (21.11.11 16:28) [13]

    > neversleep   (21.11.11 12:04) [11]

    > Получилось примерно так:

    Получилось некузяво, IMHO.
    Ты хоть сам понял, что понаписал?

    --
    Regards, LVT.
  • Leonid Troyanovsky © (21.11.11 16:36) [14]

    > neversleep   (21.11.11 12:04) [11]

    > ...В итоге забил на WM_COPYDATA

    Надо вернуться к WM_COPYDATA.
    Там всего-то надо одну строку передать: GetCommandLineW,
    (если уж unicode).

    --
    Regards, LVT.
  • neversleep (21.11.11 17:23) [15]

    > Ты хоть сам понял, что понаписал?

    После такого вопроса, я начал сомневаться :) Что не так?


    > Надо вернуться к WM_COPYDATA.
    > Там всего-то надо одну строку передать: GetCommandLineW,
    >
    > (если уж unicode).
    >

    Собственно argc и argv в параметрах DropArgs, это у меня есть результат CommandLineToArgvW + GetCommandLineW. Кажется я понял, надо передать GetCommandLineW, а на стороне приёма CommandLineToArgvW.
  • neversleep (21.11.11 17:26) [16]

    >  
    > Кажется я понял, надо передать GetCommandLineW, а на стороне
    > приёма CommandLineToArgvW.
    Хотел сказать отправлять...
  • Leonid Troyanovsky © (21.11.11 19:18) [17]

    > neversleep   (21.11.11 17:23) [15]

    > я понял, надо передать GetCommandLineW,

    Ну, да. Отправителю выделять память даже не потребуется.
    Размер пакета cbData это lstrlenW(GetCommandLineW).
    А принимающий парсит полученную строку PWChar(...lpData),
    которая будет валидна вплоть до выхода из обработчика WM_COPYDATA.

    Да, и обработчик это не procedure OnCopyData(pcds: PCopyDataStruct);
    а
    procedure TFormX.WMCOPYDATA(var msg: TMsg); // message WM_COPYDATA;

    --
    Regards, LVT.
  • neversleep (21.11.11 20:26) [18]

    > Размер пакета cbData это lstrlenW(GetCommandLineW).

    Спасибо, только cbData = (lstrlenW(GetCommandLineW) + 1) * SizeOf(WideChar).

    Финальный вариант:

    const
     IDCD_COMMANDLINE = 12345;

    procedure SendCmdLine(hWnd: HWND);
    var
     cds: TCopyDataStruct;
    begin
     cds.dwData := IDCD_COMMANDLINE;
     cds.lpData := GetCommandLineW;
     cds.cbData := (lstrlenW(cds.lpData) + 1) * SizeOf(WideChar);
     SendMessageW(hWnd, WM_COPYDATA, 0, Integer(@cds));
    end;

    procedure OnCopyData(pcds: PCopyDataStruct); // для примера :)
    var
     argc: Integer;
     argv: PCmdArgs;
    begin
     if (pcds^.dwData <> IDCD_COMMANDLINE) then
       Exit;

     argv := PCmdArgs(CommandLineToArgvW(pcds^.lpData, argc));


    Всё гениальное - просто! (c)
  • DVM © (21.11.11 23:51) [19]

    > Leonid Troyanovsky ©   (21.11.11 16:27) [12]


    > Это вовсе неочевидно, что можно.

    Код паршивый, конечно, но оно работает, я проверил. Если его подрихтовать, то по-моему вполне удобно получается - один обработчик и для перетаскиваемых на форму файлов и для файлов отправленных из второй копии.


    > Да и эти GHND or GMEM_SHARE мне не нравятся.

    пережитки Win3.1, вероятно его писали давно.


    > Оно должно быть так: WM_DROPFILES message is sent ..
    > & An application should return zero if it processes this
    > message

    Ну про то, что надо возвращать 0 написано у доброй половины оконных сообщений Windows и лишь некоторым это действительно нужно. Этому точно не требуется.
  • MBo © (22.11.11 05:29) [20]
    >Финальный вариант
    [3] ?
    ;)
  • Leonid Troyanovsky © (22.11.11 06:13) [21]

    > neversleep   (21.11.11 20:26) [18]

    > Спасибо, только cbData = (lstrlenW(GetCommandLineW) + 1)
    > * SizeOf(WideChar).

    Я упустил, sorry.

    > DVM ©   (21.11.11 23:51) [19]

    > Код паршивый, конечно, но оно работает, я проверил

    Он может и ничего, но как-то стремно после PostMessage
    освобождать память.
    Если для SendMessage у виндов встречается магия при передаче данных
    (как, например у того же WM_COPYDATA или WM_GETTEXT), то для PM
    подобного точно нет.

    --
    Regards, LVT.
  • neversleep (22.11.11 06:33) [22]

    > MBo
    > [3] ?
    > ;)

    Почти, но не совсем... мне в тот момент смекалки не хватило развить идею, голова забита была ;)


    > Он может и ничего, но как-то стремно после PostMessage
    > освобождать память.

    К слову, когда я писал код в [11], в случае если PostMessage успешно отработает, GlobalFree завершается неудачей с GetLastError = ERROR_INVALID_HANDLE, поэтому if (not PostMessage...). Хотя, мб в старых виндах оно по-другому себя вело.
  • Германн © (22.11.11 08:12) [23]

    > К слову, когда я писал код в [11], в случае если PostMessage
    > успешно отработает, GlobalFree завершается неудачей с GetLastError
    > = ERROR_INVALID_HANDLE, поэтому if (not PostMessage...)

    Шаманские пляски.
    А что значит "если PostMessage успешно отработает"?
  • Leonid Troyanovsky © (22.11.11 10:42) [24]

    > DVM ©   (21.11.11 23:51) [19]

    > Ну про то, что надо возвращать 0 написано у доброй половины
    > оконных сообщений Windows и лишь некоторым это действительно
    > нужно. Этому точно не требуется.

    Оно потребуется для определения успешности доставки.

    Т.к. WM_DROPFILES < WM_USER, то пересылыемые данные
    должны маршаллиться. Значит все GlobalAlloc/GlobalFree
    излишни и можно пользовать дельфийский менеджер памяти.
    Т.е., правильно заполняем _DROPFILES и делаем SendMessage.

    Принимающая сторона парсит путем DragQueryFile,
    делает DragFinish и возвращает Msg.Result := 0.

    Как-то так.

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

    Не тестировалось.
    Думаю, что те кто используют  подобную магию должны внимательно
    во всем этом разобраться ;)

    --
    Regards, LVT.
  • Leonid Troyanovsky © (22.11.11 10:45) [25]

    > Германн ©   (22.11.11 08:12) [23]

    > А что значит "если PostMessage успешно отработает"?

    msdn:
    Return value

    Type: BOOL

    If the function succeeds, the return value is nonzero.

    If the function fails, the return value is zero. To get extended error information, call GetLastError. GetLastError returns ERROR_NOT_ENOUGH_QUOTA when the limit is hit.

    Remarks

    When a message is blocked by UIPI the last error, retrieved with GetLastError, is set to 5 (access denied).

    --
    Regards, LVT.
 
Конференция "WinAPI" » WM_COPYDATA. Не получается отправить данные
Есть новые Нет новых   [134430   +4][b:0][p:0.004]