View Issue Details

IDProjectCategoryView StatusLast Update
0037982LazarusLCLpublic2020-12-07 14:25
ReporterZdravko Gabrovski Assigned ToJuha Manninen  
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
PlatformWindows 32/64OSWindows  
Product Version2.1 (SVN) 
Summary0037982: Combobox with csOwnerDrawFixed style does not work in DLL library under Windows
DescriptionThe following code in OnDrawItemEvent does not work when the combobox is placed on the form in DLL library under Windows LCL.
The same works fine in the host application and under Ubuntu gtk2 library.
Please, find pictures and sample project attached.

procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer;
  ARect: TRect; State: TOwnerDrawState);
var
  S: String;
  i: Integer;
begin
  with TCombobox(Control) do begin

    if odSelected in State then
      Canvas.Brush.Color := clHighLight
    else
      Canvas.Brush.Color:=clWindow;

    Canvas.FillRect(aRect);
    Canvas.TextRect(aRect, aRect.Left + 17, aRect.Top+ 3, Items[Index]);

    aRect.Right := aRect.Left + 16;

    Canvas.Brush.Color := clWhite;
    Canvas.FillRect(aRect);

    S := index.ToString;
    Canvas.Font.Color := clWindowText;
    Canvas.TextRect(aRect, aRect.Left+1, aRect.Top+3, S);

  end;

end
Steps To ReproduceJust unzip attached sample projects.
It contains two projects: HostApp and DllApp.
Load and compile.
Start the host app.
Click combobox drop down in the host app and you will see - works fine.
Than
Click the "Test me" button.
This will load dll library and will open test form in the libray.
Click combobox drop down in the dll library form and you will see - the combobox will drops down, but there is nothing in the area.
if you set the breakpoint to the "OnDrawEvent" it will stop, but does not draw anything.



Additional InformationOnly inder Windows 32/64
TagsNo tags attached.
Fixed in Revisionr64179
LazTarget-
Widgetset
Attached Files

Relationships

related to 0033787 new Can not create TStatusBar in dll 
related to 0034099 new TPageControl has wrong background color when created in dll 
related to 0032578 new Form's close buton does not work when form is in DLL 

Activities

Zdravko Gabrovski

2020-10-25 07:15

reporter  

UbuntuGTK2.png (53,575 bytes)   
UbuntuGTK2.png (53,575 bytes)   
Win64InDll.png (42,294 bytes)   
Win64InDll.png (42,294 bytes)   
Win64inTheHost.png (34,079 bytes)   
Win64inTheHost.png (34,079 bytes)   

Zdravko Gabrovski

2020-10-25 12:04

reporter   ~0126541

Remark: The issue appears only when the form in host application and the form in dll both contains the same controls (Combobxes or listbox) - the same problem.
In the given example if combobox from host app removed it is ok.
If the host application is not lazarus application, there is no any problems.

Zdravko Gabrovski

2020-10-25 12:34

reporter   ~0126542

More info: If I set Host app combobox property "Visible" to false in design time, it starts to work in dll form.
But if a set progmaticaly to false just before open the form in dll, it does not affect and the problem persists.

I think it is related some-how with windows messages loop when both host and dll are written in Lazarus.

Zdravko Gabrovski

2020-10-25 12:51

reporter   ~0126543

Update: issue also with "OnExit" combobox event in dll form.
it wil not fire this event if parent host app form contains combobox control.
please find new example attached, just try to leave the combobox field in dll form with mouse or "tab" key and you will not see ShowMessage dialog control because it will not fire OnExit event.
Please find attached new sample and small video that demonstrates the problem.

Zdravko Gabrovski

2020-11-16 19:27

reporter   ~0126986

I found a reason and fix it.
The problem: The problem comes again from the Windows (the "great" OS).
All those three type of controls: TComboBox; TListBox and TChecklistBox (which causes a problems) using a FinishCreateWindow procedure in Win32WSControls.pp unit, line 177.
The problem comes from line 0000198:
          if not GetClassInfoW(System.HInstance, PWideChar(WideString(pSubClassName)), @DummyClassW) then .....
And how it happens:
if in the parent application you have in your active screen form some of TComboBox; TListBox or TChecklistBox, the call of GetClassInfoW at line 198 will return FALSE and it will register a sub-class with a name of 'LCLComboBox', 'LCLListBox' or 'LCLCheckListBox' with Windows.RegisterClassW(@WindowClassW); at line 206 .

then, When you call a form into LCL DLL unit that have the same (TComboBox; TListBox or TChecklistBox) components over it, it will again call FinishCreateWindow, but in that moment Although hinstanse parameter is different that the HInstanca of the caller application, stupid Windows returns, that the subclass with the same name is allready registered. But unfortunately, this is not a right sublass, ,this is the subclass of the parent (host) LCL application.
In that case, this will not register a SubClassWndProc, which explains why all the messages(WM_REPAINT, WM_KILLFOCUS and so on) was not handled from the controls in a dll.

The fix:
I defined a three new subclass names in Win32Int.pp:
  LCLComboboxClsNameDLL: array[0..14] of char = 'LCLComboBoxDLL'#0;
  LCLListboxClsNameDLL: array[0..13] of char = 'LCLListBoxDLL'#0;
  LCLCheckListboxClsNameDLL: array[0..18] of char = 'LCLCheckListBoxDLL'#0;

I did a small check inside Win32WSStdCtrls and Win32WSCheckLst:

