Problem with MDIChildren created into DLLs

Hi there,

I have got a very very stange problem with delphi 7.
I created a main application (which is a test application) whose aim is to call a DLL. This DLL has only one exported procedure and creates MDIChildren in the form of the main application. The main application has only 2 buttons :
  - the first button is for loading and show the newly created MDIChild
  - the second button is for closing and unloading the MDIChild form
I have tested 3 ways of working but one of then ends up into an access violation.

First way (no access violation)
1 - click on the first button => DLL successfully loaded and MDIChild successfully created.
2 - manually closing of the MDIChild
3 - click on then second button => DLL successfully unloaded

Second way (access violation)
1 - click on the first button => DLL successfully loaded and MDIChild successfully created.
2 - click on then second button => the MDIChild is closed in the 'DLL_PROCESSS_DETACH' event and then the DLL unloading is launched => access violation

Third way (no access violation) ?????????!!!!!!
for this test, the DLL code has been changed and a simple and tiny 'showmessage' has been added in the 'DLL_PROCESSS_DETACH' event.
1 - click on the first button => DLL successfully loaded and MDIChild successfully created.
2 - click on then second button => the MDIChild is closed in the event 'DLL_PROCESSS_DETACH', the message appears and then the DLL successfully loaded

Unfortunately, I would like the second to be working. Do you have any clue that would help me correcting this very starnge access violation ?

Thx,
Cyril
groms78Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

JaccoCommented:
Have you tried using Release in stead of Free or Close (I don't know what close ends up using). Release handles pending messages before actually freeing.

Also important: You have to work with packages to have this working properly otherwise you have 2 instances of Forms.Screen...

Regards Jacco

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
groms78Author Commented:
Thx for your answer,

What does 'packages' mean?
How to work with packages?

Cyril
JaccoCommented:
Thanks :)

In the compiler options you can specify that you want to compile with packages. Meaning that you get a smaller EXE, but you need to distribute any BPL (packages) with your EXE to have it working on a machine without Delphi. BPLs are essentially DLLs with a bit more intelligence. They "know" about each other and can use classes of each other.

If you do not work with packages you EXE will contain Forms.pas and your DLL also. So there will be two distinct different global variables Screen. This will eventually get you in trouble because a lot of Delphi units rely on global variables. If you work with packages you will not experience this problem.

Regards Jacco
Exploring ASP.NET Core: Fundamentals

Learn to build web apps and services, IoT apps, and mobile backends by covering the fundamentals of ASP.NET Core and  exploring the core foundations for app libraries.

groms78Author Commented:
Hi Jacco,

If I want to compile with packages, I will have to change all my installation programs in order to be able to get my DLL application working on our clients computers. I would be to much work... Anyway, if it is the only solution, how many extra files are needed with my .dll file?

I read, in an article, that the MDIChildren in DLL can work only if we pass the 'Screen' variable from my application to the DLL. I tried but, unfortunately, when I wanted to use 'hint' on TEdit, I got the exception: EconvertError. And by using this variable, it didn't solve my access violation problem...

Regards Cyril

JaccoCommented:
Hi Cyril,

You can try to see how many packages are needed by building with packages and then choose project/project information and look under packages used.

If you pass screen to the DLL you might also consider passing Application.

I have had a solution involving Forms working once. But that came queries on the database. I wanted to have them in a DLL too. I noticed all DB units got compiled in both the EXE and the DLL, that is not what DLLs are for I thought. So I turned the whole thing around. Essentially I created a record of helper functions like

function CreateForm: TStubForm;
function CreateQuery: TStubQuery;

I passed a record of these funtions to the DLL so it could create the stub classes.

The stub classes are compiled in the DLL as pure virtual classes, in the EXE subclasses of the pure virtual classes are implemented wrapping around real Forms and Queries.

I only implemeted those methods needed in the DLL.

Example:

unit AbstractStubs;

type
  TAbstractStubQuery = class
  public
    procedure ExecSQL; virtual; abstract;
    procedure SetDatabaseName(const aDatabaseName: string); virtual; abstract;
    procedure SetSQL(const aSQL: string); virtual; abstract;
  end;

type
  TCallBacks = record
    CreateStubQuery: function: TAbstractStubQuery;
  end;

unit Stubs;

type
  TStubQuery = class
  private
    fQuery: TQuery;
  public
    procedure ExecSQL; override;
    procedure SetDatabaseName(const aDatabaseName: string); override;
    procedure SetSQL(const aSQL: string); override
  end;

  function CreateStubQuery: TAbstractStubQuery;

const
  CallBacks: TCallBacks = (
    CreateStubQuery = @CreateStubQuery
  )


Now the when the DLL needs a Query it can call back to the application to create one. You can do the same form forms.

Borland implemented BPL to overcome this hassle. Without packages I believe it is the only way.

Regards Jacco
JaccoCommented:
Oops TStubQuery should descend from TAbstractStubQuery
groms78Author Commented:
Ok, but to do so, I have to include some DLL units in the main application, hasn't it?

Anyway, I do not konw if I can implement your code to solve my problem.
Just to be clear, I will give you but the code I use in the following lines:


MainApplication (calling application)
--------------------------------------------

procedure CallDLL();
type
  TProc = procedure(ParentApplication: TApplication; ParentScreen: TScreen;
                    ParentForm: TForm; otherParam : String); stdcall;
const
  ctProcName = 'ShowMDIWindow';
  ctDLL = 'mydll.dll';
