Конференция "Начинающим" » Послать строку в сообщении [D2005, XP]
 
  • Илья_666 (04.11.18 17:29) [0]
    Возник вопрос с типом String и SendMessage.
    Итак, есть самописный компонент (за моим авторством), имеющий некоторое поле типа String. Я хочу сделать следующее: послав компоненту сообщение, установить ему значение этого поля.
    Сообщение кастомное (WM_USER + 1000).
    Посылаю сообщение так:


    var
     S: String;
    begin
     SendMessage(MyComp.Handle, WM_SETTEXTFIELD, WParam(@S), 0);
    end;



    В самом компоненте "приемник" выглядит так:


    FLabelField := String(Message.WParam)



    Под отладчиком видно, что поле FLabelField компонента принимает значение "Inaccessible value", и после закрытия программа зависает и "падает".

    Я также планировал сделать сообщение WM_GETTEXTFIELD, но теперь не совсем уверен, что это возможно.

    Подскажите, пожалуйста, что не так с кодом и/или что почитать на эту тему.
    Заранее спасибо!
  • sniknik © (04.11.18 17:50) [1]
    строки контролируются компилятором, типа "компилед магик" памятью под них рулит... используй PChar, выделяй память сам при посылке, освобождай при получении.
  • Leonid Troyanovsky © (04.11.18 17:54) [2]

    > Илья_666   (04.11.18 17:29) 

    > Подскажите, пожалуйста, что не так с кодом и/или что почитать

    RTFW: SendMessage with WM_COPYDATA

    --
    Regards, LVT.
  • Илья_666 (04.11.18 18:17) [3]

    > используй PChar, выделяй память сам при посылке, освобождай
    > при получении

    Будет ли конструкция вида S := 'qwerty' считаться выделением памяти? Или надо именно через New делать?
    Освобождать обязательно? Я думал, компилятор сам это делает.


    > RTFW: SendMessage with WM_COPYDATA

    Спасибо, но WM_COPYDATA больше подходит для обмена между приложениями, а мне надо в рамках одного приложения.
  • SeeMee (05.11.18 03:30) [4]
    @S - єто адрес строки S. Соответствено, должно бьіть FLabelField := String(Pointer(Message.WParam)^).
  • Илья_666 (05.11.18 14:12) [5]

    > @S - єто адрес строки S. Соответствено, должно бьіть FLabelField
    > := String(Pointer(Message.WParam)^).

    Спасибо! Пример также рабочий.

    В общем, с первой проблемой все решено. Осталось решить вопрос возврата значения строки в сообщении.

    Подскажите еще одну вещь: если необходимо модифицировать значение строки по ссылке, то ее надо передавать как


    SendMessage(MyComp.Handle, WM_GETTEXTFIELD, WParam(@S), 0);


    или

    SendMessage(MyComp.Handle, WM_GETTEXTFIELD, WParam(PChar(S)), 0);



    PChar - это указатель на строку (если я правильно прочитал статьи). Но система запрещает модификацию данных по этому указателю, верно? Или я ошибаюсь?

    Еще один момент. Если использовать вариант с WParam(@S), то у меня получилось модифицировать строку следующим кодом (спасибо SeeMee):

    String(Pointer(Message.WParam)^) := FLabelField;



    Насколько это корректный метод? Подразумевается, что пременная S - это стандартный тип String.
  • Sha © (06.11.18 00:10) [6]
    Илья_666   (04.11.18 18:17) [3]

    > Будет ли конструкция вида S := 'qwerty' считаться выделением памяти? > Или надо именно через New делать?
    > Освобождать обязательно? Я думал, компилятор сам это делает.

    Проблема не столько в выделении, сколько в освобождении.
    Для начала замечу, что ReadOnly-строки-константы, напр. s:='qwerty',
    можно передавать без проблем, т.к. память под них отводится
    при компиляции и в процессе работы не меняется.
    Но для вычисляемых строк, напр. s:=IntToStr(i), это не так.

    Чтобы упростить алгоритм, имеет смысл после присваивания строке нужного значения перед SendMessage сделать строку уникальной
    при помощи вызова Unique(s), чтобы ее счетчик ссылок стал равен 1.
     
    Передавать стоку надо как pointer(s).
    Затем сразу после SendMessage необходимо выполнить оператор
    pointer(s):=nil, чтобы предотвратить автоматическую финализацию
    переданной строки после передачи.

    Прием строки нельзя выполнять присваиванием, т.к. это изменит
    ее счетчик ссылок. Необходимо последовательно выполнить операторы
    s:=''; pointer(s):=ПереданноеЗначение; чтобы обеспечить последующую
    автоматическую финализацию без утечек памяти в месте приема.

    Материал предоставлен в ознакомительных целях, корректная работа не гарантируется )
    При реализация легко допустить ошибку. Плюс такой код не прозрачен.
    Потому подобных приемов следует избегать.
  • Германн © (06.11.18 01:27) [7]

    > Материал предоставлен в ознакомительных целях, корректная
    > работа не гарантируется )

    А я то сразу не понял для чего вы, Александр, приводите такие шаманские пляски в ответе :)
    По мне так при работе с WinAPI надёжнее всего изучить работу с типом PChar. И проблем не должно быть.
  • Sha © (06.11.18 19:15) [8]
    > Германн ©   (06.11.18 01:27) [7]

    В умелых руках оно быстро, наглядно, надежно и высокоуровнево )

    А вот с пчаром, там точно шаманские пляски )
  • Sha © (06.11.18 20:53) [9]
    Сейчас перечитал [6], обнаружил 2 неточности:

    1. Упомянутая функция называется, конечно, UniqueString.

    2. И нужна она не для того, чтобы упростить алгоритм,
    а чтобы упростить понимание алгоритма,
    т.е. чтобы его поведение было единообразным для всех непустых строк.
    Там поначалу было задумано длинное объяснение поведения счетчика ссылок,
    но потом решил закруглиться в виду позднего времени.

    Т.е. вызывать UniqueString на самом деле не требуется, все будет работать и без нее,
    просто счетчик ссылок в момент передачи будет иметь произвольное значение.
  • han_malign © (07.11.18 12:24) [10]

    > В умелых руках оно быстро, наглядно, надежно и высокоуровнево )

    - но требует жесткой логики приёмника. Если в приёмнике появится ветвление без захвата отпущенной строки - будет утечка... Лишний инкремент/декремент счётчика погоды не делают...
    А для изоляции var-семантики - лучше явно объявить запись со строковым полем и передавать указатель на запись... Результирующий код одинаковый, но непоняток с собаками-крышками меньше.
    type
      PGetTextField = ^TGetTextField;
      TGetTextField = record
         str: string;
      end;

    {private}
    procedure TMyComp.GetTextField_impl(var fld: TGetTextField);
    begin
      ...
    end;

    procedure TMyComp.WMGetTextField(var msg: TMessage);{message WM_GETTEXTFIELD;}
    begin
      GetTextFiled_impl(PGetTextField(msg.WParam)^);
    end;

    {public}
    procedure TMyComp.GetTextFieldExplicit(var fld: TGetTextField);
    begin
        SendMessage(Handle, WParam(@fld), 0);
    end;

    function TMyComp.GetTextField(): string;
    var fld: TGetTextField;
    begin
      GetTextFieldExplicit(fld);
      Result:= fld.str;
    end;
  • Sha © (07.11.18 12:46) [11]
    > han_malign ©   (07.11.18 12:24) [10]

    >> В умелых руках оно быстро, наглядно, надежно и высокоуровнево )

    > - но требует жесткой логики приёмника.
    > Если в приёмнике появится ветвление без захвата отпущенной строки - будет утечка...

    Жесткая логика пишется один раз, комментируется - и 100 лет счастья.
    И нефиг туда лезть кому попало, а полезли - бить по рукам )

    > Лишний инкремент/декремент счётчика погоды не делают...

    Дело тут вовсе не в лишнем инкременте-декременте,
    а в гарантии правильной работы.

    Иначе возможен сценарий, когда сначала происходит декремент и,
    как следствие, финализации строки, после чего приемник улетает в космос.
  • han_malign © (07.11.18 13:13) [12]

    > а в гарантии правильной работы.
    >
    > Иначе возможен сценарий,

    это отдельная тема оптимистического программирования
    if( PostMessage(Handle, WParam(Pointer(hackStr)), 0) )then
      Pointer(hackStr):= nil;
  • Sha © (07.11.18 21:52) [13]
    > han_malign ©   (07.11.18 13:13) [12]

    Что тут имеется в виду?
    Нельзя ли немного развернуть мысль?
  • sniknik © (08.11.18 10:40) [14]
    вообще всех этих извращений следует избегать... ведь тут локальный внутри программный компонент, команда SendMessage синхронная, т.е. все чего добиваются тут это "крутая/как winapi" замена обычного присваивания MyComp.MyStr:= S;
    без цели/реальной необходимости в чем то подобном (в вопросе ее нет) все это не более чем извращения.

    > это отдельная тема оптимистического программирования
    > if( PostMessage(Handle, WParam(Pointer(hackStr)), 0) )then
    >    Pointer(hackStr):= nil;
    а вот это уже приведет к глюкам, т.к. PostMessage это с постановкой в очередь, и необязательно сразу прочитает из нее, немедленное "обниление" до прочтения = AV.
  • Sha © (08.11.18 10:49) [15]
    > sniknik ©   (08.11.18 10:40) [14]
    > вообще всех этих извращений следует избегать...

    согласен

    >> if( PostMessage(Handle, WParam(Pointer(hackStr)), 0) )then
    >>    Pointer(hackStr):= nil;
    > а вот это уже приведет к глюкам ...

    откуда такая уверенность?
    сначала не мешало бы проверить )
  • sniknik © (08.11.18 11:01) [16]
    > откуда такая уверенность?
    по логике, знаю как работает PostMessage - делает типа очереди сообщений, если туда вклинится что-то "служебное", да что угодно, будет разрыв между освобождением памяти и ее чтением, память могут и "перезанять" - AV, или хуже тихая ошибка с прочтением не того.

    > сначала не мешало бы проверить )
    а вот на проверке, тем более простой, не обремененной другим функционалом-событиями скорее всего все будет работать.

    самая сложная ошибка когда все работает и только иногда... в зависимости от фаз луны... в общем я бы такого не писал.
  • Sha © (08.11.18 12:02) [17]
    > sniknik ©   (08.11.18 11:01) [16]
    > по логике, знаю как работает PostMessage...
    > память могут и "перезанять" - AV, или хуже тихая ошибка с прочтением не того.
    > самая сложная ошибка когда все работает и только иногда...

    Пример с перезанятием или тихой ошибкой будет?

    Я не только знаю, как работает PostMessage, но и использую.
    Много лет такая передача работает независимо от фаз луны.
  • han_malign © (08.11.18 12:48) [18]

    > команда SendMessage синхронная, т.е. все чего добиваются тут это "крутая/как winapi" замена обычного присваивания MyComp.MyStr:= S;

    - синхронная, только WindowProc выполняется в контексте потока в котором вызывался CreateWindow (см. GetWindowThreadProcessId()), и - если освобождение строки потокобезопасно, то с обычным присваиванием будут проблемы - для строк присваивание гарантированно не атомарное, со всеми прелестями в виде утечек, повторного удаления и AV...
    C PostMessage vs PeekMessage/GetMessage - такая же фигня "... The window must belong to the current thread. ...".
    SendMessage:
    ...
    If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine. If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure. Messages sent between threads are processed only when the receiving thread executes message retrieval code. The sending thread is blocked until the receiving thread processes the message. However, the sending thread will process incoming nonqueued messages while waiting for its message to be processed. ...



    > Нельзя ли немного развернуть мысль?
    ...
    > будет разрыв между освобождением памяти и ее чтением


    procedure post_string(const inputStr: string);
    var hackStr: string;
    begin
     {implicit compiler magic
      Initialize(hackStr);
      try
     }
         hackStr:= inputStr;//add inputStr reference count
         if( delegate_reference_owning(pointer(hackStr)) )then
            Pointer(hackStr):= nil
        { duplicates implicit compiler magic
         else // освобождаем память если владелец ссылки не поменялся
            hackStr:= '' // release string explicitly
        };

     {implicit compiler magic
      finally
         Finalize(hackStr);
      end;
     }
    end;

    procedure  hook_string(looseString: pointer);
    var hackStr: string;
    begin
     {implicit compiler magic
      Initialize(hackStr);
      try
     }
         pointer(hackStr):= looseString;
         process_owned_string(hackStr);
     {implicit compiler magic
      finally
         Finalize(hackStr);
      end;
     }
    end;

    Под оптимистичностью - здесь подразумевается, что пока новый владелец не обработал сообщение - ссылка ничья - потенциальная утечка памяти.
    То есть - должно подразумеваться, что неожиданное завершение обработки очереди сообщений - возможно только в случае завершения работы приложения. Иначе будет течь...
  • Sha © (08.11.18 13:53) [19]
    > То есть - должно подразумеваться, что неожиданное завершение обработки очереди сообщений
    > возможно только в случае завершения работы приложения. Иначе будет течь...

    Ну это само собой.
    Обычно передают главному потоку, так что это требование выполняется автоматом.
  • Илья_666 (12.11.18 17:52) [20]
    Прошу прощения за долгое отсутствие - проблемы с Интернетом (я из глубинки).

    Почитал ваши ответы, товарищи, и решил применить вариант с записями (спасибо пользователю han_malign!). Как-то надежнее выглядит. В компоненте сделаю что-то наподобие макросов для ListView: несколько публичных свойств и никаких проблем. А то все равно так и не разобрался с указателями и строками.

    Пользователю Sha: ну Вы и написали в [6]! Такое не сразу осознаешь. Это же где такому учат? Или Вы по книгам изучали?

    Большое спасибо всем откликнувшимся!
  • Eraser © (13.11.18 19:05) [21]
    оно то работает конечно, но в современном делфи есть все средства, чтобы сделать по феншую, например, через TEncoding.Unicode.GetBytes.
    по поводу синхронности - не уверен, что WM_COPYDATA вообще заработает через PostMessage, но и пробовать не нужно, отсылайте через SendMessage, как и велит документация - все будет синхронно.

    но, в твоем случае, правильно использовать WM_SETTEXT + PChar + SendMessage и не изобретать велосипед.


    >  А то все равно так и не разобрался с указателями и строками.

    без этого про сообщения можно забыть, почти весь Win32 завязан на указателях.
  • Илья_666 (14.11.18 16:37) [22]

    > не уверен, что WM_COPYDATA вообще заработает через PostMessage

    Меня, видимо, не правильно поняли (или я не толком не описал свой вопрос). Мне нужно иметь возможность установки/получения значения текстового поля (тип String), находящегося "внутри" компонента. Компонент самописный, все работы с ним ведутся в пределах одной формы. Поэтому WM_COPYDATA не подойдет в моем случае.


    > но, в твоем случае, правильно использовать WM_SETTEXT +
    > PChar + SendMessage и не изобретать велосипед.
    >

    Хорошая идея! Я сразу про это сообщение подумал (до того, как задать вопрос), но потом решил, что универсальное название сообщения (WM_SETTEXT) как-то слишком туманно выглядит. И решил реализовать схожий функционал через свое сообщение))


    > без этого про сообщения можно забыть, почти весь Win32 завязан
    > на указателях.

    Второй день уже читаю статьи на Королевстве и в блогах разных программистов. Вроде все толком пишут, вроде понятно. Пробую на практике - и не понимаю: что, куда и как. Надо еще времени уделить этой сфере.
  • Sha © (14.11.18 21:03) [23]
    Простейший пример посылки сообщений из формы cамой себе:

    unit PostStringForm;

    interface

    uses
     Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;

    const
     WM_MYDATA= WM_USER+100;
     MyPostAsInteger = $52983698;
     MyPostAsString  = $23984729;

    type
     TForm1 = class(TForm)
       Memo1: TMemo;
       Edit1: TEdit;
       Button1: TButton;
       Button2: TButton;
       procedure Button1Click(Sender: TObject);
       procedure Button2Click(Sender: TObject);
     private
       procedure WMMyData(var M: TMessage); message WM_MYDATA;
     public
       { Public declarations }
     end;

    var
     Form1: TForm1;

    implementation

    {$R *.dfm}

    function PostInteger(handle: HWND; data: integer): boolean;
    begin;
     Result:=PostMessage(handle, WM_MYDATA, MyPostAsInteger, LPARAM(data));
     end;

    function PostString(handle: HWND; const data: string): boolean;
    var
     s: string;
    begin;
     s:=data;
     Result:=PostMessage(handle, WM_MYDATA, MyPostAsString, LPARAM(pointer(s)));
     if Result then pointer(s):=nil;
     end;

    function LParamToString(lp: LPARAM): string;
    begin;
     Result:='';
     LPARAM(Result):=LP;
     end;

    procedure TForm1.WMMyData(var M: TMessage);
    var
     s: string;
    begin;
     M.Result:=1;
     if M.WParam=MyPostAsInteger then s:=IntToStr(M.LParam)
     else if M.WParam=MyPostAsString then s:=LParamToString(M.LParam)
     else M.Result:=0;

     if M.Result=0
     then Memo1.Lines.Add('неизвестный тип: ' + IntToHex(M.WParam, 8))
     else Memo1.Lines.Add('принято: ' + s);
     end;

    procedure TForm1.Button1Click(Sender: TObject);
    var
     i: integer;
    begin;
     i:=Random(100);
     if PostInteger(Handle, i) then Memo1.Lines.Add('послано: ' + IntToStr(i));
     end;

    procedure TForm1.Button2Click(Sender: TObject);
    var
     s: string;
    begin;
     s:=Edit1.Text;
     if PostString(Handle, s) then Memo1.Lines.Add('послано: ' + s);
     end;

    end.
 
Конференция "Начинающим" » Послать строку в сообщении [D2005, XP]
Есть новые Нет новых   [94805   +147][b:0.001][p:0.004]