in class function TWin32WSCustomComboBox.CreateHandle(const AWinControl: TWinControl;

replaced
      pSubClassName := LCLComboboxClsName
with
    if IsLibrary then
      pSubClassName := LCLComboboxClsNameDLL
    else
      pSubClassName := LCLComboboxClsName;


inside class function TWin32WSCustomListBox.CreateHandle(const AWinControl: TWinControl;

replaced

pSubClassName := LCLListboxClsName

with
    if IsLibrary then
      pSubClassName := LCLListboxClsNameDLL
    else
      pSubClassName := LCLListboxClsName;

and finally in class function TWin32WSCustomCheckListBox.CreateHandle(
  const AWinControl: TWinControl; const AParams: TCreateParams): TLCLIntfHandle

replaced
pSubClassName := LCLCheckListboxClsName
with
    if IsLibrary then
      pSubClassName := LCLCheckListboxClsNameDLL
    else
      pSubClassName := LCLCheckListboxClsName;


Now, everything seems to be OK.
win32wsstdctrls.diff (864 bytes)   
Index: lcl/interfaces/win32/win32wsstdctrls.pp
===================================================================
--- lcl/interfaces/win32/win32wsstdctrls.pp	(revision 64142)
+++ lcl/interfaces/win32/win32wsstdctrls.pp	(working copy)
@@ -802,7 +802,10 @@
   with Params do
   begin
     pClassName := ListBoxClsName;
-    pSubClassName := LCLListboxClsName;
+    if IsLibrary then
+      pSubClassName := LCLListboxClsNameDLL
+    else
+      pSubClassName := LCLListboxClsName;
     SubClassWndProc := @ListBoxWindowProc;
   end;
   // create window
@@ -1021,7 +1024,10 @@
   with Params do
   begin
     pClassName := ComboboxClsName;
-    pSubClassName := LCLComboboxClsName;
+    if IsLibrary then
+      pSubClassName := LCLComboboxClsNameDLL
+    else
+      pSubClassName := LCLComboboxClsName;
     SubClassWndProc := @ComboBoxWindowProc;
   end;
 
win32wsstdctrls.diff (864 bytes)   
win32wschecklst.diff (574 bytes)   
Index: lcl/interfaces/win32/win32wschecklst.pp
===================================================================
--- lcl/interfaces/win32/win32wschecklst.pp	(revision 64142)
+++ lcl/interfaces/win32/win32wschecklst.pp	(working copy)
@@ -157,7 +157,10 @@
   with Params do
   begin
     pClassName := ListBoxClsName;
-    pSubClassName := LCLCheckListboxClsName;
+    if IsLibrary then
+      pSubClassName := LCLCheckListboxClsNameDLL
+    else
+      pSubClassName := LCLCheckListboxClsName;
     SubClassWndProc := @CheckListBoxWndProc;
   end;
   // create window
win32wschecklst.diff (574 bytes)   
win32int.diff (824 bytes)   
Index: lcl/interfaces/win32/win32int.pp
===================================================================
--- lcl/interfaces/win32/win32int.pp	(revision 64142)
+++ lcl/interfaces/win32/win32int.pp	(working copy)
@@ -239,6 +239,9 @@
   LCLComboboxClsName: array[0..11] of char = 'LCLComboBox'#0;
   LCLListboxClsName: array[0..10] of char = 'LCLListBox'#0;
   LCLCheckListboxClsName: array[0..15] of char = 'LCLCheckListBox'#0;
+  LCLComboboxClsNameDLL: array[0..14] of char = 'LCLComboBoxDLL'#0;
+  LCLListboxClsNameDLL: array[0..13] of char = 'LCLListBoxDLL'#0;
+  LCLCheckListboxClsNameDLL: array[0..18] of char = 'LCLCheckListBoxDLL'#0;
   ClsNameW: array[0..6] of WideChar = ('W', 'i', 'n', 'd', 'o', 'w', #0);
   ClsHintNameW: array[0..10] of WideChar = ('H', 'i', 'n', 't', 'W', 'i', 'n', 'd', 'o', 'w', #0);
 
win32int.diff (824 bytes)   

Anton Kavalenka

2020-11-16 20:14

reporter   ~0126987

Is not the better way to check if window class not registered, then register it?

Zdravko Gabrovski

2020-11-16 22:14

reporter   ~0126988

It is this check that gives the wrong result.
Windows returns that the class is registered when the function is called in DLL, although it is not registered in it. It is registered in the host app. But Windows threat host and dll as a same app instance, although they have dihherent hinstance.
The way to avoid this is to change sub class name in DLL. In that case it works fine.

Serge Anvarov

2020-11-17 20:29

reporter   ~0127015

> Windows returns that the class is registered when the function is called in DLL, although it is not registered in it.
I made a simple project.
In Windows 7 x64, this all works as it should, i.e. the dll reports that the class is not registered, even if it is registered in the host application.
uSimpleClassReg.pp (870 bytes)   
unit uSimpleClassReg;

{$MODE OBJFPC}
{$LONGSTRINGS ON}

interface

uses Windows;

function IsClassRegistered(Instance: HINST): Boolean;
function RegisterClass(Instance: HINST): Boolean;
function UnRegisterClass(Instance: HINST): Boolean;

implementation

const
  TestClassName = 'ClassName1';

function IsClassRegistered(Instance: HINST): Boolean;
var
  R: TWNDCLASSW;
begin
  Result := GetClassInfoW(Instance, TestClassName, @R);
end;

function RegisterClass(Instance: HINST): Boolean;
var
  R: TWNDCLASSW;
begin
  ZeroMemory(@R, SizeOf(R));
  R.hInstance := Instance;
  R.lpfnWndProc := @DefWindowProcW;
  R.lpszClassName := TestClassName;
  Result := Windows.RegisterClassW(@R) <> 0;
end;

function UnRegisterClass(Instance: HINST): Boolean;
begin
  Result := Windows.UnregisterClassW(TestClassName, Instance);
end;

end.

uSimpleClassReg.pp (870 bytes)   
project1Dll.lpr (429 bytes)   
library project1Dll;

{$MODE OBJFPC}

uses uSimpleClassReg;

function DllIsRegistered: Boolean; stdcall;
begin
  Result := IsClassRegistered(HInstance);
end;

function DllRegister: Boolean; stdcall;
begin
  Result := RegisterClass(HInstance);
end;

function DllUnRegister: Boolean; stdcall;
begin
  Result := UnRegisterClass(HInstance);
end;

exports
  DllIsRegistered, DllRegister, DllUnRegister;
end.

project1Dll.lpr (429 bytes)   
project1Exe.lpr (1,039 bytes)   
program project1Exe;

{$MODE OBJFPC}
{$LONGSTRINGS ON}
{$APPTYPE CONSOLE}

uses uSimpleClassReg;

function DllIsRegistered: Boolean; stdcall; external 'project1Dll.dll';
function DllRegister: Boolean; stdcall; external 'project1Dll.dll';
function DllUnRegister: Boolean; stdcall; external 'project1Dll.dll';

begin
  Writeln('Is registered in EXE = ', IsClassRegistered(HInstance));
  Writeln('Regiser in EXE result = ', RegisterClass(HInstance));
  Writeln('Is registered in EXE = ', IsClassRegistered(HInstance));
  Writeln('Is registered in DLL = ', DllIsRegistered);
  Writeln('Regiser in DLL result = ', DllRegister);
  Writeln('Is registered in DLL = ', DllIsRegistered);
  Writeln('UnRegiser in DLL result = ', DllUnRegister);
  Writeln('Is registered in DLL = ', DllIsRegistered);
  Writeln('Is registered in EXE = ', IsClassRegistered(HInstance));
  Writeln('UnRegiser in EXE result = ', UnRegisterClass(HInstance));
  Writeln('Is registered in EXE = ', IsClassRegistered(HInstance));
  Readln;
end.


project1Exe.lpr (1,039 bytes)   

Zdravko Gabrovski

2020-11-17 21:50

reporter   ~0127019

Last edited: 2020-11-17 22:02

View 3 revisions

You are right for the case ypu developed, but it is not a same case that I report as a bug.
You made a console application (you miss all LCL initialization lifecycle in host application).
Your dll library miss dependency from the LCL and initialization of application object inside DLL library.
I modify your project and after add the missing dependencies, change host app from console to standard windows application, add a ne method in DLL that initialize application object inside DLL.
And the bug appear again (Windows 10 x64, Windows 7 X64).
Here you are.
Parallels Picture.png (9,938 bytes)   
Parallels Picture.png (9,938 bytes)   
DllApp.7z (1,284 bytes)
HostApp.7z (53,256 bytes)

Serge Anvarov

2020-11-18 16:02

reporter   ~0127033

I change Unit1 so that it can be compiled in FPC 3.2.0 as well. Lazarus 2.0.10 or Lazarus 2.1.0 revision 63735 - result is OK, i.e. like console App.
Windows 7, x64.
unit1.pp (1,771 bytes)   
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

uses uSimpleClassReg;

function DllInit: Boolean; stdcall; external 'project1Dll.dll';
function DllIsRegistered: Boolean; stdcall; external 'project1Dll.dll';
function DllRegister: Boolean; stdcall; external 'project1Dll.dll';
function DllUnRegister: Boolean; stdcall; external 'project1Dll.dll';

procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;
begin
  Memo1.Clear;
  WriteStr(S, 'Dll Init result = ', DllInit);
  Memo1.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'Regiser in EXE result = ', RegisterClass(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
  Memo1.Append(S);
  WriteStr(S, 'Regiser in DLL result = ', DllRegister);
  Memo1.Append(S);
  WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
  Memo1.Append(S);
  WriteStr(S, 'UnRegiser in DLL result = ', DllUnRegister);
  Memo1.Append(S);
  WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
  Memo1.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'UnRegiser in EXE result = ', UnRegisterClass(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Memo1.Append(S);
end;


end.
unit1.pp (1,771 bytes)   
Screen.jpg (29,907 bytes)   
Screen.jpg (29,907 bytes)   

Zdravko Gabrovski

2020-11-18 16:50

reporter   ~0127034

My friend,
Your test is right, but again is different than the issue I reported.
I talking for a PLUGIN library, which is loaded dynamically with SafeLoadLibrary.
Plugin library can not be linked like you propose - plugin must be load/unload dynamically.

Please, do your test on this way (put my code over your button click handler):
Type
    TDllFunc = function : Boolean; stdcall;

procedure TForm1.Button1Click(Sender: TObject);
Var
  DllIsRegistered : TDllFunc;
  DllRegister : TDllFunc;
  DllUnRegister : TDllFunc;
  DllInit : TDllFunc;
  LIbHandle : TLibHandle;

begin
  LIbHandle := SafeLoadLibrary( 'project1Dll.dll' );
  if LIbHandle<>0 then begin
    DllIsRegistered := TDllFunc( GetProcAddress( LIbHandle, 'DllIsRegistered'));
    DllRegister := TDllFunc(GetProcAddress( LIbHandle, 'DllRegister'));
    DllUnRegister := TDllFunc(GetProcAddress( LIbHandle, 'DllUnRegister'));
    DllInit := TDllFunc(GetProcAddress( LIbHandle, 'DllInit'));
    if Assigned(DllInit) and Assigned(DllIsRegistered) and Assigned(DllRegister) and Assigned(DllUnRegister) then begin
      Memo1.Lines.Clear;
      Memo1.Lines.Add('Dll Init = '+ ifthen(Boolean(DllInit), 'True','False'));
      Memo1.Lines.Add('Is registered in EXE = '+ ifthen(IsClassRegistered(HInstance), 'True','False'));
      Memo1.Lines.Add('Regiser in EXE result = '+ ifthen(RegisterClass(HInstance), 'True','False'));
      Memo1.Lines.Add('Is registered in EXE = '+ ifthen(IsClassRegistered(HInstance), 'True','False'));
      Memo1.Lines.Add('Is registered in DLL = '+ ifthen(Boolean(DllIsRegistered), 'True','False'));
      Memo1.Lines.Add('Regiser in DLL result = '+ ifthen(Boolean(DllRegister), 'True','False'));
      Memo1.Lines.Add('Is registered in DLL = '+ ifthen(Boolean(DllIsRegistered), 'True','False'));
      Memo1.Lines.Add('UnRegiser in DLL result = '+ ifthen(Boolean(DllUnRegister), 'True','False'));
      Memo1.Lines.Add('Is registered in DLL = '+ ifthen(Boolean(DllIsRegistered), 'True','False'));
      Memo1.Lines.Add('Is registered in EXE = '+ ifthen(IsClassRegistered(HInstance), 'True','False'));
      Memo1.Lines.Add('UnRegiser in EXE result = '+ ifthen(UnRegisterClass(HInstance), 'True','False'));
      Memo1.Lines.Add('Is registered in EXE = '+ ifthen(IsClassRegistered(HInstance), 'True','False'));
      end
    else
      ShowMessage('Get Proc Address failed!');
    end
  else
    begin
    ShowMessage('Unable to load library!');
    end;

end;

Zdravko Gabrovski

2020-11-18 16:53

reporter   ~0127035

And the new files in samle host project
umain.lfm (623 bytes)   
object Form1: TForm1
  Left = 601
  Height = 449
  Top = 287
  Width = 707
  Caption = 'Form1'
  ClientHeight = 449
  ClientWidth = 707
  LCLVersion = '2.1.0.0'
  object Button1: TButton
    Left = 63
    Height = 25
    Top = 31
    Width = 75
    Caption = 'Button1'
    OnClick = Button1Click
    TabOrder = 0
  end
  object Memo1: TMemo
    Left = 77
    Height = 286
    Top = 138
    Width = 595
    TabOrder = 1
  end
  object Button2: TButton
    Left = 144
    Height = 25
    Top = 31
    Width = 75
    Caption = 'Button2'
    OnClick = Button2Click
    TabOrder = 2
  end
end
umain.lfm (623 bytes)   
umain.pas (4,038 bytes)   
unit uMain;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, StrUtils;

Type
    TDllFunc = function : Boolean; stdcall;

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private

  public

  end;


var
  Form1: TForm1;



implementation

{$R *.lfm}
uses
  uSimpleClassReg;
{ TForm1 }

function DllInit: Boolean; stdcall; external 'project1Dll.dll';
function DllIsRegistered: Boolean; stdcall; external 'project1Dll.dll';
function DllRegister: Boolean; stdcall; external 'project1Dll.dll';
function DllUnRegister: Boolean; stdcall; external 'project1Dll.dll';

procedure TForm1.Button1Click(Sender: TObject);


Var
  DllIsRegistered : TDllFunc;
  DllRegister : TDllFunc;
  DllUnRegister : TDllFunc;
  DllInit : TDllFunc;
  LIbHandle : TLibHandle;

begin
  LIbHandle := SafeLoadLibrary( 'project1Dll.dll' );
  if LIbHandle<>0 then begin
    DllIsRegistered := TDllFunc( GetProcAddress( LIbHandle, 'DllIsRegistered'));
    DllRegister := TDllFunc(GetProcAddress( LIbHandle, 'DllRegister'));
    DllUnRegister := TDllFunc(GetProcAddress( LIbHandle, 'DllUnRegister'));
    DllInit := TDllFunc(GetProcAddress( LIbHandle, 'DllInit'));
    if Assigned(DllInit) and Assigned(DllIsRegistered) and Assigned(DllRegister) and Assigned(DllUnRegister) then begin
      Memo1.Lines.Clear;
      Memo1.Lines.Add('Dll Init = '+ ifthen(Boolean(DllInit), 'True','False'));
      Memo1.Lines.Add('Is registered in EXE = '+ ifthen(IsClassRegistered(HInstance), 'True','False'));
      Memo1.Lines.Add('Regiser in EXE result = '+ ifthen(RegisterClass(HInstance), 'True','False'));
      Memo1.Lines.Add('Is registered in EXE = '+ ifthen(IsClassRegistered(HInstance), 'True','False'));
      Memo1.Lines.Add('Is registered in DLL = '+ ifthen(Boolean(DllIsRegistered), 'True','False'));
      Memo1.Lines.Add('Regiser in DLL result = '+ ifthen(Boolean(DllRegister), 'True','False'));
      Memo1.Lines.Add('Is registered in DLL = '+ ifthen(Boolean(DllIsRegistered), 'True','False'));
      Memo1.Lines.Add('UnRegiser in DLL result = '+ ifthen(Boolean(DllUnRegister), 'True','False'));
      Memo1.Lines.Add('Is registered in DLL = '+ ifthen(Boolean(DllIsRegistered), 'True','False'));
      Memo1.Lines.Add('Is registered in EXE = '+ ifthen(IsClassRegistered(HInstance), 'True','False'));
      Memo1.Lines.Add('UnRegiser in EXE result = '+ ifthen(UnRegisterClass(HInstance), 'True','False'));
      Memo1.Lines.Add('Is registered in EXE = '+ ifthen(IsClassRegistered(HInstance), 'True','False'));
      end
    else
      ShowMessage('Get Proc Address failed!');
    end
  else
    begin
    ShowMessage('Unable to load library!');
    end;

end;

procedure TForm1.Button2Click(Sender: TObject);
var
  S: string;
begin
  Memo1.Clear;
  WriteStr(S, 'Dll Init result = ', DllInit);
  Memo1.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'Regiser in EXE result = ', RegisterClass(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
  Memo1.Append(S);
  WriteStr(S, 'Regiser in DLL result = ', DllRegister);
  Memo1.Append(S);
  WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
  Memo1.Append(S);
  WriteStr(S, 'UnRegiser in DLL result = ', DllUnRegister);
  Memo1.Append(S);
  WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
  Memo1.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'UnRegiser in EXE result = ', UnRegisterClass(HInstance));
  Memo1.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Memo1.Append(S);
end;

end.

umain.pas (4,038 bytes)   

Serge Anvarov

2020-11-21 13:55

reporter   ~0127081

Equally.
unit1-2.pp (3,033 bytes)   
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

uses uSimpleClassReg;

type
  TDllFunc = function: Boolean; stdcall;

procedure MakeReport(Dest: TMemo;
  DllInit, DllIsRegistered, DllRegister, DllUnRegister: TDllFunc);
var
  S: string;
begin
  Dest.Clear;
  WriteStr(S, 'Dll Init result = ', DllInit);
  Dest.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Dest.Append(S);
  WriteStr(S, 'Regiser in EXE result = ', RegisterClass(HInstance));
  Dest.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Dest.Append(S);
  WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
  Dest.Append(S);
  WriteStr(S, 'Regiser in DLL result = ', DllRegister);
  Dest.Append(S);
  WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
  Dest.Append(S);
  WriteStr(S, 'UnRegiser in DLL result = ', DllUnRegister);
  Dest.Append(S);
  WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
  Dest.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Dest.Append(S);
  WriteStr(S, 'UnRegiser in EXE result = ', UnRegisterClass(HInstance));
  Dest.Append(S);
  WriteStr(S, 'Is registered in EXE = ', IsClassRegistered(HInstance));
  Dest.Append(S);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  DllIsRegistered: TDllFunc;
  DllRegister: TDllFunc;
  DllUnRegister: TDllFunc;
  DllInit: TDllFunc;
  LibHandle: TLibHandle;
begin
  LibHandle := SafeLoadLibrary('project1Dll.dll');
  if LibHandle <> 0 then
  try
    DllIsRegistered := TDllFunc(GetProcAddress(LibHandle, 'DllIsRegistered'));
    DllRegister := TDllFunc(GetProcAddress(LibHandle, 'DllRegister'));
    DllUnRegister := TDllFunc(GetProcAddress(LibHandle, 'DllUnRegister'));
    DllInit := TDllFunc(GetProcAddress(LibHandle, 'DllInit'));
    if Assigned(DllInit) and Assigned(DllIsRegistered) and
      Assigned(DllRegister) and Assigned(DllUnRegister) then
    begin
      MakeReport(Memo1, DllInit, DllIsRegistered, DllRegister, DllUnRegister);
    end
    else
      ShowMessage('Get Proc Address failed!');
  finally
    UnloadLibrary(LibHandle);
  end
  else
    ShowMessage('Unable to load library!');
end;

function DllInit: Boolean; stdcall; external 'project1Dll.dll';
function DllIsRegistered: Boolean; stdcall; external 'project1Dll.dll';
function DllRegister: Boolean; stdcall; external 'project1Dll.dll';
function DllUnRegister: Boolean; stdcall; external 'project1Dll.dll';

procedure TForm1.Button2Click(Sender: TObject);
begin
  MakeReport(Memo1, @DllInit, @DllIsRegistered, @DllRegister, @DllUnRegister);
end;

end.
unit1-2.pp (3,033 bytes)   
Screen-2.jpg (31,533 bytes)   
Screen-2.jpg (31,533 bytes)   

Zdravko Gabrovski

2020-11-22 10:26

reporter   ~0127092

There is something wrong.
In the way you call the function
WriteStr(S, 'Is registered in DLL = ', DllIsRegistered);
It will NOT call the function in DLL.
You can try to put a breakpoint in DLL in function DllIsRegistered and the breakpoint is never reached.

So, please, use the following code, which causes to call a function in DLL
      B := Boolean( DllIsRegistered );
      WriteStr(S, 'Is registered in DLL = ', B );
OR
      WriteStr(S, 'Is registered in DLL = ', Boolean( DllIsRegistered ) );

In that case the breakoint in DLL is reached.
and the result is TRUE.

I am sorry my friend but I am right - there is a problem in LCL with registerclass id DLL.

Serge Anvarov

2020-11-22 13:13

reporter   ~0127095

I tried with changing the way you write:
[code]
B := DllIsRegistered();
WriteStr(S, 'Is registered in DLL = ', B);
Dest.Append(S);
[/code] and so on.
Result not changed - first "Is registered in DLL" = FALSE

And, by the way, the code could not fail to reach the DLL, this is shown by the results of the function.
What version of Windows do you have?

Zdravko Gabrovski

2020-11-22 23:18

reporter   ~0127126

7, 10 64 bit - result is the same.
In your way to function call there is not problem.
In my way - there is.
You are using
B := DllIsRegistered();
I am using
B := Boolean( DllIsRegistered );

In both cases reach break point in dll
in your case result is False in my case - it is true.
Any ideas?

jamie philbrook

2020-11-23 00:41

reporter   ~0127127

Last edited: 2020-11-23 00:54

View 2 revisions

Please try to examine the Hinstance of the returned WNDClass if it finds a class already created..

if the Hinstance isn't the same as the DLL HInstance then alter the code to force it to make an attempt to create another class but it will have now the DLL Hinstance.

EDIT:
 Looking at MS Docs, it states that all classes registered in a DLL must be unregistered from the DLL before it exits. The OS will not automatically unregister any classes registered in a DLL, the user code must do it.

As for the host app, the OS will unregister those within the host without the call to the API.

So keep this in mind.

Zdravko Gabrovski

2020-11-23 10:38

reporter   ~0127135

Last edited: 2020-11-23 10:50

View 2 revisions

I examined it, the result is that HInstance=dll hinstance.
I try then following: ( in FinishCreateWindow, Win32WSControls.pp)
......
          B := GetClassInfoW(System.HInstance, PWideChar( WideString(pSubClassName) ), @DummyClassW) ; // return TRUE, DummyClassW.hInstance=dll hInstance.
          if B then
            B:=Windows.UnregisterClassW(PWideChar( WideString(pSubClassName) ), System.HInstance); // Here return false, and the error code=1412.
          Err := Windows.GetLastError; // return 1412
          B := GetClassInfoW(System.HInstance, PWideChar( WideString(pSubClassName) ), @DummyClassW) ; // still retunr TRUE, the class registerredm but it is not registerred in the DLL
          if not B then
          begin
            with WindowClassW do .....

And I received err 1412 = 1412 (0x584)

Class still has open windows.

The only open window in that moment is parent window in the host application.
Any ideas???

Zdravko Gabrovski

2020-11-23 10:47

reporter   ~0127136

I keep insisting that the solution to the problem is to change the class name in Dll.

Serge Anvarov

2020-11-23 17:00

reporter   ~0127141

I'm not sure why. As you can see, in my case, registration in the DLL and EXE are independent. Although the combobox example shows the same bug.

jamie philbrook

2020-11-23 23:31

reporter   ~0127147

if checking for the Class returns a class already created with the DLL instance handle then I would say its has already been registered and you are doing something wrong.

You should not get a return back indicating that it's already registered with the current DLL instance handle, that can only happen somewhere if a Constructor was called already and what is happening is you are creating another instance of the control which isn't attached to the messages.

 I can see this happening if for example you used TFORM1.Create instead of TFORm1.CreateNew or even use the TApplication way of doing it, both will call the "Create" and not the CreateNew.

 The difference is that "Create" goes out and reads the resource file and gathers all the events that are actually allocated for the Host app.


 The fact that you are receiving data indicating that the class is already registered tells me that this is happening.. How else do you think the class would already be registered to the DLL module handle otherwise ?

 This is only an assumption on my part but it sounds plausible.

Zdravko Gabrovski

2020-11-24 10:10

reporter   ~0127153

You sound realistically. But I search all the code inside lcl/interfacace/win32, and the only call of this class registration is at that place.
Also, if I removed a TComboBox in a host application form, it will return FALSE. If you are right, there will be no matter if there is or there is not TComboBox in a host application form active.
I still think there is a bug inside Windows API and they thread DLL Hinstance as a host app hinstance.
I had similar problem with a dll library load/unload in host and my dll. For example:
If I load a library (open sll in my case) in host application and after that try to load the same library in my DLL, it will not load, it use the dll loaded from host application. And When I unloaded it from DLL, it fucks opensll in host app.

https://www.atozed.com/forums/thread-1936.html

jamie philbrook

2020-11-25 01:23

reporter   ~0127167

Last edited: 2020-11-25 02:13

View 2 revisions

I just spent some time building a host test app and a DLL to go with it..

I have a button and Combobox on the form of the Host app...

In the DLL, I also have a Combobox on that too, both have contents within them..

I defined the OnChange event of the Combobox in the DLL and that works.

 I defined the OnClick event of the mouse for the Form in the DLL and that works..

EDIT:
 It appears during PAINT session the WM_DRAWITEM is not going to the correct window it appears for the Drop down window which has a different handle.

 I don't believe this has anything to do with class names, its just an effect to get around an issue not properly covered so using one hack to fix what is still a bug somewhere for DLL use is a bad idea.
this needs to be researched more for the List box which is where the issue is.


 the events for the combobox seem to be working just fine as does the host app too..

I loaded the DLL at runtime with a button instead of the static method because for some strange reason the host app bombs when trying to load it .. I haven't yet figured that one out.

But the dynamic load at will seems to work..

This is the 64 bit target I did this on.
I can post the two projects here if anyone is interested.

Zdravko Gabrovski

2020-11-25 12:37

reporter   ~0127174

@Jamie
Please test the OnExit event both on list and combobox.
In my DLL combobox I not receive "OnExit".
If yoy are can not register class you will not register WndProc for this ComboBox.
And the Combobox will not receive message (for examle WM_KILLFOCUS which must trigger "OnExit" ).
The same for ListBoxControl.

jamie philbrook

2020-12-05 00:16

reporter   ~0127349

Last edited: 2020-12-05 00:21

View 2 revisions

I have been poking around the "Win32WSconctrols" in the "FinishCreateWindow(……..) procedure..

This appears to be where the trouble starts. If the Host App first registers the LCLComboBox which is a Superclass built on top of the ComboBox, when the DLL then checks for the same superclass already defined it skips any setting of the "LPfnWindProc" for the superclass because it has already been defined in the Host app.

  Further down the Window for the control is created and the subclassWndProc is set however, that is only for the window so the inherited window procedure is still the host app.

 This works the other wall round too if the DLL first registers the class then the HOST app, the HOST app then suffers ..

  You can fix this by fixing the inherited window procedure but there is a larger problem so that idea shouldn't even be considered.

 DLL's need to release the Registered classes it registers during the Detach process event. also, this causes a mix up between DLLs and Host apps that may not have the same exact version classes.. , Same problem Delphi has with dynamic packages.

 The fix all would be simply to append the Module handle to the Superclass name only when there is a Library in play.. You need to use the module handle because you can have multiple modules in play with the same classes..

 What do you think ?

PS.
  btw, I wanted to note that this effects all controls that are super-classed created.

Zdravko Gabrovski

2020-12-05 09:28

reporter   ~0127352

You mean "Subclass"? In FinishCreateWindow there is check for SubClass, that tries to register a Subclass LCLComboBox.

Completely agree with you for the module handle in the name. In the patch proposed by me I did something similar, if there is DLL, I simple append "DLL" to a subclass name.
You are right - this will work correct only if there is one DLL in a main application. If you load another DLL which uses the same technique, it will fail!

But: For me is very strange, the GetClassInfoW pass System.HInstance parameter, which is completely different for Host and Dll. For me - Windows did not handle this in correct way! My feeling is, that there is check inside there GetClassInfoW function in API, which check if DLL instance is a part form application instance.

Zdravko Gabrovski

2020-12-05 11:32

reporter   ~0127353

Last edited: 2020-12-05 12:12

View 2 revisions

I did it in your way, please find attached new path files.
Now I append HINSTANCE value to a subclass name as a hexadecimal value.
Edit: This only in a DLL.
win32wschecklst.diff1 (1,514 bytes)   
Index: lcl/interfaces/win32/win32wschecklst.pp
===================================================================
--- lcl/interfaces/win32/win32wschecklst.pp	(revision 64142)
+++ lcl/interfaces/win32/win32wschecklst.pp	(working copy)
@@ -28,7 +28,7 @@
 // uncomment only when needed for registration
 ////////////////////////////////////////////////////
   Windows, Classes, Controls, CheckLst, StdCtrls, Themes, Graphics, LCLType,
-  LazUTF8, LMessages, LCLMessageGlue,
+  LazUTF8, LMessages, LCLMessageGlue, SysUtils,
 ////////////////////////////////////////////////////
   WSCheckLst, WSLCLClasses, Win32Int, Win32Proc, Win32WSControls, Win32WSStdCtrls;
 
@@ -151,6 +151,8 @@
   const AWinControl: TWinControl; const AParams: TCreateParams): TLCLIntfHandle;
 var
   Params: TCreateWindowExParams;
+  S : String;
+  L,L1: SizeInt;
 begin
   // general initialization of Params
   PrepareCreateWindow(AWinControl, AParams, Params);
@@ -157,7 +159,16 @@
   with Params do
   begin
     pClassName := ListBoxClsName;
-    pSubClassName := LCLCheckListboxClsName;
+    if IsLibrary then begin
+      S := IntToHex( System.HINSTANCE );
+      L:=Length(pChar(LCLCheckListboxClsNameDLL));
+      L1 := Length( S );
+      Move( S[1], LCLCheckListboxClsNameDLL[L], L1);
+      LCLCheckListboxClsNameDLL[L+L1] := #0;
+      pSubClassName := LCLCheckListboxClsNameDLL;
+      end
+    else
+      pSubClassName := LCLCheckListboxClsName;
     SubClassWndProc := @CheckListBoxWndProc;
   end;
   // create window
win32wschecklst.diff1 (1,514 bytes)   
win32wsstdctrls.diff1 (1,761 bytes)   
Index: lcl/interfaces/win32/win32wsstdctrls.pp
===================================================================
--- lcl/interfaces/win32/win32wsstdctrls.pp	(revision 64142)
+++ lcl/interfaces/win32/win32wsstdctrls.pp	(working copy)
@@ -796,6 +796,8 @@
   const AParams: TCreateParams): HWND;
 var
   Params: TCreateWindowExParams;
+  S: String;
+  L, L1: integer;
 begin
   // general initialization of Params
   PrepareCreateWindow(AWinControl, AParams, Params);
@@ -802,7 +804,16 @@
   with Params do
   begin
     pClassName := ListBoxClsName;
-    pSubClassName := LCLListboxClsName;
+    if IsLibrary then begin
+      S := IntToHex( System.HINSTANCE );
+      L:=Length(pChar(LCLListboxClsNameDLL));
+      L1 := Length( S );
+      Move( S[1], LCLListboxClsNameDLL[L],L1);
+      LCLListboxClsNameDLL[L+L1] := #0;
+      pSubClassName := LCLListboxClsNameDLL;
+      end
+    else
+      pSubClassName := LCLListboxClsName;
     SubClassWndProc := @ListBoxWindowProc;
   end;
   // create window
@@ -1014,6 +1025,8 @@
 var
   Params: TCreateWindowExParams;
   Info: TComboboxInfo;
+  S: String;
+  L,L1: SizeInt;
 begin
   // general initialization of Params
   PrepareCreateWindow(AWinControl, AParams, Params);
@@ -1021,7 +1034,16 @@
   with Params do
   begin
     pClassName := ComboboxClsName;
-    pSubClassName := LCLComboboxClsName;
+    if IsLibrary then begin
+      S := IntToHex( System.HINSTANCE );
+      L:=Length(pChar(LCLComboboxClsNameDLL));
+      L1 := Length( S );
+      Move( S[1], LCLComboboxClsNameDLL[L], L1);
+      LCLComboboxClsNameDLL[L+L1] := #0;
+      pSubClassName := LCLComboboxClsNameDLL;
+    end
+    else
+      pSubClassName := LCLComboboxClsName;
     SubClassWndProc := @ComboBoxWindowProc;
   end;
 
win32wsstdctrls.diff1 (1,761 bytes)   
win32int.diff1 (824 bytes)   
Index: lcl/interfaces/win32/win32int.pp
===================================================================
--- lcl/interfaces/win32/win32int.pp	(revision 64142)
+++ lcl/interfaces/win32/win32int.pp	(working copy)
@@ -239,6 +239,9 @@
   LCLComboboxClsName: array[0..11] of char = 'LCLComboBox'#0;
   LCLListboxClsName: array[0..10] of char = 'LCLListBox'#0;
   LCLCheckListboxClsName: array[0..15] of char = 'LCLCheckListBox'#0;
+  LCLComboboxClsNameDLL: array[0..30] of char = 'LCLComboBoxDLL'#0;
+  LCLListboxClsNameDLL: array[0..29] of char = 'LCLListBoxDLL'#0;
+  LCLCheckListboxClsNameDLL: array[0..34] of char = 'LCLCheckListBoxDLL'#0;
   ClsNameW: array[0..6] of WideChar = ('W', 'i', 'n', 'd', 'o', 'w', #0);
   ClsHintNameW: array[0..10] of WideChar = ('H', 'i', 'n', 't', 'W', 'i', 'n', 'd', 'o', 'w', #0);
 
win32int.diff1 (824 bytes)   

jamie philbrook

2020-12-05 16:07

reporter   ~0127361

That is way over kill and unnecessary..

There shall be no changing of the constants and there is no need to be off in several files..

The final end is in the FinalCreateWindow in the Win32WSControls.. file

also, please do not add extra units, especially the Systutil. just for a Hexstr function. There is a function already in the system that can do that but it needs to left trim.

Sucking in a huge Lib just for a simple function isn't acceptable..

 what needs to be done is simple create a local copy of the pSubClassName and alter it when registering the class instead of using the Pointer version of it from the given parameters.

for example
Var
 LocalSubClassName :Wstring;
begin
  LocalSubClassName := parms.pSubClassName;
if IsLibrary Then LocalSubClassName += System.HexStr(Pointer(System.HInstance));

Change the reference to LocalSubClassName instead of the pSubClassName;

….
 IN the finalCreate procedure which is called from all other units in the Win32 widget sets this should fix it.


But I still need to research more because something very funky is taking place with the Instance handle values in the DLL's...

jamie philbrook

2020-12-05 19:26

reporter   ~0127362

Last edited: 2020-12-05 19:28

View 2 revisions

After looking at this, it appears these class named constants are referenced all over the place..

So this is my idea..

In the Win32Int unit in the initialization section, redefined the constants to add the Module handle value to it.

So for example:
  Currently the LCLComboClsName has an array of [0.11] of char = 'LCLComboBox'#0;

Enlarge that array so that it will accept the max size of the Instance handle value plus the NULL char..
since these are arrays and not strings, the NUL char terminates the string which works out well here.

so make that array size at least [0..20] of char so that 10 more chars can be added with a NULL..
do this for LCLListBoxClsName, LCLCheckBoxClName too.

 and In the Initialization section append the module handle value as Hex text to this array.

 This is only done once so it should work correctly through out all the other widget files.

 But there still is the problem of cleaning up the DLL on process detach, you still need to know the class name to eliminate the registered class and I don't think the LCL knows how to handle that because normally windows will clean that up from the application termination, not the DLL's..
   If you were to unload and reload a DLL with the same class names and windows decides to use the same Lib handle as before, its going to fail again because now that class name you have modified will still be there living...

 I think overall this may not be worth the trip at the current moment and maybe we can put this to side for now until a better solution is made..

 With DLLs you most absolutely do need to unregister the classes you created. I am not sure if the TFORM does that ?

Zdravko Gabrovski

2020-12-05 23:56

reporter   ~0127366

I did not add sysutils in Win32WSControls it was already there. I add it in CheckListBoxUint.
In fact I am doing your proposal (I extend array length of the constant and I add HInstance to it)
I am not agree that is safe to call Windows API with a pointer that will points to a local variable.
I can not find any peace of code that unregister class in Win32WSControls.
I think windows did it automatically in FreeLibrary??
But even not, I am changing the class name in global variable and if there is code that Unregister Windows Subclass this will happens (because the sub class name will be changed in global place).
 Your proposal for change in initialization section is good. I will do it in this way. Tomorrow.

jamie philbrook

2020-12-06 01:04

reporter   ~0127367

Windows will unregister the classes when the host app or app itself terminates but, if you terminate a DLL windows does not unregister the class, this is what I am saying..
so if a DLL drops out and the host app is still alive and later on this DLL gets reloaded again using the same Instance handle, this trick will not work so keep this in mind when ever creating classes in your DLL, you will need to unregister these classes

as for the Sysutil, Its in one of the patch files you dropped here, you added the unit so you can use the HexTostr which does not matter at this point.

if you find that it works then maybe we could use a compressed string for the instance handle, I have an idea for that..

Zdravko Gabrovski

2020-12-06 07:28

reporter   ~0127370

Dont worry I removed sysutils.
Now there is a "perfect" patch - I append HInstance to the class names in the initialization section, I add UnregisterClassW in the finalisation section.
Final patch is only one file - win32int.pp.

Please, take a look.
win32int.diff2 (2,405 bytes)   
Index: lcl/interfaces/win32/win32int.pp
===================================================================
--- lcl/interfaces/win32/win32int.pp	(revision 64142)
+++ lcl/interfaces/win32/win32int.pp	(working copy)
@@ -236,9 +236,9 @@
   TabControlClsName: array[0..15] of char = 'SysTabControl32'#0;
   ListViewClsName: array[0..13] of char = 'SysListView32'#0;
 
-  LCLComboboxClsName: array[0..11] of char = 'LCLComboBox'#0;
-  LCLListboxClsName: array[0..10] of char = 'LCLListBox'#0;
-  LCLCheckListboxClsName: array[0..15] of char = 'LCLCheckListBox'#0;
+  LCLComboboxClsName: array[0..27] of char = 'LCLComboBox'#0;
+  LCLListboxClsName: array[0..26] of char = 'LCLListBox'#0;
+  LCLCheckListboxClsName: array[0..31] of char = 'LCLCheckListBox'#0;
   ClsNameW: array[0..6] of WideChar = ('W', 'i', 'n', 'd', 'o', 'w', #0);
   ClsHintNameW: array[0..10] of WideChar = ('H', 'i', 'n', 't', 'W', 'i', 'n', 'd', 'o', 'w', #0);
 
@@ -341,6 +341,9 @@
 {$I win32winapi.inc}
 {$I win32lclintf.inc}
 
+var
+  S : String;
+  L, L1 : integer;
 
 initialization
   Pointer(GetDpiForMonitor) := @xGetDpiForMonitor;
@@ -356,6 +359,22 @@
   else
     IntSetPixel:=@Windows.SetPixel;
 
+  // Register a windows sub-classes failed in a DLL library
+  // https://bugs.freepascal.org/view.php?id=37982
+  if IsLibrary then begin
+    S := IntToHex( System.HINSTANCE );
+    L:=Length(pChar(LCLListboxClsName));
+    L1 := Length( S );
+    Move( S[1], LCLListboxClsName[L],L1);
+    LCLListboxClsName[L+L1] := #0;
+    L:=Length(pChar(LCLComboboxClsName));
+    Move( S[1], LCLComboboxClsName[L], L1);
+    LCLComboboxClsName[L+L1] := #0;
+    L:=Length(pChar(LCLCheckListboxClsName));
+    Move( S[1], LCLCheckListboxClsName[L], L1);
+    LCLCheckListboxClsName[L+L1] := #0;
+    end;
+
 finalization
   if CurDoubleBuffer.Bitmap <> 0 then
   begin
@@ -362,4 +381,12 @@
     Windows.DeleteObject(CurDoubleBuffer.Bitmap);
     CurDoubleBuffer.Bitmap := 0;
   end;
+
+  // Register a windows sub-classes failed in a DLL library
+  // https://bugs.freepascal.org/view.php?id=37982
+  if IsLibrary then begin
+    Windows.UnregisterClassW(PWideChar(  WideString(LCLListboxClsName) ), System.HInstance);
+    Windows.UnregisterClassW(PWideChar(  WideString(LCLComboboxClsName) ), System.HInstance);
+    Windows.UnregisterClassW(PWideChar(  WideString(LCLCheckListboxClsName) ), System.HInstance);
+    end;
 end.
win32int.diff2 (2,405 bytes)   

jamie philbrook

2020-12-06 14:21

reporter   ~0127374

From what I can see it looks good..

I would say if you have rebuilt the IDE and your code with this patch and everything is still working I would say it's a done deal..

I liked the idea of the unregistering of the super classes in the end.

Lets see what others have to say about it.

Zdravko Gabrovski

2020-12-06 22:02

reporter   ~0127385

There is no DLL usage inside IDE.
But I did it - everything is OK, IDE Works.
Please, let somebody apply the patch..

jamie philbrook

2020-12-06 22:12

reporter   ~0127386

Last edited: 2020-12-06 23:24

View 2 revisions

Ok, I have no objection to having it applied. Now we need to find someone here that has the godly power to do it ;)


EDIT

  The IntToHex(value) does not exist, there is no overload for it.. where did u get that..?

 This must compile at least using 3.0.4 and up...

 Testing here I do not find it in the 3.2.0 compiler or anything less. I don't have 3.3.x to test with .

jamie philbrook

2020-12-06 23:40

reporter   ~0127388

Please resubmit the patch using this to resolve the hex string

S := System.HexStr(Pointer(System.Hinstance));

This will produce a 16 char for 64 bit and a 8 char for 32 bit...

 You can leave the array sizes as you have them, the extra space will not harm the 32 bit target

Zdravko Gabrovski

2020-12-07 06:46

reporter   ~0127391

The patch is for the trunk .
Not necessary to use HexStr, IntToHex has declaration for all of input type, it will automatically use int32 for 32 bit .
If you overlook the code, just in case I put #0 at the end (LCLListboxClsName[L+L1] := #0;), for a 0 termination of the string.

jamie philbrook

2020-12-07 12:37

reporter   ~0127408

+ S := IntToHex( System.HINSTANCE );
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This overload does not exist in 3.2.0 FPC or down..
Look I know you understand what I am saying and if this is an attempt to push the community to use the trunk compiler along with the laz trunk I don't think it will go over well because it will then make the trunk non usable for those that have the 3.2.0 or less and I just happen to be one of those because I like to use released tools not those that are in constant flux.

either use IntToHex(System.HInstance, SizeOf(Hinstance) shl 1)) or use the Pointer version like I purposed.. Those work with 3.2.0 and down.

Otherwise I think this patch can be put on hold until Lazarus gets released with trunk compiler.

I and many of us do not use anything other than released compilers because of constant work being done on them

Zdravko Gabrovski

2020-12-07 12:46

reporter   ~0127410

OK, agree my friend,
Please, use this patch!
win32int.diff3 (2,417 bytes)   
Index: lcl/interfaces/win32/win32int.pp
===================================================================
--- lcl/interfaces/win32/win32int.pp	(revision 64142)
+++ lcl/interfaces/win32/win32int.pp	(working copy)
@@ -236,9 +236,9 @@
   TabControlClsName: array[0..15] of char = 'SysTabControl32'#0;
   ListViewClsName: array[0..13] of char = 'SysListView32'#0;
 
-  LCLComboboxClsName: array[0..11] of char = 'LCLComboBox'#0;
-  LCLListboxClsName: array[0..10] of char = 'LCLListBox'#0;
-  LCLCheckListboxClsName: array[0..15] of char = 'LCLCheckListBox'#0;
+  LCLComboboxClsName: array[0..27] of char = 'LCLComboBox'#0;
+  LCLListboxClsName: array[0..26] of char = 'LCLListBox'#0;
+  LCLCheckListboxClsName: array[0..31] of char = 'LCLCheckListBox'#0;
   ClsNameW: array[0..6] of WideChar = ('W', 'i', 'n', 'd', 'o', 'w', #0);
   ClsHintNameW: array[0..10] of WideChar = ('H', 'i', 'n', 't', 'W', 'i', 'n', 'd', 'o', 'w', #0);
 
@@ -341,6 +341,9 @@
 {$I win32winapi.inc}
 {$I win32lclintf.inc}
 
+var
+  S : String;
+  L, L1 : integer;
 
 initialization
   Pointer(GetDpiForMonitor) := @xGetDpiForMonitor;
@@ -356,6 +359,22 @@
   else
     IntSetPixel:=@Windows.SetPixel;
 
+  // Register a windows sub-classes failed in a DLL library
+  // https://bugs.freepascal.org/view.php?id=37982
+  if IsLibrary then begin
+    S := System.HexStr(Pointer(System.Hinstance));
+    L:=Length(pChar(LCLListboxClsName));
+    L1 := Length( S );
+    Move( S[1], LCLListboxClsName[L],L1);
+    LCLListboxClsName[L+L1] := #0;
+    L:=Length(pChar(LCLComboboxClsName));
+    Move( S[1], LCLComboboxClsName[L], L1);
+    LCLComboboxClsName[L+L1] := #0;
+    L:=Length(pChar(LCLCheckListboxClsName));
+    Move( S[1], LCLCheckListboxClsName[L], L1);
+    LCLCheckListboxClsName[L+L1] := #0;
+    end;
+
 finalization
   if CurDoubleBuffer.Bitmap <> 0 then
   begin
@@ -362,4 +381,12 @@
     Windows.DeleteObject(CurDoubleBuffer.Bitmap);
     CurDoubleBuffer.Bitmap := 0;
   end;
+
+  // Register a windows sub-classes failed in a DLL library
+  // https://bugs.freepascal.org/view.php?id=37982
+  if IsLibrary then begin
+    Windows.UnregisterClassW(PWideChar(  WideString(LCLListboxClsName) ), System.HInstance);
+    Windows.UnregisterClassW(PWideChar(  WideString(LCLComboboxClsName) ), System.HInstance);
+    Windows.UnregisterClassW(PWideChar(  WideString(LCLCheckListboxClsName) ), System.HInstance);
+    end;
 end.
win32int.diff3 (2,417 bytes)   

Juha Manninen

2020-12-07 14:17

developer   ~0127416

I applied the latest patch with minimal testing. It was apparently tested by you guys a lot.
Thanks.
Can you please have a look at the related issues in case they benefit from the same approach.

Zdravko Gabrovski

2020-12-07 14:25

reporter   ~0127419

Many, Many thanks, Juha!
I will take a look, May be end of the week.

Issue History

Date Modified Username Field Change
2020-10-25 07:15 Zdravko Gabrovski New Issue
2020-10-25 07:15 Zdravko Gabrovski File Added: ComboBoxOwnerDrawDllBug.7z
2020-10-25 07:15 Zdravko Gabrovski File Added: UbuntuGTK2.png
2020-10-25 07:15 Zdravko Gabrovski File Added: Win64InDll.png
2020-10-25 07:15 Zdravko Gabrovski File Added: Win64inTheHost.png
2020-10-25 09:38 Juha Manninen Relationship added related to 0033787
2020-10-25 09:39 Juha Manninen Relationship added related to 0034099
2020-10-25 09:41 Juha Manninen Relationship added related to 0032578
2020-10-25 12:04 Zdravko Gabrovski Note Added: 0126541
2020-10-25 12:34 Zdravko Gabrovski Note Added: 0126542
2020-10-25 12:51 Zdravko Gabrovski Note Added: 0126543
2020-10-25 12:51 Zdravko Gabrovski File Added: ComboBoxOwnerDrawDllBug-2.7z
2020-10-25 12:51 Zdravko Gabrovski File Added: r9oJCHREeg.mp4
2020-11-16 19:27 Zdravko Gabrovski Note Added: 0126986
2020-11-16 19:27 Zdravko Gabrovski File Added: win32wsstdctrls.diff
2020-11-16 19:27 Zdravko Gabrovski File Added: win32wschecklst.diff
2020-11-16 19:27 Zdravko Gabrovski File Added: win32int.diff
2020-11-16 20:14 Anton Kavalenka Note Added: 0126987
2020-11-16 22:14 Zdravko Gabrovski Note Added: 0126988
2020-11-17 20:29 Serge Anvarov Note Added: 0127015
2020-11-17 20:29 Serge Anvarov File Added: uSimpleClassReg.pp
2020-11-17 20:29 Serge Anvarov File Added: project1Dll.lpr
2020-11-17 20:29 Serge Anvarov File Added: project1Exe.lpr
2020-11-17 21:50 Zdravko Gabrovski Note Added: 0127019
2020-11-17 21:50 Zdravko Gabrovski File Added: Parallels Picture.png
2020-11-17 21:50 Zdravko Gabrovski File Added: DllApp.7z
2020-11-17 21:50 Zdravko Gabrovski File Added: HostApp.7z
2020-11-17 21:57 Zdravko Gabrovski Note Edited: 0127019 View Revisions
2020-11-17 22:02 Zdravko Gabrovski Note Edited: 0127019 View Revisions
2020-11-18 16:02 Serge Anvarov Note Added: 0127033
2020-11-18 16:02 Serge Anvarov File Added: unit1.pp
2020-11-18 16:02 Serge Anvarov File Added: Screen.jpg
2020-11-18 16:50 Zdravko Gabrovski Note Added: 0127034
2020-11-18 16:53 Zdravko Gabrovski Note Added: 0127035
2020-11-18 16:53 Zdravko Gabrovski File Added: umain.lfm
2020-11-18 16:53 Zdravko Gabrovski File Added: umain.pas
2020-11-21 13:55 Serge Anvarov Note Added: 0127081
2020-11-21 13:55 Serge Anvarov File Added: unit1-2.pp
2020-11-21 13:55 Serge Anvarov File Added: Screen-2.jpg
2020-11-22 10:26 Zdravko Gabrovski Note Added: 0127092
2020-11-22 13:13 Serge Anvarov Note Added: 0127095
2020-11-22 23:18 Zdravko Gabrovski Note Added: 0127126
2020-11-23 00:41 jamie philbrook Note Added: 0127127
2020-11-23 00:54 jamie philbrook Note Edited: 0127127 View Revisions
2020-11-23 10:38 Zdravko Gabrovski Note Added: 0127135
2020-11-23 10:47 Zdravko Gabrovski Note Added: 0127136
2020-11-23 10:50 Zdravko Gabrovski Note Edited: 0127135 View Revisions
2020-11-23 17:00 Serge Anvarov Note Added: 0127141
2020-11-23 23:31 jamie philbrook Note Added: 0127147
2020-11-24 10:10 Zdravko Gabrovski Note Added: 0127153
2020-11-25 01:23 jamie philbrook Note Added: 0127167
2020-11-25 02:13 jamie philbrook Note Edited: 0127167 View Revisions
2020-11-25 12:37 Zdravko Gabrovski Note Added: 0127174
2020-12-05 00:16 jamie philbrook Note Added: 0127349
2020-12-05 00:21 jamie philbrook Note Edited: 0127349 View Revisions
2020-12-05 09:28 Zdravko Gabrovski Note Added: 0127352
2020-12-05 11:32 Zdravko Gabrovski Note Added: 0127353
2020-12-05 11:32 Zdravko Gabrovski File Added: win32wschecklst.diff1
2020-12-05 11:32 Zdravko Gabrovski File Added: win32wsstdctrls.diff1
2020-12-05 11:32 Zdravko Gabrovski File Added: win32int.diff1
2020-12-05 12:12 Zdravko Gabrovski Note Edited: 0127353 View Revisions
2020-12-05 16:07 jamie philbrook Note Added: 0127361
2020-12-05 19:26 jamie philbrook Note Added: 0127362
2020-12-05 19:28 jamie philbrook Note Edited: 0127362 View Revisions
2020-12-05 23:56 Zdravko Gabrovski Note Added: 0127366
2020-12-06 01:04 jamie philbrook Note Added: 0127367
2020-12-06 07:28 Zdravko Gabrovski Note Added: 0127370
2020-12-06 07:28 Zdravko Gabrovski File Added: win32int.diff2
2020-12-06 14:21 jamie philbrook Note Added: 0127374
2020-12-06 22:02 Zdravko Gabrovski Note Added: 0127385
2020-12-06 22:12 jamie philbrook Note Added: 0127386
2020-12-06 23:24 jamie philbrook Note Edited: 0127386 View Revisions
2020-12-06 23:40 jamie philbrook Note Added: 0127388
2020-12-07 06:46 Zdravko Gabrovski Note Added: 0127391
2020-12-07 12:37 jamie philbrook Note Added: 0127408
2020-12-07 12:46 Zdravko Gabrovski Note Added: 0127410
2020-12-07 12:46 Zdravko Gabrovski File Added: win32int.diff3
2020-12-07 14:03 Juha Manninen Assigned To => Juha Manninen
2020-12-07 14:03 Juha Manninen Status new => assigned
2020-12-07 14:17 Juha Manninen Status assigned => resolved
2020-12-07 14:17 Juha Manninen Resolution open => fixed
2020-12-07 14:17 Juha Manninen Fixed in Revision => r64179
2020-12-07 14:17 Juha Manninen LazTarget => -
2020-12-07 14:17 Juha Manninen Note Added: 0127416
2020-12-07 14:25 Zdravko Gabrovski Note Added: 0127419