Recent

Author Topic: [SOLVED] Win API and WideString  (Read 2817 times)

JimD

  • Jr. Member
  • **
  • Posts: 62
[SOLVED] Win API and WideString
« on: September 10, 2018, 11:45:29 pm »
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%A1n

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  9.   Windows;
  10.  
  11. type
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     ButtonOK: TButton;
  16.     Memo1: TMemo;
  17.     procedure ButtonOKClick(Sender: TObject);
  18.     procedure FormCreate(Sender: TObject);
  19.     procedure FormDestroy(Sender: TObject);
  20.   end;
  21. var
  22.   Form1: TForm1;
  23.   WindowList: TStringList;
  24.  
  25.  
  26. implementation
  27.  
  28. {$R *.lfm}
  29.  
  30. { methods }
  31.  
  32. function EnumWindowsProc(Handle: HWnd; lParam: LPARAM): Bool; stdcall;
  33. var
  34.   S: WideString;
  35.   Len: LongInt;
  36.   sTitle: string ;
  37. begin
  38.  
  39.   SetLength(S, 256);
  40.  
  41.   //Len := GetWindowTextW(Handle, @S[1], Length(S)); //this works also
  42.  
  43.   Len := GetWindowTextW(Handle, LPWSTR(S), Length(S));
  44.   if Len > 0 then
  45.     sTitle:=StrPas(PWChar(S)); //maybe one function versus two?
  46.   if (not sTitle.IsEmpty) and IsWindowVisible(Handle) then begin
  47.      WindowList.Add(sTitle);
  48.   end;
  49.  
  50.   Result := True;
  51. end;
  52.  
  53. { TForm1 }
  54.  
  55. procedure TForm1.ButtonOKClick(Sender: TObject);
  56. var
  57.   i: integer;
  58. begin
  59.   Memo1.Clear;
  60.  
  61.   EnumWindows(@EnumWindowsProc,0);
  62.  
  63.   for i:=0 to WindowList.Count-1 do begin
  64.     Memo1.Append(WindowList[i]);
  65.   end;
  66.  
  67.   WindowList.Clear;
  68.  
  69. end;
  70.  
  71. procedure TForm1.FormCreate(Sender: TObject);
  72. begin
  73.   WindowList:=TStringList.Create;
  74. end;
  75.  
  76. procedure TForm1.FormDestroy(Sender: TObject);
  77. begin
  78.   WindowList.Free;
  79. end;
  80.  
  81. end.
  82.  
  83.  

« Last Edit: September 11, 2018, 12:39:37 am by jasc2v8 »

lucamar

  • Hero Member
  • *****
  • Posts: 2965
Re: Win API and WideString
« Reply #1 on: September 10, 2018, 11:52:44 pm »
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][+][-]
  1.   SetLength(S, 256);
  2.   Len := GetWindowTextW(Handle, LPWSTR(S), Length(S));
  3.   SetLength(S, Len); {<<< This may be important!!!}
  4.  
« Last Edit: September 11, 2018, 12:08:31 am by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.8/FPC 3.0.4 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

taazz

  • Hero Member
  • *****
  • Posts: 5365
Re: Win API and WideString
« Reply #2 on: September 10, 2018, 11:53:58 pm »
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%A1n

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  9.   Windows;
  10.  
  11. type
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     ButtonOK: TButton;
  16.     Memo1: TMemo;
  17.     procedure ButtonOKClick(Sender: TObject);
  18.     procedure FormCreate(Sender: TObject);
  19.     procedure FormDestroy(Sender: TObject);
  20.   end;
  21. var
  22.   Form1: TForm1;
  23.   WindowList: TStringList;
  24.  
  25.  
  26. implementation
  27.  
  28. {$R *.lfm}
  29.  
  30. { methods }
  31.  
  32. function EnumWindowsProc(Handle: HWnd; lParam: LPARAM): Bool; stdcall;
  33. var
  34.   S: WideString;
  35.   Len: LongInt;
  36.   sTitle: string ;
  37. begin
  38.  
  39.   SetLength(S, 256);
  40.  
  41.   //Len := GetWindowTextW(Handle, @S[1], Length(S)); //this works also
  42.  
  43.   Len := GetWindowTextW(Handle, LPWSTR(S), Length(S));
  44.   if Len > 0 then
  45.     sTitle:=StrPas(PWChar(S)); //maybe one function versus two?
  46.   if (not sTitle.IsEmpty) and IsWindowVisible(Handle) then begin
  47.      WindowList.Add(sTitle);
  48.   end;
  49.  
  50.   Result := True;
  51. end;
  52.  
  53. { TForm1 }
  54.  
  55. procedure TForm1.ButtonOKClick(Sender: TObject);
  56. var
  57.   i: integer;
  58. begin
  59.   Memo1.Clear;
  60.  
  61.   EnumWindows(@EnumWindowsProc,0);
  62.  
  63.   for i:=0 to WindowList.Count-1 do begin
  64.     Memo1.Append(WindowList[i]);
  65.   end;
  66.  
  67.   WindowList.Clear;
  68.  
  69. end;
  70.  
  71. procedure TForm1.FormCreate(Sender: TObject);
  72. begin
  73.   WindowList:=TStringList.Create;
  74. end;
  75.  
  76. procedure TForm1.FormDestroy(Sender: TObject);
  77. begin
  78.   WindowList.Free;
  79. end;
  80.  
  81. end.
  82.  
  83.  