var
  myProc : TProc;
  hDLL : THandle;
begin
      hDLL := LoadLibrary(PChar(ctDLL));
      if hDLL >= 32 then
      begin
        try
          @myProc := GetProcAddress(hDLL, ctProcName);
        except
          on E:Exception do
          begin
            MessageDlg('Unable to find "' + ctProcName + '" entry point into DLL: ' + sShortFileName,
                       mtError, [mbOk], 0);
          end;
        end;
      end;

      try
        myProc(Application, Screen, Form, 'useless');
      except
        on E:Exception do
        begin
          MessageDlg('Error while loading DLL procedure: ' + ctProcName,
                     mtError, [mbOk], 0);
        end;
      end;
end;

--------------------------------------------------------------------------------

mydll.dpr
-----------

library mcoass;

uses
  ShareMem,  //CJ 27/08/03
  Windows,
  Messages,
  SysUtils,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  Activex,
  childWin in 'childWin.pas' {FormAssistant}; // the unit including my MDIForm in this DLL

{$R *.res}

var
 oApp : TApplication;
// oScr : TScreen;

procedure ShowMDIWindow(ParentApplication: TApplication; ParentScreen: TScreen;
                    ParentForm: TForm; otherParam : String); stdcall;
begin
 Application := ParentApplication;
// Screen := ParentScreen;  // CJ 28/08/03

 FormAssistant:=nil;
 for i:=0 to ParentForm.ComponentCount-1 do
 begin
    if(ParentForm.Components[i] is TFormAssistant)then
    begin
      FormAssistant:=ParentForm.Components[i] as TFormAssistant;
      break;
    end;
 end;

 if not Assigned(FormAssistant) then
 begin
   ParentApplication.CreateForm(TFormAssistant, FormAssistant);
   if Assigned(FormAssistant) then
   begin
      FormAssistant.Init(otherParam);
   end;
 end;

 if Assigned(FormAssistant) and (FormAssistant.Initialized) then
 begin
  FormAssistant.Show;
  FormAssistant.BringToFront;
 end;
end;


procedure DLLUnloadProc(Reason: Integer);
begin
 if Reason = DLL_PROCESS_DETACH then
 begin
   if Assigned(FormAssistant) then
   begin
     FormAssistant.Close;
     begin
       MessageDlg('the message that allows the DLL not to hang!', mtInformation, [mbOk], 0); // the so-called message!!!!
     end;
   end;

   Application := oApp;
//   Screen := oScr;
 end;
end;


exports
 ShowMDIWindow;

begin
 oApp := Application;
// oScr := Screen;
 DLLProc := @DLLUnloadProc;
end.

--------------------------------------------------------------------------------

childWin.pas
----------------

unit childWin;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ActiveX, ComCtrls, ImgList,InfosComp,  Menus, StdCtrls, ExtCtrls,
  Mask, Buttons;

type
  TFormAssistant = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Déclarations privées }
  public
    { Déclarations publiques }
    Initialized : Boolean;
    function Init(myOtherParam : String): Integer;
  end;

var
 FormAssistant: TFormAssistant=nil;
// DllApplication: TApplication;
 DeskCommands: OleVariant;
 oPDOSSIER : OleVariant;

const
 ctSpecDLL = 'COM_DLL.dll';

implementation

{$R *.dfm}

procedure TFormAssistant.FormCreate(Sender: TObject);
begin
 Initialized := False;
end;

procedure TFormAssistant.FormClose(Sender: TObject; var Action: TCloseAction);
var
  hDLL: THandle;
begin
 oPDOSSIER := unassigned;
 oPFLOTTE  := unassigned;

 hDLL := GetModuleHandle(ctSpecDLL);
 if hDLL >= 32 then FreeLibrary(hDLL);

 DeskCommands := unassigned;

 CoUninitialize;

 Initialized := False;

 FormAssistant := nil;
 action:=cafree;
end;

function TFormAssistant.Init(myOtherParam : String): Integer;
begin
  CoInitialize(nil);

  WinExecAndWait32(Pchar('regsvr32.exe ' + ctSpecDLL + ' -s'), SW_HIDE); // function that calls 'CreateProcess' and waits until the process is running

  oPDOSSIER := CreateOleObject('COM_DLL.PDOSSIER');
  DeskCommands:= CreateOleObject('<MainApplication>.Commands'); // my MainApplication that calls the DLL is called by the DLL using COM!
end;

end.

--------------------------------------------------------------------------------

That's it!!!!!!
Can you see the problem?
As you will see in this code, my main application calls a function included in the DLL (mydll.dll).
This DLL:
  - initializes COM
  - registers and calls another DLL COM
  - calls the main application by COM
I haven't tried your proposition yet (the one with the abstract classes and so on) cause I don't know how to implement it in my DLL.

Thx for everything!
Cyril
JaccoCommented:
Hi Cyril,

>Ok, but to do so, I have to include some DLL units in the main application, hasn't it?
Yes, but only the abstract classes.


The EConvertError is caused by TFont in the application being a different class then the TFont class in the DLL.

Essentially the TFont class may only exist in one of the two otherwise the "is" operator will return false when comparing a TFont created in the DLL with one from the EXE. So the unit Graphics may only be used in one of the two, which also means Forms may only be used in one...

Again I think packages are the only solution.

Regards Jacco
groms78Author Commented:
Hi Jacco,

Thanks for you advices. I'll try to use BPL with the main application and the DLL.

Regards Cyril
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.