* * *

Author Topic: Form's close buton dos not work when form in DLL  (Read 1130 times)

vejrous

  • Newbie
  • Posts: 4
Form's close buton dos not work when form in DLL
« on: August 30, 2017, 01:22:02 am »
I have form in DLL. Form shows OK and works but I am unnable to close it in any way (not from Form close menu button or from a button on form with callin Close procedure).

DLL Code:
  TForm1 = class(TForm)
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  private
    { private declarations }
  public
    { public declarations }
  end;   

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  CloseAction := caFree;
end;

procedure ShowForm; stdcall;
var
  frm: TForm1;
begin
  frm := TForm1.Create(nil);
  frm.Show;
end;

exports
   ShowForm;

end.

When form is in exe code works.

GetMem

  • Hero Member
  • *****
  • Posts: 2248
Re: Form's close buton dos not work when form in DLL
« Reply #1 on: August 30, 2017, 07:04:24 am »
I seriously doubt somebody can help without seeing more code. Please read this: http://wiki.freepascal.org/Form_in_DLL or test the attachment from here:  http://forum.lazarus.freepascal.org/index.php/topic,30252.msg192411.html#msg192411 . You can also post your project if you like.

vejrous

  • Newbie
  • Posts: 4
Re: Form's close buton dos not work when form in DLL
« Reply #2 on: August 30, 2017, 10:28:56 am »
I read articles that you mention before I posted.
Main application is not written in pascal, DLL is called mainly from VBA, VB and pascal (Delphi).

I am transfering this DLL from Delphi 2006, where this code is enought to work (caFree just frees the form and on Destroy you free all attached Datamodules or you set Datamodel owner to Form).
There are of course some things (VBA and VB does not have all declarations same, example vba bool is pascal wordbool, etc). Lazarus also ads problem witch string coding - VBA and Delphi uses system - solved.

Adding Self.Free to FormClose does the job, but produce error when running with Heaptrc (memory checking -gh), ie no good.

Using this code in exe works OK, so I am missing something Lazarus specific.

All missing DLL code:
Code: Pascal  [Select]
  1. library ppvtCmd;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   Interfaces, Classes, LCLType, StdCtrls, Controls, Forms, Dialogs,
  7.   { you can add units after this }
  8.   Unit1;
  9.  
  10. {$R *.res}
  11.  
  12. begin
  13.   Application.Initialize;
  14. end.
  15.  
Code: Pascal  [Select]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  16.   private
  17.     { private declarations }
  18.   public
  19.     { public declarations }
  20.   end;
  21.  
  22. implementation
  23.  
  24. {$R *.lfm}
  25.  
  26. { TForm1 }
  27.  
  28. procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  29. begin
  30.   CloseAction := caFree;
  31. end;
  32.  
  33. procedure ShowForm; stdcall;
  34. var
  35.   frm: TForm1;
  36. begin
  37.   frm := TForm1.Create(nil);
  38.   frm.Show;
  39. end;
  40.  
  41. exports
  42.         ShowForm;
  43. end.
  44.  
There is no pascal code for the main App.
« Last Edit: August 30, 2017, 11:44:38 pm by vejrous »

vejrous

  • Newbie
  • Posts: 4
Re: Form's close buton dos not work when form in DLL
« Reply #3 on: August 30, 2017, 07:29:03 pm »
To siplify question: is there a way to use DLL with forms made under Lazarus, that will be called from other programing languages (mostly VBA)?
Has anyone succesfully used this?

jamie

  • Jr. Member
  • **
  • Posts: 54
Re: Form's close buton dos not work when form in DLL
« Reply #4 on: September 02, 2017, 12:36:16 am »
The Instant object variable is inside the calling function you use from the outside.,. when that
function returns, the call variable is lost in stack land somewhere.

 Put your FRM variable outside so the DLL can keep a constant valid reference to it.

 Also, any functionality, members etc should be called via this variable..

That was be my first correction. without seeing more of your code who can say different but
you need to place the instant variable in DLL heap space not stack space..

Mick

  • New member
  • *
  • Posts: 34
Re: Form's close buton dos not work when form in DLL
« Reply #5 on: September 02, 2017, 01:16:25 am »
@jamie, it has nothing to do with the variable scope of the form instance. Global variable will not help in any way, it can possibly (in some usage scenarios) even make things worse. Imagine you have called that function twice - the second call would overwrite the global reference set by the first call.

