View Issue Details

IDProjectCategoryView StatusLast Update
0037982LazarusLCLpublic2020-11-25 12:37
ReporterZdravko Gabrovski Assigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
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 Revision
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.

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