Code: Pascal  [Select][+][-]
  1.      sTitle:=StrPas(PWChar(S));
that is not two function its a type cast and a function, that is safer(?) form of
Code: Pascal  [Select][+][-]
  1.      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.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

JimD

  • Jr. Member
  • **
  • Posts: 62
Re: Win API and WideString
« Reply #3 on: September 11, 2018, 12:39:20 am »
lucamar,

You solved the core issue I had, but did not mention:

Code: Pascal  [Select][+][-]
  1. SetLength(S, 256);
  2. Len := GetWindowTextW(Handle, LPWSTR(S), Length(S));
  3. SetLength(S, Len); {<<< This may be important!!!}
  4. sTitle := S
  5.  

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

  • Hero Member
  • *****
  • Posts: 2965
Re: Win API and WideString
« Reply #4 on: September 11, 2018, 12:47:18 am »
You 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][+][-]
  1. Len := GetWindowTextW(Handle, nil, 0);
  2. SetLength(S, Len);
  3. GetWindowTextW(Handle, LPWSTR(S), Length(S));
  4. sTitle := S
  5.  
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.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.8/FPC 3.0.4 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

JimD

  • Jr. Member
  • **
  • Posts: 62
Re: [SOLVED] Win API and WideString
« Reply #5 on: September 11, 2018, 01:17:49 am »
Yes, indeed. Updated the code, thank you!

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  9.   Windows;
  10.  
  11. type
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     ButtonOK: TButton;
  16.     Memo1: TMemo;
  17.     procedure ButtonOKClick(Sender: TObject);
  18.     procedure FormCreate(Sender: TObject);
  19.     procedure FormDestroy(Sender: TObject);
  20.   end;
  21. var
  22.   Form1: TForm1;
  23.   WindowList: TStringList;
  24.  
  25.  
  26. implementation
  27.  
  28. {$R *.lfm}
  29.  
  30. { methods }
  31.  
  32. function EnumWindowsProc(Handle: HWnd; lParam: LPARAM): Bool; stdcall;
  33. var
  34.   S: WideString;
  35.   Len: LongInt;
  36.   sTitle: string ;
  37. begin
  38.  
  39.   //SetLength(S, 256); //replaced with GetWindowTextLengthW
  40.  
  41.   Len := GetWindowTextLengthW(Handle);
  42.   SetLength(S, Len);
  43.  
  44.   Len := GetWindowTextW(Handle, LPWSTR(S), Len+1);
  45.   if Len > 0 then
  46.     sTitle:=S;
  47.  
  48.   if (not sTitle.IsEmpty) and IsWindowVisible(Handle) then begin
  49.      WindowList.Add(sTitle);
  50.   end;
  51.  
  52.   Result := True;
  53. end;
  54.  
  55. { TForm1 }
  56.  
  57. procedure TForm1.ButtonOKClick(Sender: TObject);
  58. var
  59.   i: integer;
  60. begin
  61.   Memo1.Clear;
  62.  
  63.   EnumWindows(@EnumWindowsProc,0);
  64.  
  65.   for i:=0 to WindowList.Count-1 do begin
  66.     Memo1.Append(WindowList[i]+' [appended text]'); //appended text proves setlength
  67.   end;
  68.  
  69.   WindowList.Clear;
  70.  
  71. end;
  72.  
  73. procedure TForm1.FormCreate(Sender: TObject);
  74. begin
  75.   WindowList:=TStringList.Create;
  76. end;
  77.  
  78. procedure TForm1.FormDestroy(Sender: TObject);
  79. begin
  80.   WindowList.Free;
  81. end;
  82.  
  83. end.
  84.  

lucamar

  • Hero Member
  • *****
  • Posts: 2965
Re: [SOLVED] Win API and WideString
« Reply #6 on: September 11, 2018, 02:54:10 am »
While we are at it, how about this?

Code: Pascal  [Select][+][-]
  1. function EnumWindowsProc(Handle: HWnd; lParam: LPARAM): Bool; stdcall;
  2. var
  3.   S: WideString;
  4.   Len: LongInt;
  5.   sTitle: string ;
  6. begin
  7.   if IsWindowVisible(Handle) then begin
  8.     Len := GetWindowTextLengthW(Handle);
  9.     if Len > 0 then begin
  10.       SetLength(S, Len);
  11.       GetWindowTextW(Handle, LPWSTR(S), Len+1);
  12.       {Pre-conversion shouldn't be needed, really; but better safe than sorry!}
  13.       sTitle := S;
  14.       WindowList.Add(sTitle);
  15.     end;
  16.   end;
  17.   Result := True;
  18. end;
  19.  

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.  8-)
« Last Edit: September 11, 2018, 03:01:18 am by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.8/FPC 3.0.4 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

JimD

  • Jr. Member
  • **
  • Posts: 62
Re: [SOLVED] Win API and WideString
« Reply #7 on: September 11, 2018, 03:06:55 am »
Yes, concise, clean, faster, and simpler.
Thank you.

 

TinyPortal © 2005-2018