And by the way, as far as I know declaring the reference type locally in the method does not mean that the variable (object instance) will be on stack. The reference (the address of the object instance) will be, indeed, but not the object instance itself. Objects are always allocated on the heap. Someone will correct me if I am wrong, but I am pretty sure it is that way.

But, to the point. @vejrous, forms in DLLs are not so easy. The main problem here is that the form is not created by the Application.CreateForm method, but instantiated directly. Your application has no main form. So the form lifecycle is not as you could expect.

You can try to solve it by trying to instantiate the form with the Appication.CreateForm, but, to be honest, I have never tried this approach. Instead, you can try to use a bit different way that I have successfully used in the past for forms in DLLs.

It is using the ShowModal method call instead of calling Show. Try this approach. But I am not sure if it will work with Lazarus' implementation of application and forms lifecycle, for me it worked long time ago with Delphi. But at least give it a try, I believe that Lazarus is not so much different from Delphi.

Code: Pascal  [Select]
  1. procedure ShowForm; stdcall;
  2. begin
  3.   with TForm1.Create(nil) do
  4.   try
  5.     ShowModal();
  6.   finally
  7.     Free();
  8.   end;
  9. end;
  10.  

Using ShowModal instead would also resolve some other issues with your old original approach. Because there is an extra issue with using the method Show that you have probably missed. If you expect that your function exported by DLL will not return until the form is alive, then you are wrong. In reality, the function will return immediately after calling the Show method. I mean that the execution will be outside of the exported function, but the form will be still alive. ShowModal uses its own message loop, so the function will need to wait until ShowModal call is finished and will only return after you have closed the modal form.
« Last Edit: September 02, 2017, 01:40:10 am by Mick »

jamie

  • Jr. Member
  • **
  • Posts: 54
Re: Form's close buton dos not work when form in DLL
« Reply #6 on: September 02, 2017, 01:42:56 am »
regardless of other issues, the fact remains you need to first fix the object variable..

 I did make comment that has to be fixed first.

 I did not say it will correct the issue..

 btw. I have done forms in DLL's and I don't remember this issue. Of course that was done in
Delphi..
 the simplest thing to do as a debugger approach is to first test if the form.Close event is
getting fired.

 I would do that by putting in a BEEP;, simplest thing there is to do.

 The DLL sits on a different Instant space and thus may need its own message pump.

jamie

  • Jr. Member
  • **
  • Posts: 54
Re: Form's close buton dos not work when form in DLL
« Reply #7 on: September 02, 2017, 02:24:33 am »
I wanted to add to this, since there Is no local managed instant variable how is the
OnClose event handler called? Last time I knew you have to have valid instant to set the
handler so when the event takes place, so it'll get called. I have no idea how it is getting called here..
must be voodoo .

Mick

  • New member
  • *
  • Posts: 34
Re: Form's close buton dos not work when form in DLL
« Reply #8 on: September 02, 2017, 02:50:27 am »
It is not a voodoo. The OnClose handler is called by the form instance itself. The instance can be created with, or without assigning the result of the constructor call to a local or global (does not matter here) variable. The fact, that the object reference (being the result of the constructor call) is not stored in any variable, does not mean that there is no object instance. There is one, because it was instantiated with a constructor call. The instance (form) lives its own life since then, even when there was no "explicit assignment". And it is able to call that OnClose handler, because it manages its lifecycle on its own.
« Last Edit: September 02, 2017, 02:53:02 am by Mick »

jamie

  • Jr. Member
  • **
  • Posts: 54
Re: Form's close buton dos not work when form in DLL
« Reply #9 on: September 02, 2017, 05:02:21 am »
 have it your way, I find it hard to believe coders would do that. That maybe fine for a test
case but it won't fly down the road when you want to start accessing members that requires it.

 Btw, I played with that example, built a Dll and GUI app..

 I can get the form to close with a little hacking. The OnClose event does get called, the problem
is the Rest of the process does not..

 You can issue a HIDE in that event and later if you show the form again test for the existence of
it first and show it or create it. But then again that would mean keeping the instant available in
the DLL.

 I think posting a WM_DESTROY message will take care of it in the OnClose event. have not tested that yet




jamie

  • Jr. Member
  • **
  • Posts: 54
Re: Form's close buton dos not work when form in DLL
« Reply #10 on: September 02, 2017, 05:11:36 am »
Ok, There is something weird about how the LCL handles messages..

Posting a WM_DESTORY just creates a duplicated form.

However, I did find that DestroyWindow(FOrm.Handle) works when used inside the
OnClose event.

 EDIT:
  The implementation of messages in the form are flawed, using DestoryWindow does not call the
