[SOLVED] Win API and WideString lazarus / 34-Windows ---------------------------------------------------------------------- JimD · 2018-09-10 21:45 I'm still learning so I thought I'd ask if I'm on the right track or not. My Config:   Win10 Home 64-bit, Lazarus 1.8.2 with FPC 3.0.4 64 bit, and cross-i386-win32-win64 After lots of searching and reading, I pieced together the following code. It runs fine, but is there a better way to implement line 45 with maybe one function versus two?      sTitle:=StrPas(PWChar(S)); My trival test is that I have this window open and I see the unicode chars in the Memo, as expected: https://vi.wikipedia.org/wiki/Nh%C3%A0_th%E1%BB%9D_Ch%E1%BB%A3_Qu%C3%A 1n Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,  Windows; type  { TForm1 }   TForm1 = class(TForm)    ButtonOK: TButton;    Memo1: TMemo;    procedure ButtonOKClick(Sender: TObject);    procedure FormCreate(Sender: TObject);    procedure FormDestroy(Sender: TObject);  end;var  Form1: TForm1;  WindowList: TStringList;  implementation {$R *.lfm} { methods } function EnumWindowsProc(Handle: HWnd; lParam: LPARAM): Bool; stdcall;var  S: WideString;  Len: LongInt;  sTitle: string ;begin   SetLength(S, 256);   //Len := GetWindowTextW(Handle, @S[1], Length(S)); //this works also   Len := GetWindowTextW(Handle, LPWSTR(S), Length(S));  if Len > 0 then    sTitle:=StrPas(PWChar(S)); //maybe one function versus two?  if (not sTitle.IsEmpty) and IsWindowVisible(Handle) then begin   WindowList.Add(sTitle);  end;   Result := True;end; { TForm1 } procedure TForm1.ButtonOKClick(Sender: TObject);var  i: integer;begin  Memo1.Clear;   EnumWindows(@EnumWindowsProc,0);   for i:=0 to WindowList.Count-1 do begin    Memo1.Append(WindowList[i]);  end;   WindowList.Clear; end; procedure TForm1.FormCreate(Sender: TObject);begin  WindowList:=TStringList.Create;end; procedure TForm1.FormDestroy(Sender: TObject);begin  WindowList.Free;end; end. ---------------------------------------------------------------------- lucamar · 2018-09-10 21:52 You're complicating things; you can simply do:     sTitle := S. since String and WideString are assignable to each other. At most, you may convert specifically to UTF-8 by doing:     sTitle := UTF8Encode(S). Nowadays StrPas() is basically a left-over from the past By the way, a more proper way to adjust the widestring is: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   SetLength(S, 256);  Len := GetWindowTextW(Handle, LPWSTR(S), Length(S));  SetLength(S, Len); {<<< This may be important!!!} ---------------------------------------------------------------------- taazz · 2018-09-10 21:53 Quote from: jasc2v8 on September 10, 2018, 11:45:29 pmI'm still learning so I thought I'd ask if I'm on the right track or not. My Config:   Win10 Home 64-bit, Lazarus 1.8.2 with FPC 3.0.4 64 bit, and cross-i386-win32-win64 After lots of searching and reading, I pieced together the following code. It runs fine, but is there a better way to implement line 45 with maybe one function versus two?      sTitle:=StrPas(PWChar(S)); My trival test is that I have this window open and I see the unicode chars in the Memo, as expected: https://vi.wikipedia.org/wiki/Nh%C3%A0_th%E1%BB%9D_Ch%E1%BB%A3_Qu%C3%A 1n Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,  Windows; type  { TForm1 }   TForm1 = class(TForm)    ButtonOK: TButton;    Memo1: TMemo;    procedure ButtonOKClick(Sender: TObject);    procedure FormCreate(Sender: TObject);    procedure FormDestroy(Sender: TObject);  end;var  Form1: TForm1;  WindowList: TStringList;  implementation {$R *.lfm} { methods } function EnumWindowsProc(Handle: HWnd; lParam: LPARAM): Bool; stdcall;var  S: WideString;  Len: LongInt;  sTitle: string ;begin   SetLength(S, 256);   //Len := GetWindowTextW(Handle, @S[1], Length(S)); //this works also   Len := GetWindowTextW(Handle, LPWSTR(S), Length(S));  if Len > 0 then    sTitle:=StrPas(PWChar(S)); //maybe one function versus two?  if (not sTitle.IsEmpty) and IsWindowVisible(Handle) then begin   WindowList.Add(sTitle);  end;   Result := True;end; { TForm1 } procedure TForm1.ButtonOKClick(Sender: TObject);var  i: integer;begin  Memo1.Clear;   EnumWindows(@EnumWindowsProc,0);   for i:=0 to WindowList.Count-1 do begin    Memo1.Append(WindowList[i]);  end;   WindowList.Clear; end; procedure TForm1.FormCreate(Sender: TObject);begin  WindowList:=TStringList.Create;end; procedure TForm1.FormDestroy(Sender: TObject);begin  WindowList.Free;end; end.  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i      sTitle:=StrPas(PWChar(S)); that is not two function its a type cast and a function, that is safer(?) form of Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i      sTitle:=StrPas(@S[1]);, in the sense that the compiler will generate the correct code for you instead of you accessing by mistake the @s[0] which does not exists and will lead to an expcetion. ---------------------------------------------------------------------- JimD · 2018-09-10 22:39 lucamar, You solved the core issue I had, but did not mention: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i SetLength(S, 256);Len := GetWindowTextW(Handle, LPWSTR(S), Length(S));SetLength(S, Len); {<<< This may be important!!!}sTitle := S  Without resetting the length of the WideString, I wasn't able to append any strings to the S, they were truncated when appending to the Memo.  Now I understand why, thank you! taaz, Thank you for the clarification; one type cast and one method.  Also the tip about not trying @S[0] which fortunately I caught early on.  I read the docs about the WideString memory allocation so this makes total sense. ---------------------------------------------------------------------- lucamar · 2018-09-10 22:47 Quote from: jasc2v8 on September 11, 2018, 12:39:20 amYou solved the core issue I had, but did not mention: [...] FYI, This style of Windows API calls usually can be invoked with nil as the pointer, in which case they return the length of the buffer needed, so all one has to do is something like this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Len := GetWindowTextW(Handle, nil, 0);SetLength(S, Len);GetWindowTextW(Handle, LPWSTR(S), Length(S));sTitle := S But I don't remember, without looking in the PSDK, whether GetWindowText can be called thus, hence the proposed solution. Glad to have been of help. ---------------------------------------------------------------------- JimD · 2018-09-10 23:17 Yes, indeed. Updated the code, thank you! Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,  Windows; type  { TForm1 }   TForm1 = class(TForm)    ButtonOK: TButton;    Memo1: TMemo;    procedure ButtonOKClick(Sender: TObject);    procedure FormCreate(Sender: TObject);    procedure FormDestroy(Sender: TObject);  end;var  Form1: TForm1;  WindowList: TStringList;  implementation {$R *.lfm} { methods } function EnumWindowsProc(Handle: HWnd; lParam: LPARAM): Bool; stdcall;var  S: WideString;  Len: LongInt;  sTitle: string ;begin   //SetLength(S, 256); //replaced with GetWindowTextLengthW   Len := GetWindowTextLengthW(Handle);  SetLength(S, Len);   Len := GetWindowTextW(Handle, LPWSTR(S), Len+1);  if Len > 0 then    sTitle:=S;   if (not sTitle.IsEmpty) and IsWindowVisible(Handle) then begin     WindowList.Add(sTitle);  end;   Result := True;end; { TForm1 } procedure TForm1.ButtonOKClick(Sender: TObject);var  i: integer;begin  Memo1.Clear;   EnumWindows(@EnumWindowsProc,0);   for i:=0 to WindowList.Count-1 do begin    Memo1.Append(WindowList[i]+' [appended text]'); //appended text proves setlength  end;   WindowList.Clear; end; procedure TForm1.FormCreate(Sender: TObject);begin  WindowList:=TStringList.Create;end; procedure TForm1.FormDestroy(Sender: TObject);begin  WindowList.Free;end; end. ---------------------------------------------------------------------- lucamar · 2018-09-11 00:54 While we are at it, how about this? Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function EnumWindowsProc(Handle: HWnd; lParam: LPARAM): Bool; stdcall;var  S: WideString;  Len: LongInt;  sTitle: string ;begin  if IsWindowVisible(Handle) then begin    Len := GetWindowTextLengthW(Handle);    if Len > 0 then begin      SetLength(S, Len);      GetWindowTextW(Handle, LPWSTR(S), Len+1);    {Pre-conversion shouldn't be needed, really; but better safe than sorry!}      sTitle := S;      WindowList.Add(sTitle);    end;  end;  Result := True;end;  What you're really doing is adding to WindowList the name of visible windows which have one; hence we test IsWindowVisible() first of all and only keep going if it is. Next we check if the window has a name by requesting the size of the buffer needed to hold it. If it's zero, nothing else needs be done so, again, the function can skip the next steps; if Len, however, is not zero then the window has a name, so we retrieve it and add it to WindowList. Finally, the unconditional Result := True is used to tell Windows to keep enumerating windows until no more are left. Quite simple, really. ---------------------------------------------------------------------- JimD · 2018-09-11 01:06 Yes, concise, clean, faster, and simpler. Thank you.