7

Is SetWindowSubClass() supposed to change an ANSI window into a UNICODE widow? I didn't find anything in the documentation, or on the web, about this behavior.

I created a test application (full source) just to illustrate how SetWindowSubclass (I believe) changes the type of the affected window from ANSI to UNICODE, as it shouldn't! IsWindowUnicode() confirms the change.

 program TwoWaySubclassing;

 {$apptype gui}
 {$R Generic.res}                          

 {
 { I created this test application just to illustrate how SetWindowSubclass()
 { changes -- I believe -- the type of the affected window from ANSI to UNICODE,
 { as it shouldn't! IsWindowUnicode() confirms that.
 {
 { The Delphi 7 (all ANSI) application has 2 edit controls:
 {   1. The smaller, which is subclassed in 2 switchable ways (called Modes).
 {   2. The bigger, like a memo, not subclassed. Just for dumping info.
 {   3. A button for switching between modes, on-the-fly.
 {
 { The default subclassing Mode uses SetWindowLong (the classic way).
 { When pressing the button, the edit control is subclassed via SetWindowSubclass.
 { Pressing it again brings the edit control back to the default SetWindowLong mode.
 {
 { The main window (and all child controls) are created using the ANSI version
 { of the API procedure, so the message handler should receive, in "lParam",
 { a pointer to an ANSI text (along with the wm_SetText message), always!
 {
 { The problem is that's not happening when the edit control is subclassed using
 { the SetWindowSubclass mode! SetWindowSubclass() simply changes the window
 { from ANSI to UNICODE and starts sending a PWideChar(lParam) rather than the
 { expected PAnsiChar(lParam).
 {
 { Once back to the default SetWindowLong mode, the window becomes ANSI again!
 { Just run the application and try switching between modes. Look carefully at the
 { detailed info shown in the bigger edit control.
 {
 { Screenshots:
 {   1. http://imgh.us/mode1.png
 {   2. http://imgh.us/mode2.png
 {
 { Environment:
 {   Windows 7 32-bit
 {   Delphi 7 (all-ANSI)
 {
 { Regards,
 {   Paulo França Lacerda
 }

 uses
   Windows,
   Messages,
   SysUtils;

 type
   UINT_PTR  = Cardinal;
   DWORD_PTR = Cardinal;

   TSubClassProc = function (hWnd:HWND; uMsg:UINT; wParam:WPARAM; lParam:LPARAM; uIdSubclass:UINT_PTR; dwRefData:DWORD_PTR) :LRESULT; stdcall;

   TSubMode = (
     subSetWindowLong,
     subSetWindowSubclass);

 const
   LtBool    :Array[Boolean]  of String = ('False', 'True');
   LtSubMode :Array[TSubMode] of String = ('SetWindowLong', 'SetWindowSubclass');

   strTextUsingPAnsiChar = 'ANSI Text in PAnsiChar(lParam)';
   strTextUsingPWideChar = 'UNICODE Text in PWideChar(lParam)';

 const
   cctrl = Windows.comctl32;

 function SetWindowSubclass    (hWnd:Windows.HWND; pfnSubclass:TSubClassProc; uIdSubclass:UINT_PTR; dwRefData:DWORD_PTR) :BOOL; stdcall; external cctrl name 'SetWindowSubclass';
 function RemoveWindowSubclass (hWnd:Windows.HWND; pfnSubclass:TSubClassProc; uIdSubclass:UINT_PTR) :BOOL;                      stdcall; external cctrl name 'RemoveWindowSubclass';
 function DefSubclassProc      (hWnd:HWND; uMsg:UINT; wParam:WPARAM; lParam:LPARAM) :LRESULT;                                   stdcall; external cctrl name 'DefSubclassProc';

 var
   wc  :TWndClass;
   Msg :TMsg;

   hButton :HWnd;
   hEdit   :HWnd;
   hEdit2  :HWnd;
   hFont   :HWnd;
   hFont2  :HWnd;

   hMainHandle :HWnd;
   swl_OldProc :Pointer;  // Default Procedure for Subclassing #1 (via SetWindowLong)
   SubMode   :TSubMode;


 procedure Release_Resources;
 begin
   DestroyWindow (hButton);  hButton := 0;
   DestroyWindow (hEdit);    hEdit   := 0;
   DestroyWindow (hEdit2);   hEdit2  := 0;
   DeleteObject  (hFont);    hFont   := 0;
   DeleteObject  (hFont2);   hFont2  := 0;
 end;

 procedure MsgBox (S:String);
 begin
   MessageBox (hMainHandle, PChar(S), 'Information', mb_Ok or mb_IconInformation);
 end;

 procedure Reveal_Text (lParam:LPARAM);
 const
   lf  = #13#10;
   lf2 = lf+lf;
 var
   S :String;
   AnsiTxt :String;
   UnicTxt :String;
   Remarks :Array[1..3] of String;
 begin
   if   IsWindowUnicode(hEdit)
   then Remarks[1] := '    (Man! SetWindowSubclass changed it to Unicode!!)'
   else Remarks[1] := '    (great! as designed)';

   AnsiTxt := PAnsiChar(lParam);

   if  (Length(AnsiTxt) = 1)
   then Remarks[2] := '    (text is obviously truncated)'
   else Remarks[2] := '    (text is healthy and is ANSI, as it should)';

   UnicTxt := PWideChar(lParam);

   if  (Pos('?',UnicTxt) > 0)
   then Remarks[3] := '    (text is obviously garbaged)'
   else Remarks[3] := '    (text is healthy, but I want it to be ANSI)';

   S :=
          'Subclassed using: '
     +lf +'    '+LtSubMode[SubMode]+'()'
     +lf2+ 'IsUnicodeWindow(hEdit)? '
     +lf +'    '+LtBool[IsWindowUnicode(hEdit)]
     +lf +       Remarks[1]
     +lf2+'PAnsiChar(lParam):'
     +lf +'    "'+PAnsiChar(lParam)+'"'
     +lf +       Remarks[2]
     +lf2+ 'PWideChar(lParam):'
     +lf +'    "'+PWideChar(lParam)+'"'
     +lf +       Remarks[3];

   SetWindowText (hEdit2, PChar(S));
 end;

 function swl_EditWndProc (hWnd:HWnd; uMsg:UInt; wParam:WParam; lParam:LParam) :LResult; stdcall;
 begin
   Result := CallWindowProc (swl_OldProc, hWnd, uMsg, wParam, lParam);
   if (uMsg = wm_SetText) then Reveal_Text(lParam);
 end;

 function sws_EditWndProc (hWnd:HWND; uMsg:UINT; wParam:WPARAM; lParam:LPARAM; uIdSubclass:UINT_PTR; dwRefData:DWORD_PTR) :LRESULT; stdcall;
 begin
   Result := DefSubclassProc (hWnd, uMsg, wParam, lParam);
   if (uMsg = wm_SetText) then Reveal_Text(lParam);
 end;

 procedure do_SetWindowSubclass;
 begin
   if   not SetWindowSubclass (hEdit, @sws_EditWndProc, 1, dword_ptr($1234{whatever}))
   then RaiseLastOSError;

   SubMode := subSetWindowSubclass;
 end;

 procedure undo_SetWindowSubclass;
 begin
   if   not RemoveWindowSubclass (hEdit, @sws_EditWndProc, 1)
   then RaiseLastOSError;

   SubMode := subSetWindowLong;  // restored
 end;

 function AppWindowProc (hWnd:HWnd; uMsg:UInt; wParam:WParam; lParam:LParam) :LResult; stdcall;
 begin
   case uMsg of
     wm_Command:
     begin
       if (lParam = hButton) then
       case SubMode of
         subSetWindowLong:
         begin
           do_SetWindowSubclass;  // now using SetWindowSubclass()
           SetWindowText (hEdit,   PChar(strTextUsingPWideChar));
           SetWindowText (hButton, PChar('Switch back to SetWindowLong mode'));
         end;

         subSetWindowSubclass:
         begin
           undo_SetWindowSubclass;  // back to SetWindowLong()
           SetWindowText (hEdit,   PChar(strTextUsingPAnsiChar));
           SetWindowText (hButton, PChar('Switch to SetWindowSubclass mode'));
         end;
       end;
     end;

     wm_Destroy:
     begin
       Release_Resources;
       PostQuitMessage (0);
       Exit;
     end;
   end;

   Result := DefWindowProc (hWnd, uMsg, wParam, lParam);
 end;

 var
   W,H :Integer;

 begin
   wc.hInstance     := hInstance;
   wc.lpszClassName := 'ANSI_Wnd';
   wc.Style         := cs_ParentDC;
   wc.hIcon         := LoadIcon(hInstance,'MAINICON');
   wc.lpfnWndProc   := @AppWindowProc;
   wc.hbrBackground := GetStockObject(white_brush);
   wc.hCursor       := LoadCursor(0,IDC_ARROW);

   RegisterClass(wc);  // ANSI (using Delphi 7, so all Windows API is mapped to ANSI).

   W := 500;
   H := 480;

   hMainHandle := CreateWindow (  // ANSI (using Delphi 7, so all Windows API is mapped to ANSI).
     wc.lpszClassName,'2-Way Subclassing App',
     ws_OverlappedWindow or ws_Caption or ws_MinimizeBox or ws_SysMenu or ws_Visible,
     ((GetSystemMetrics(SM_CXSCREEN)-W) div 2),  //   vertically centered in screen
     ((GetSystemMetrics(SM_CYSCREEN)-H) div 2),  // horizontally centered in screen
     W,H,0,0,hInstance,nil);

   // create the fonts
   hFont := CreateFont (-14,0,0,0,0,0,0,0, default_charset, out_default_precis, clip_default_precis, default_quality, variable_pitch or ff_swiss, 'Tahoma');
   hFont2:= CreateFont (-14,0,0,0,0,0,0,0, default_charset, out_default_precis, clip_default_precis, default_quality, variable_pitch or ff_swiss, 'Courier New');

   // create the edits
   hEdit :=CreateWindowEx (WS_EX_CLIENTEDGE,'EDIT','some text', WS_VISIBLE or WS_CHILD or ES_LEFT or ES_AUTOHSCROLL,                10,35,W-40, 23,hMainHandle,0,hInstance,nil);
   hEdit2:=CreateWindowEx (WS_EX_CLIENTEDGE,'EDIT','details',   WS_VISIBLE or WS_CHILD or ES_LEFT or ES_AUTOHSCROLL or ES_MULTILINE,10,72,W-40,300,hMainHandle,0,hInstance,nil);
   SendMessage(hEdit, WM_SETFONT,hFont, 0);
   SendMessage(hEdit2,WM_SETFONT,hFont2,0);

   // create the button
   hButton:=CreateWindow ('Button','Switch to SetWindowSubclass mode', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 90,H-95,290,32,hMainHandle,0,hInstance,nil);
   SendMessage(hButton,WM_SETFONT,hFont,0);

   // subclass the Edit using the default method.
   swl_OldProc := Pointer(GetWindowLong(hEdit,GWL_WNDPROC));
   SetWindowLong (hEdit,GWL_WNDPROC,Longint(@swl_EditWndProc));

   SubMode := subSetWindowLong;
   SetWindowText (hEdit, PChar(strTextUsingPAnsiChar));

   // message loop
   while GetMessage(Msg,0,0,0) do
   begin
     TranslateMessage(Msg);
     DispatchMessage(Msg);
   end;
 end.

The application has 2 edit controls:

  1. The smaller one, which is subclassed in 2 switchable ways (here called Modes).
  2. The bigger one, like a memo, not subclassed. Just for dumping info.

There is also a button for switching between the modes.

The default subclassing mode uses SetWindowLong() (the classic way):

SetWindowLong mode

In Delphi 2007 and earlier, the main window (and all child controls) are created using the ANSI version of the Win32 API procedures, so the message handler (of the subclassed control) should receive ANSI text (along with the WM_SETTEXT message), always!

The problem is that's not happening when the edit control is subclassed using SetWindowSubclass()! SetWindowSubClass() changes the window from ANSI to UNICODE and it starts receiving Unicode text rather than the expected ANSI text.

Pressing the button subclasses the edit control via SetWindowSubclass():

SetWindowSubclass mode

Pressing the button again subclasses the edit control via SetWindowLong().

Once back to the SetWindowLong() mode, the edit control automatically receives ANSI text again:

SetWindowLong mode

Just run the application and try switching between modes. Look carefully at the detailed info shown in the bigger edit control.

Just to be clear: I think this is a Microsoft bug. However, in case it's a "feature", could someone lead me to the respective documentation? I could not find it anywhere.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    Are you calling `SetWindowSubClassA` or `SetWindowSubClassW` ? – Jonathan Potter Aug 22 '16 at 03:23
  • @Jonathan Potter: As I mentioned, Delphi 7 always maps to the ANSI version of the API. I also tested with explicit calls to all API that comes with A/W flavors. `SetWindowSubClassA` I didn't manage to link. Does it exist? I got a compile error when trying to link to it. – Paulo França Lacerda Aug 22 '16 at 03:30
  • 2
    @JonathanPotter: I don't think there are A and W versions. The documentation doesn't mention them, and the function doesn't take a string, so there's no obvious reason why there would be. – Harry Johnston Aug 22 '16 at 03:35
  • Waiting for a good soul willing to try it out in another language. :) – Paulo França Lacerda Aug 22 '16 at 03:40
  • Runtime error trying to ![link to SetWindowSubClassA](http://imgh.us/EntryPointNotFound.png) – Paulo França Lacerda Aug 22 '16 at 03:43
  • I believe there's no SetWindowSubClassA/SetWindowSubClassW variations, because they do not appear anywhere on the web. – Paulo França Lacerda Aug 22 '16 at 03:44
  • Just compiled with Delphi 2005/2006/2007 (all 3 are ANSI-based like D7), and got the same results. – Paulo França Lacerda Aug 22 '16 at 03:55
  • @PauloFrançaLacerda, I'm not sure what you are trying to do exactly, but you could try the TNT Unicode controls, instead of the D7 ANSI VCL. Moving to Unicode is the way to go IMO. – kobik Aug 22 '16 at 08:32
  • @kobik: TNT (awesome component suite) is for applications. My goal is not an app, but a pure WinApi framework. I'm creating my own (VCL-free) Visual Framework, just for fun, with no hush. It supports Unicode when compiled with Delphi 2009+ without any hack (just compiler native unicode support). The reason for this topic is that I got surprised with the ComCtl32 (now proven) "feature", just it. Thanks for the suggestion. :) – Paulo França Lacerda Aug 22 '16 at 08:51

1 Answers1

11

According to MSDN:

Subclassing Controls Using ComCtl32.dll version 6

Note ComCtl32.dll version 6 is Unicode only. The common controls supported by ComCtl32.dll version 6 should not be subclassed (or superclassed) with ANSI window procedures.

...

Note All strings passed to the procedure are Unicode strings even if Unicode is not specified as a preprocessor definition.

So it seems this is as designed.

comctl32.dll in my c:\windows\syswow64 folder is version 6.1.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
  • Man! You got it. Very clear answer! I did read so many articles out there that I ended up missing that one you pointed out. I think I'll have to stick with the classic SetWindowLong-way of subclassing my ANSI windows. :) – Paulo França Lacerda Aug 22 '16 at 05:09
  • I'm afraid so.I didn't find any alternative either. – Lieven Keersmaekers Aug 22 '16 at 05:23
  • This is good info to know, thanks. But does it really matter if a subclassed Ansi window receives Unicode messages? You know they will be in Unicode, so you could just convert them from Unicode to Ansi if you need to work with them as Ansi. But why not work with them as Unicode? And BTW, your app won't use ComCtrl v6 unless it is explicitly manifested for it (to enable visual styles, for instance). – Remy Lebeau Aug 22 '16 at 20:22
  • @RemyLebeau: `"..just convert them from Unicode to Ansi.."` Hmmm.. Unicode to Ansi conversion can be very lossy. – Paulo França Lacerda Aug 23 '16 at 00:48
  • @RemyLebeau: `"why not work with..Unicode?"` My app, actually, is (going to be) a framework, not a final app. I want it to rely on compiler's native support for Unicode, so if the user needs Unicode, he'll have to use a Unicode-ready compiler, such as Delphi 2009+. – Paulo França Lacerda Aug 23 '16 at 00:50
  • @RemyLebeau: `"..your app won't use ComCtrl v6 unless..manifested."` Yes, but it's up to the user (app developer) decide if he wants his app manifest'ed. All I need (as framework developer) is ensure the user's choice won't break my code. – Paulo França Lacerda Aug 23 '16 at 00:51
  • @PauloFrançaLacerda: my point is, if your framework is doing the subclassing with `SetWindowSubClass()` then your framework knows that the messages it will receive will be Unicode, so it can handle them accordingly. If your framework needs to give the message data to the rest of the app, it can translate the data to Ansi in pre-D2009 versions as needed. – Remy Lebeau Aug 23 '16 at 00:57
  • @PauloFrançaLacerda: I just noticed that `SetWindowSubClass()` is available in ComCtrl 5.8 and later, so you would have to detect the ComCtrl version actually being used at runtime (via [`DllGetVersion()`](https://msdn.microsoft.com/en-us/library/windows/desktop/bb776404.aspx)) and adjust your subclass processing accordingly. – Remy Lebeau Aug 23 '16 at 00:58
  • @RemyLebeau: Your suggestion is very logical and doable. I just prefer to avoid adding a new layer of complexity to my framework. :) – Paulo França Lacerda Aug 23 '16 at 01:07