destructor but does make it all go away.
  calling Free also frees it. Implementing a user message into another class can also be used to free it. oh well..

 so there you have it, I've done my part of debugging. I have a working version.
 
« Last Edit: September 02, 2017, 05:17:48 am by jamie »

jamie

  • Jr. Member
  • **
  • Posts: 54
Re: Form's close buton dos not work when form in DLL
« Reply #11 on: September 02, 2017, 11:20:27 pm »
I wish I understood how to post code in a code editor window..I am sure someone will inform me..
Here is my version of a Form in a DLL.

library DLLMAin;

{$mode Delphi}{$H+}
Uses Classes, forms, Interfaces, Windows,Sysutils;
Type
  Ttest = Class(TCustomForm)
    Private
     Procedure FOnCloseProc(Sender:Tobject; Var aAction:TCloseAction);
    Public
     Destructor destroy; override;
  end;
Var
  T:Ttest;
  OldExitProc:Pointer;
 Procedure DLLSHOW; StdCall; Export;
 Begin
   RequireDerivedFormResource:= False;
   T.Free;
   T := Ttest.CreateNew(Nil);
   T.OnCLose := T.fOnCloseProc;
   T.SetBounds(0,0, 300,300);
   T.Show;
 end;
 Destructor TTest.Destroy;
 Begin
   If Self <> nil then inherited Destroy;
   Self := nil;
 end;
 Procedure NewExitProc;
 Begin
   T.Free;
   ExitProc := OldExitProc;
 end;

 Procedure Ttest.fOnCloseProc(Sender:Tobject; Var AAction:TCloseAction);
  Begin
     aAction := caFree;
     DestroyWindow(Handle);
  End;
Exports
 DLLSHOW INDEX 1;
Begin
  T := nil;
  OldExitProc := ExitProc;
  ExitProc := @NewExitProc;
  Application.initialize;
end.

Mick

  • New member
  • *
  • Posts: 34
Re: Form's close buton dos not work when form in DLL
« Reply #12 on: September 03, 2017, 02:10:27 am »
I wish I understood how to post code in a code editor window..I am sure someone will inform me..
To post code use "Insert code" tool button (labeled "#") or "Code" combobox in the post editor.

have it your way, I find it hard to believe coders would do that. That maybe fine for a test
case but it won't fly down the road when you want to start accessing members that requires it.
It is just the matter of design. In my opinion the functions that are exported by DLLs and manipulate resources internally should be self-enclosed, independent entities. They should be treated as black-boxes and used in "call and forget" approach, without worrying about the memory or other resources management (these should be handled by the function itself, if possible).

But if you are planning to do an "interactive" approach (e.g. export some other functions that would manipulate the state of the form created by calling the ShowForm function), then you should design your DLL interface carefully. Instead of exporting a procedure that shows the form you should rather transform it into a function that would return the reference to the created form. Methods that would manipulate the form should take the reference as the input parameter. This way you will be able to create, show and manipulate many instances of the created form. And (for example) close them.

As for your code posted - with your current approach you will overwrite the global variable T when you will call the DLLSHOW procedure for the second time when the form created in previous call is still "alive". It will make a mess and most probably memory and other resource leaks. As for the rest of the details of your implementation (especially the DestroyWindow call) I cannot tell anything, because I did not test your code.

Forest

  • New member
  • *
  • Posts: 13
Re: Form's close buton dos not work when form in DLL
« Reply #13 on: September 03, 2017, 10:10:16 am »
Try this http://wiki.freepascal.org/Form_in_DLL and if works with your VBA application then I guess you have to implement Application processing of signals inside DLL (not necessarily the construction of controls on runtime).

jamie

  • Jr. Member
  • **
  • Posts: 54
Re: Form's close buton dos not work when form in DLL
« Reply #14 on: September 03, 2017, 04:13:41 pm »
There is an issue with creating a form with no owner, one that shouldn't exist.
If you use the standard approach via the Create(Nil), it does a stack depletion dump and that is
because someone while doing some midnight code decided to do some recursive with no
regard of doing some check points.
 
 Basically the form, which should be able to operate without an owner can not. Delphi does this
perfectly.
 So sometime in it's past the TcustomForm class was edited in the Create constructor and hacked.
 
Create calls a Craetenew and in Createnew it Calls Create. this just loops if certain conditions aren't
meant and those are operations that take place if there is an owner..

 I have requested to those that know more about the compiler than I do if there is a ready  made
function that can return the returning address or the Code where it was called from, this way it can be used to determine if this is taking place and do appropriate actions to avoid such a mess.

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus