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
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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
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
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
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
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
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(ParentApplicatio n: 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(ParentApplic ation: 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.CreateFo rm(TFormAs sistant, FormAssistant);
if Assigned(FormAssistant) then
begin
FormAssistant.Init(otherPa ram);
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(S ender: 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(myOthe rParam : String): Integer;
begin
CoInitialize(nil);
WinExecAndWait32(Pchar('re gsvr32.exe ' + ctSpecDLL + ' -s'), SW_HIDE); // function that calls 'CreateProcess' and waits until the process is running
oPDOSSIER := CreateOleObject('COM_DLL.P DOSSIER');
DeskCommands:= CreateOleObject('<MainAppl ication>.C ommands'); // 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
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(ParentApplicatio
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(ParentApplic
ParentForm: TForm; otherParam : String); stdcall;
begin
Application := ParentApplication;
// Screen := ParentScreen; // CJ 28/08/03
FormAssistant:=nil;
for i:=0 to ParentForm.ComponentCount-
begin
if(ParentForm.Components[i
begin
FormAssistant:=ParentForm.
break;
end;
end;
if not Assigned(FormAssistant) then
begin
ParentApplication.CreateFo
if Assigned(FormAssistant) then
begin
FormAssistant.Init(otherPa
end;
end;
if Assigned(FormAssistant) and (FormAssistant.Initialized
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(
begin
Initialized := False;
end;
procedure TFormAssistant.FormClose(S
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(myOthe
begin
CoInitialize(nil);
WinExecAndWait32(Pchar('re
oPDOSSIER := CreateOleObject('COM_DLL.P
DeskCommands:= CreateOleObject('<MainAppl
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
>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
ASKER
Hi Jacco,
Thanks for you advices. I'll try to use BPL with the main application and the DLL.
Regards Cyril
Thanks for you advices. I'll try to use BPL with the main application and the DLL.
Regards Cyril
ASKER
What does 'packages' mean?
How to work with packages?
Cyril