Link to home
Start Free TrialLog in
Avatar of groms78
groms78

asked on

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
ASKER CERTIFIED SOLUTION
Avatar of Jacco
Jacco
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of groms78
groms78

ASKER

Thx for your answer,

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

Cyril
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
Avatar of groms78

ASKER

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

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
Oops TStubQuery should descend from TAbstractStubQuery
Avatar of groms78

ASKER

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
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
Avatar of groms78

ASKER

Hi Jacco,

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

Regards Cyril