View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0037982 | Lazarus | LCL | public | 2020-10-25 07:15 | 2020-12-07 14:25 |
Reporter | Zdravko Gabrovski | Assigned To | Juha Manninen | ||
Priority | normal | Severity | minor | Reproducibility | always |
Status | resolved | Resolution | fixed | ||
Platform | Windows 32/64 | OS | Windows | ||
Product Version | 2.1 (SVN) | ||||
Summary | 0037982: Combobox with csOwnerDrawFixed style does not work in DLL library under Windows | ||||
Description | The 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 Reproduce | Just 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 Information | Only inder Windows 32/64 | ||||
Tags | No tags attached. | ||||
Fixed in Revision | r64179 | ||||
LazTarget | - | ||||
Widgetset | |||||
Attached Files |
|
|
|
|
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. |
|
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. |
|
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. |
|
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; 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 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); |
|
Is not the better way to check if window class not registered, then register it? |
|
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. |
|
> 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. 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. 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. |
|
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. |
|
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. |
|
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; |
|
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.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. |
|
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. |
|
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. |
|
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? |
|
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? |
|
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. |
|
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??? |
|
I keep insisting that the solution to the problem is to change the class name in Dll. |
|
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. |
|
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. |
|
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 |
|
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. |
|
@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. |
|
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. |
|
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. |
|
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 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; 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); |
|
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... |
|
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 ? |
|
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. |
|
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.. |
|
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. |
|
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. |
|
There is no DLL usage inside IDE. But I did it - everything is OK, IDE Works. Please, let somebody apply the patch.. |
|
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 . |
|
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 |
|
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. |
|
+ 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 |
|
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. |
|
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. |
|
Many, Many thanks, Juha! I will take a look, May be end of the week. |
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 |