I found this source code from a Delphi sample codes, and
I am adding a control or component inside a Delphi dynamic DLL, I can't figure it out,
library DLLEntryLib;
uses
SysUtils,
Windows,
Dialogs,
Classes,
msHTML,
SHDocVw;
type
TMyWeb = class(TWebBrowser)
constructor create(Aowner: TComponent); override;
end;
var
web: TMyWeb;
// Initialize properties here
constructor TMyWeb.Create(AOwner: TComponent);
begin
inherited Create(Self);
end;
procedure getweb;
begin
web := TmyWeb.create(nil);
web.Navigate('http://mywebsite.com');
end;
procedure xDLLEntryPoint(dwReason: DWord);
begin
case dwReason of
DLL_PROCESS_ATTACH:
begin
getweb; //I THINK THE ERROR IS HERE, HOW TO WORK THIS OUT?
ShowMessage('Attaching to process');
end;
DLL_PROCESS_DETACH: ShowMessage('Detaching from process');
DLL_THREAD_ATTACH: MessageBeep(0);
DLL_THREAD_DETACH: MessageBeep(0);
end;
end;
begin
{ First, assign the procedure to the DLLProc variable }
DllProc := #xDLLEntryPoint;
{ Now invoke the procedure to reflect that the DLL is attaching to the
process }
xDLLEntryPoint(DLL_PROCESS_ATTACH);
end.
//IN MY APPLICATION FORM.
procedure TMainForm.btnLoadLibClick(Sender: TObject);
begin
if LibHandle = 0 then
begin
LibHandle := LoadLibrary('DLLENTRYLIB.DLL');
if LibHandle = 0 then
raise Exception.Create('Unable to Load DLL');
end
else
MessageDlg('Library already loaded', mtWarning, [mbok], 0);
end;
How do I get rid of the error?
raise to many consicutive exception
When you write:
inherited Create(Self);
you should write
inherited Create(AOwner);
You are asking the control to own itself. That just cannot work. That quite possibly leads to a non-terminated recursion if the constructor fails.
The other big problem is that you are creating a web browser control inside DllMain. That's a very big no-no. You'll want to stop doing that. Move that code into a separate exported function. Do nothing in DllMain.
Presumably the caller has already initialized COM. If not, you will need to ensure that the caller does so. If the caller is a VCL forms app then COM will be initialized automatically.
Related
I have created a simple test control inheriting from Tcustom control, which contains 2 panels. The first is a header aligned to the top and client panel aligned to alclient.
I would like the client panel to accept controls from the designer and although I can place controls on the panel, they are not visible at run time and they do not save properly when the project is closed.
The sample code for the control is as follows
unit Testcontrol;
interface
uses Windows,System.SysUtils, System.Classes,System.Types, Vcl.Controls,
Vcl.Forms,Vcl.ExtCtrls,graphics,Messages;
type
TtestControl = class(TCustomControl)
private
FHeader:Tpanel;
FClient:Tpanel;
protected
public
constructor Create(Aowner:Tcomponent);override;
destructor Destroy;override;
published
property Align;
end;
implementation
{ TtestControl }
constructor TtestControl.Create(Aowner: Tcomponent);
begin
inherited;
Fheader:=Tpanel.create(self);
Fheader.Caption:='Header';
Fheader.Height:=20;
Fheader.Parent:=self;
Fheader.Align:=altop;
Fclient:=Tpanel.Create(Self);
with Fclient do
begin
setsubcomponent(true);
ControlStyle := ControlStyle + [csAcceptsControls];
Align:=alclient;
Parent:=self;
color:=clwhite;
BorderStyle:=bssingle;
Ctl3D:=false;
ParentCtl3D:=false;
Bevelouter:=bvnone;
end;
end;
destructor TtestControl.Destroy;
begin
FHeader.Free;
FClient.Free;
inherited;
end;
end.
If I put a button on the test component, the structure shows it as part of the form and not a subcomponent of the test component....and then it doesnt work anyway.
Is there a way to do this?
After plenty of googling around, I found some information which allowed me to cobble together a solution that seems to work.
It seems there two procedures in the base class needs to be overridden to update the control.
The first is the a method called "Loaded" which is called when the streaming has ended.
It seems the streaming places all the sub-panel components placed by the designer on the base component, not on the panel they were originally parent to. So this routine manually reassigns the Parent properties after the loading process has finished.
The second method is called GetChildren, I couldn't find much information as to what this method actually does other than the rather cryptic text in the chm help. However I adapted the overridden code from another example I found on the web which had a similar requirement and it worked. So if anyone can provide some insight as to why this is necessary then that would be useful information.
I have pasted the complete source code for the sample custom component below so that anyone who has a similar requirement in the future, can use it as a starting template for their own components.
unit Testcontrol;
interface
uses Windows, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.ExtCtrls,graphics;
type
TtestControl = class(TCustomControl)
private
FHeader:Tpanel;
FClient:Tpanel;
protected
procedure Loaded;override;
procedure GetChildren(Proc:TGetChildProc; Root:TComponent);override;
public
constructor Create(Aowner:Tcomponent);override;
destructor Destroy;override;
published
property Align;
end;
implementation
{ TtestControl }
constructor TtestControl.Create(Aowner:Tcomponent);
begin
inherited;
Fheader:=Tpanel.create(self);
Fheader.setsubcomponent(true);
Fheader.Caption:='Header';
Fheader.Height:=20;
Fheader.Parent:=self;
Fheader.Align:=altop;
Fclient:=Tpanel.Create(Self);
with Fclient do
begin
setsubcomponent(true);
ControlStyle := ControlStyle + [csAcceptsControls];
Align:=alclient;
Parent:=self;
color:=clwhite;
BorderStyle:=bssingle;
Ctl3D:=false;
ParentCtl3D:=false;
Bevelouter:=bvnone;
end;
end;
destructor TtestControl.Destroy;
begin
FHeader.Free;
FClient.Free;
inherited;
end;
procedure TtestControl.Loaded;
var i:integer;
begin
inherited;
for i := ControlCount - 1 downto 0 do
if (Controls[i] <> Fheader) and (Controls[i] <> Fclient) then
Controls[i].Parent := Fclient;
end;
procedure TtestControl.GetChildren(Proc:TGetChildProc; Root:TComponent);
var i:integer;
begin
inherited;
for i := 0 to Fclient.ControlCount-1 do
Proc(Fclient.Controls[i]);
end;
end.
I have a dll that contains a class that implements a interface. The dll has an exported method that returns the interface.
I can explicit load the dll succefully, but when I try to use Free Library I get Access Violation. I did not tried use implicit link, because I need use the explicit mode.
If I just load the library and free right after, without geting the interface, everything works fine.
Dll
library Tef;
uses
uTTefFacade;
{$R *.res}
exports
CreateTef;
begin
end.
Interface in dll:
type
ITefFacade = interface
['{77691DD1-C6E9-4F75-951F-BFA1468DC36C}']
function IniciarTransacao(AParam: TTefIniciarTransacaoParamDTO): TTefIniciarTransacaoResultDTO;
end;
Class in dll:
type
TTefFacade = class (TInterfacedObject, ITefFacade)
private
function IniciarTransacao(AParam: TTefIniciarTransacaoParamDTO): TTefIniciarTransacaoResultDTO;
public
constructor Create;
destructor Free;
end;
function CreateTef: ITefFacade; export; stdcall;
function CreateTef: ITefFacade;
begin
Result := ITefFacade(TTefFacade.Create);
end;
Exe:
procedure TForm1.FormCreate(Sender: TObject);
var
CreateTef: function: ITefFacade; stdcall;
begin
try
FTef := nil;
FHTef := LoadLibrary('Tef.dll');
if (FHTef > 0) then
begin
#CreateTef := GetProcAddress(FHTef, 'CreateTef');
if (#CreateTef <> nil) then
FTef := CreateTef;
end;
if (FTef = nil) then
ShowMessage('Error.');
except
on E: Exception do
ShowMessage('Erro: ' + E.Message);
end;
end;
And here in the calling Free Library, access violation occurs.
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeLibrary(FHTef);
end;
You have to nil the FTef reference before releasing the DLL.
The object behind the interface lives in the DLL, you should respect this. If you try to unload the DLL without releasing the interface first, there will be problems when the object is accessed after the unload (such as when Delphi auto-nils the reference when it goes out of scope).
Here is my code in DLL:
procedure TTaskTest;
begin
TTask.Run(
procedure
begin
Sleep(300);
end);
end;
exports TTaskTest;
After calling this method in host app, then call FreeLibrary will freeze host app.
After debug , I found that the program freezes at if TMonitor.Wait(FLock, Timeout) then in TLightweightEvent.WaitFor , but the debugger cannot step into TMonitor.Wait.
How to solve?
This issue was reported (RSP-13742 Problem with ITask, IFuture inside DLL).
It was closed "Works as Expected" with a remark:
To prevent this failure using ITask or IFuture from a DLL, the DLL will need to be using its own instance of TThreadPool in place of the default instance of TThreadPool.
Here is an example from Embarcadero how to handle it:
library TestLib;
uses
System.SysUtils,
System.Classes,
System.Threading;
{$R *.res}
VAR
tpool: TThreadPool;
procedure TestDelay;
begin
tpool := TThreadPool.Create;
try
TTask.Run(
procedure begin
Sleep(300);
end,
tpool
);
finally
FreeAndNil(tpool);
end;
end;
exports
TestDelay;
begin
end.
Another way is to create the threadpool when the library is loaded, and add a release procedure, which you call before calling FreeLibrary.
// In dll
procedure TestDelay;
begin
TTask.Run(
procedure begin
Sleep(300);
end,
tpool
);
end;
procedure ReleaseThreadPool;
begin
FreeAndNil(tpool);
end;
exports
TestDelay,ReleaseThreadPool;
begin
tpool := TThreadPool.Create;
end.
I am working at a huge, legacy source code where several SetFocus is called at many places, but sometimes, the check if the control is visible or enabled is missing.
Due to limited time, and the huge amount of source code, I decided that I want to ignore these errors, since the focus is (in our case) not a critical feature. A raised Exception will result in a complete failure, while a missing focus is just an optical issue.
My current plan is following:
I create an unit with a class helper like this:
type
TWinControlEx = class helper for TWinControl
procedure SetFocusSafe;
end;
procedure TWinControlEx.SetFocusSafe;
begin
if CanFocus then SetFocus;
end;
I include the unit to every unit which uses ".SetFocus" (I will use the global code search)
I replace every .SetFocus with .SetFocusSafe
There is a problem though: If possible, I want to avoid that coworkers accidently use .SetFocus , or forget to include the classhelper unit.
Which other options do I have?
The best case would be if there is a technique/hack to make SetFocus not raising an exception. (Without recompiling the VCL)
Just patch the TWinControl.SetFocus method:
unit SetFocusFix;
interface
implementation
uses
Controls,
Forms,
SysUtils,
Windows;
type
TWinControlHack = class(TWinControl)
public
procedure SetFocus; override;
end;
procedure TWinControlHack.SetFocus;
var
Parent: TCustomForm;
begin
if not CanFocus then Exit;
Parent := GetParentForm(Self);
if Parent <> nil then
Parent.FocusControl(Self)
else if ParentWindow <> 0 then
Windows.SetFocus(Handle)
else
ValidParentForm(Self);
end;
procedure RedirectFunction(OrgProc, NewProc: Pointer);
type
TJmpBuffer = packed record
Jmp: Byte;
Offset: Integer;
end;
var
n: UINT_PTR;
JmpBuffer: TJmpBuffer;
begin
JmpBuffer.Jmp := $E9;
JmpBuffer.Offset := PByte(NewProc) - (PByte(OrgProc) + 5);
if not WriteProcessMemory(GetCurrentProcess, OrgProc, #JmpBuffer, SizeOf(JmpBuffer), n) then
RaiseLastOSError;
end;
initialization
RedirectFunction(#TWinControl.SetFocus, #TWinControlHack.SetFocus);
end.
Alternatively
TWinControlEx = class helper for TWinControl
procedure SetFocus; reintroduce;
end;
with...
procedure TWinControlEx.SetFocus;
var
Parent: TCustomForm;
begin
if not CanFocus then Exit;
Parent := GetParentForm(Self);
if Parent <> nil then
Parent.FocusControl(Self)
else if ParentWindow <> 0 then
Winapi.Windows.SetFocus(Handle)
else
ValidParentForm(Self);
end;
My answer below does not answer DIRECTLY your question but it is still relevant because you rely on CanFocus. CanFocus returns a lie. You should not rely on it. The documentation is also wrong. More exactly, CanFocus can return True even if the control is not focusable. In this case an exception will be raised.
So, use this instead:
function CanFocus(Control: TWinControl): Boolean;
begin
Result:= Control.CanFocus AND Control.Enabled AND Control.Visible;
if Result
AND NOT Control.InheritsFrom(TForm)
then
{ Recursive call:
This control might be hosted by a panel which could be also invisible/disabled.
So, we need to check all the parents down the road, until we encounter the parent Form.
Also see: GetParentForm }
Result:= CanFocus(Control.Parent); { Parent of a control could be nil, but in this case Control.CanFocus will deal with that.}
end;
procedure SetFocus(Control: TWinControl);
begin
if CanFocus(Control)
then Control.SetFocus;
end;
PS: Under Lazarus CanFocus works properly.
Justification:
J provided a nice answer, but I don't like class helpers because if you have more than one class helper for the same class, the only one will be used. The process is almost "by dice": the order of the units in the "uses" clause determine which helper will apply. I don't like this amount of randomness in a programming language.
Just before my TCustomWinControl is destroyed permanently, I need to do somthing with its handle.
If I try to access its handle in the destructor I get an error:
"Control "xxx" has no parent window".
So what is the last stage before TWinControl Destructor where its handle (HandleAllocated) is still valid?
type
TPanel = class(ExtCtrls.TPanel)
protected
procedure DestroyWindowHandle; override;
public
procedure BeforeDestruction; override;
end;
procedure TPanel.DestroyWindowHandle;
begin
Beep;
if csDestroying in ComponentState then Beep;
inherited;
end;
procedure TPanel.BeforeDestruction;
begin
if HandleAllocated then Beep;
inherited;
end;
There is no Beep.
Update
It is more complex than I originally thought. Your control lives on a form, and the control's death is provoked by the death of that form. When a form is destroyed, the child windows are also destroyed. So, the Win32 API is responsible for destroying your window. The VCL keeps track of this by responding to the WM_NCDESTROY message:
procedure TWinControl.WMNCDestroy(var Message: TWMNCDestroy);
begin
inherited;
FHandle := 0;
FShowing := False;
end;
So, I guess you could handle WM_NCDESTROY yourself. Look for csRecreating in ControlState to switch behaviour based on whether or not the window destruction is related to VCL window re-creation.
An interesting point to note here is that there's no reason why the destructor of your control has to be called. If it is not owned by the form then your control won't be destroyed. You could then re-parent it onto another form. So WM_NCDESTROY really is the right hook.
Original answer
The source code of the destructor looks like this:
destructor TWinControl.Destroy;
var
I: Integer;
Instance: TControl;
begin
Destroying;
if FDockSite then
begin
FDockSite := False;
RegisterDockSite(Self, False);
end;
FDockManager := nil;
FDockClients.Free;
if Parent <> nil then RemoveFocus(True);
if FHandle <> 0 then DestroyWindowHandle;
I := ControlCount;
while I <> 0 do
begin
Instance := Controls[I - 1];
Remove(Instance);
Instance.Destroy;
I := ControlCount;
end;
FBrush.Free;
{$IFDEF LINUX}
if FObjectInstance <> nil then WinUtils.FreeObjectInstance(FObjectInstance);
{$ENDIF}
{$IFDEF MSWINDOWS}
if FObjectInstance <> nil then Classes.FreeObjectInstance(FObjectInstance);
{$ENDIF}
inherited Destroy;
end;
The call to the Win32 API DestroyWindow is made in this line:
if FHandle <> 0 then DestroyWindowHandle;
So you need to run your code before then.
You could override DestroyWindowHandle and do your work there. That would work well so long as the event you need to deal with is the destruction of the window. But bear in mind that DestroyWindowHandle will be called when the window is re-created.
If you need to do something related to the destruction of the VCL control, then you would be best overriding BeforeDestruction. Or as an alternative, you could override DestroyWindowHandle and in there test for csDestroying in the ComponentState.