It seems pretty straightforward but the code below doesn't work.
BPL:
procedure DoSomething();
begin
LogEvent('Did');
end;
exports
DoSomething;
Main EXE:
procedure CallModuleFunc;
var
H: THandle;
P: procedure();
begin
H := LoadPackage('mymod.bpl');
try
if (H <> 0) then
begin
#P := GetProcAddress(H, 'DoSomething');
if Assigned(P) then
P();
end;
finally
UnloadPackage(H);
end;
end;
Now there's no error, the bpl loads successfully with LoadPackage() but GetProcAddress() returns nil. Why? probably because name mangling. I've tried adding stdcall (both exported function and P's declaration) but that didn't solve the problem. I've seen hundreds of examples on the web that it's supposed to be working this way. I even tried GetProcAddress(H, 'DoSomething$qqsv') but it didn't work either. What am I missing here?
After hours of searches and trial and errors, I realized it had to be about something I do or did differently. The problem was that my very first version of mymod.bpl was put into Delphi's default BPL output directory (which had no export, no DoSomething() at all). Then, I had changed the BPL output directory to my project's root source directory, so that I could see the sources and the bpl modules in one place. The exe was not put into where the sources reside as it used to be in Delphi 7, it was under Debug or Release folders. What misdirecting me was that when LoadPackage() cannot find the module in exe's current dir (which was Debug/Release) it looks at Delphi's default package folder (which had the very first and wrong version of the bpl) and loads it, so no error there but also no DoSomething() because it was no longer updated by my module's compile.
I hope this explanation helps someone else who might have a similar issue to figure things out. Thanks to everyone who spared time to read this and written comments.
See below a piece of code.
It's running under Delphi XE3.
// Package declaration
package Package1;
{$R *.res}
{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
{$DEBUGINFO ON}
{$EXTENDEDSYNTAX ON}
{$IMPORTEDDATA ON}
{$IOCHECKS ON}
{$LOCALSYMBOLS ON}
{$LONGSTRINGS ON}
{$OPENSTRINGS ON}
{$OPTIMIZATION OFF}
{$OVERFLOWCHECKS OFF}
{$RANGECHECKS OFF}
{$REFERENCEINFO ON}
{$SAFEDIVIDE OFF}
{$STACKFRAMES ON}
{$TYPEDADDRESS OFF}
{$VARSTRINGCHECKS ON}
{$WRITEABLECONST OFF}
{$MINENUMSIZE 1}
{$IMAGEBASE $400000}
{$DEFINE DEBUG}
{$ENDIF IMPLICITBUILDING}
{$IMPLICITBUILD ON}
requires
rtl,
vcl;
contains
Unit1 in 'Unit1.pas';
end.
The unit Unit1.pas
unit Unit1;
interface
uses Vcl.Forms;
procedure Test(); stdcall;
exports
Test;
implementation
procedure Test();
var F : TForm;
begin
F := TForm.Create(nil);
F.ShowModal;
F.Release;
end;
end.
The Project for testing bpl.
Contains only one Tform with one TButton.
(Package1.bpl is in the same directory of project1.exe)
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
type
TProcTest = procedure;
var
PackageModule: HModule;
proc : TProcTest;
begin
PackageModule := LoadPackage('Package1.bpl');
if PackageModule <> 0 then
begin
#Proc := GetProcAddress( PackageModule, 'Test' );
if #Proc <> nil then
Proc;
UnloadPackage(PackageModule);
end;
end;
end.
You must have to add stdCall in your declaration.
procedure DoSomething();stdcall;
begin
LogEvent('Did');
end;
exports
DoSomething;
Related
I have met a really weird behaviour using Delphi and DLL.
I want to learn how to create and use DLL's. To do so, I created two files: one containing the DLL, and the other one calling it.
The DLL retrieves informations from a *.ini file (the GetInfos function). When calling it in a form, I get the following error: "Access violation at address XXXXXXXX in "dlltest.dll" module. Read of address 00000000.
I wanted to try some other things, so I created a simple procedure, that just display a message.
The interesting thing is that when I call this procedure (which only purpose is to display a message), I no longer get the Access violation error.
I really do not understand how it can make my code work. I'd really like if someone could give me some explanation. I am using Delphi 10.3 Rio Version 26.0.36039.7899
Here is the code I produced :
dlltest:
library dlltest;
uses
System.SysUtils, System.Classes, System.IniFiles, Winapi.Windows, vcl.dialogs;
var
// Ini file var
iniConfig: TInifile;
procedure SayHello;
var
hello: PChar;
begin
hello := 'Hello world!';
ShowMessage(hello);
end;
// -----------------------------------------------------------------------------
// Retrieving .ini file
function GetIni: TInifile;
begin
result := TInifile.Create('.\monitoring.ini');
end;
// -----------------------------------------------------------------------------
function GetInfos: ShortString;
begin
iniConfig := GetIni;
try
result := iniConfig.ReadString('filename', 'start', 'start');
finally
iniConfig.Free;
end; { try }
end; { function GetInfos }
exports
SayHello, GetInfos;
begin
end.
Using dll:
unit testUtilisationDll;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils,
System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms,
Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
Form1: TForm1;
procedure SayHello; StdCall; external 'dlltest.dll';
function GetInfos: ShortString; StdCall;
external 'dlltest.dll';
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
SayHello;
ShowMessage(GetInfos(self));
end;
end.
When you export the functions, they are using the default calling convention (Register), whereas when you import them, you are telling the import that they're using StdCall calling convention.
The normal case for DLL exported functions is StdCall convention, so you should declare your functions to use this calling convention in your .DLL source:
function GetInfos: ShortString; stdcall;
and
procedure SayHello; stdcall;
Another problem is that when you call GetInfos:
ShowMessage(GetInfos(self));
you are passing it a parameter (self), but your declaration of the import:
function GetInfos: ShortString; StdCall; external 'dlltest.dll';
doesn't list any parameters. You won't be able to compile the program as it is shown in your question...
I am using Delphi 10.4. This is a Windows VCL Application.
I wanted to convert all my ShowMessage, MessageDlg and MessageBox calls to TaskDialogs in my program. When I tried to do that, I couldn't get TaskDialog to display anything.
So what I did was create a new minimal VCL application, simply added a button and a TaskDialog to it:
This was my code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
TaskDialog1: TTaskDialog;
procedure MyMessageBox;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
procedure TForm1.MyMessageBox;
begin
Form1.TaskDialog1.Caption := 'My Application';
Form1.TaskDialog1.Title := 'Hello World!';
Form1.TaskDialog1.Text := 'I am a TTaskDialog, that is, a wrapper for the Task Dialog introduced ' +
'in the Microsoft Windows Vista operating system. Am I not adorable?';
Form1.TaskDialog1.CommonButtons := [tcbClose];
Form1.TaskDialog1.Execute;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
MyMessageBox;
end;
{$R *.dfm}
begin
Application.Run;
end.
That worked fine. When running it and pressing Button1, I get:
So now I go to my application. I add a button to my main form, and set the MyMessageBox procedure to this:
procedure TLogoAppForm.MyMessageBox;
begin
ShowMessage('ShowMessage ......................................');
Application.MessageBox('Application.MessageBox ...........................', 'Error', 0);
MessageDlg('MessageDlg ................................', mtWarning, [mbOk], 0);
LogoAppForm.TaskDialog1.Caption := 'My Application';
LogoAppForm.TaskDialog1.Title := 'Hello World!';
LogoAppForm.TaskDialog1.Text := 'I am a TTaskDialog, that is, a wrapper for the Task Dialog introduced ' +
'in the Microsoft Windows Vista operating system. Am I not adorable?';
LogoAppForm.TaskDialog1.CommonButtons := [tcbClose];
LogoAppForm.TaskDialog1.Execute;
end;
Pressing the button in my application correctly brings up each of the ShowMessage, MessageBox and MessageDlg windows in sequence, but after closing the MessageDlg window, nothing at all appears for the TaskDialog.
Does anyone know what might be causing TaskDialog to not work in my application and how I might fix this?
You must enable runtime themes for the VCL TTaskDialog to work. Go to Project/Options/Application/Manifest to do so.
I want load a image that will be the background of a maximized Form that stays in a dll.
The dll is called from a Vcl Form Application but have a trouble where not is possible load the background image on Form, the dll always crashes.
Thank you by you help.
===========================================================================
Executable
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm2 = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
end;
var
Form2: TForm2;
implementation {$R *.dfm}
procedure LoadDLL;
type
TShowformPtr = procedure; stdcall;
var
HDLL: THandle;
Recv: TShowformPtr;
begin
HDLL := LoadLibrary('lib.dll');
if HDLL <> 0 then
begin
#Recv := GetProcAddress(HDLL, 'Recv');
if #Recv <> nil then
Recv;
end;
//FreeLibrary(HDLL);
end;
procedure TForm2.btn1Click(Sender: TObject);
begin
LoadDLL;
end;
end.
Dll
Main:
library Project2;
uses
SysUtils, Classes, Unit1, Unit2;
{$R *.res}
procedure Recv; stdcall;
begin
showform;
end;
exports
Recv;
begin
end.
Unit1 (Form):
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
img1: TImage;
pnl1: TPanel;
procedure FormShow(Sender: TObject);
private
{ Private declarations }
procedure CreateParams(var Params: TCreateParams); override;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.WndParent:= Application.Handle;
Params.ExStyle := Params.ExStyle or WS_EX_TOPMOST or WS_EX_TRANSPARENT;
Params.ExStyle := WS_EX_TRANSPARENT or WS_EX_TOPMOST;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
brush.Style := bsclear;
img1.Picture.LoadFromFile(IncludeTrailingBackslash(GetCurrentDir) + 'background.bmp');
SetWindowPos(Form1.handle, HWND_TOPMOST, Form1.Left, Form1.Top, Form1.Width,
Form1.Height, 0);
ShowWindow(Application.handle, SW_HIDE);
pnl1.Top := (self.Height div 2) - (pnl1.Height div 2);
pnl1.Left := (self.Width div 2) - (pnl1.Width div 2);
end;
end.
Unit2:
unit Unit2;
interface
Uses
windows,
Unit1,
SysUtils;
procedure showform;
implementation
procedure showform;
begin
Form1 := TForm1.Create(Form1);
sleep(100);
Form1.Show;
Form1.Pnl1.Visible := True;
end;
end.
Your question has a lot of problems, so I would try to answer it as best I can, considering the lack of details.
You are using forms so you are building a VCL application. You need to let the IDE assign the VCL framework to your project.
This line is terribly wrong:
Form1 := TForm1.Create(Form1);
In rare circumstances show a from own itself. I would go and say that most probably this is why your application crashes. See this for details about forms in DLLs.
If you cannot properly debug your application put a beep before that line and one after (make a delay between them).
I think your question should be rather called "how to debug a Delphi project".
What you need to do is to get the exact line on which the program crashes. This will give you an insight of why the error/crash (by the way, you never shown the exact error message) appears.
Go check HadShi (recommended) or EurekaLog (buggy) or Smartinspect (I never tried it. Price is similar to the other two). Make sure that you are running in debug mode, the Integrated debugger is on (see IDE options) and that the debug information is present in your EXE/DLL.
PS: you can still debug your app without have one of the three loggers shown above. Just configure your project properly to run in Debug mode!
To debug the DLL see the 'Run->Parameters' menu. Define there a host application that will load your DLL. If the error is the DLL, the debugger will take control and put the cursor to the line of code that generated the crash.
I don't know what the final purpose/what is that you want to achieve. Because of this I must warn you that you might need to take into consideration these questions:
Do you need to use ShareMM?
Why are you building this as a DLL? Can't the application be written as a single EXE? Or two EXEs that communicate with each other?
I have an application set up in the following manner in Delphi XE5:
main.exe: calls a function in sub.dll using the export delayed directive
function MyFunction: boolean; external 'sub.dll' delayed;
sub.dll: contains a FireDAC query object which runs a simple SELECT query.
Upon opening the query, with the delayed directive the application does not terminate when the main form is closed (process main.exe remains in task manager). Process explorer shows a thread remaining for sub.dll. The main.exe process terminates correctly when I do not specify the delayed directive. What am I missing? I feel like I'm not freeing an object but I can't figure out what it is.
Simplified code:
Main.exe:
program Main;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
function MyFunction: boolean; external 'Sub.dll' delayed;
begin
try
MyFunction;
except
on E: Exception do begin
Writeln(E.ClassName, ': ', E.Message);
readln;
end;
end;
end.
Sub.dll
library Sub;
uses
System.SysUtils,
System.Classes,
DBConn in 'DBConn.pas';
{$R *.res}
function MyFunction: boolean; export;
var Conn: TConn;
begin
Conn := TConn.Create;
Conn.Destroy;
Result := True;
end;
exports
MyFunction;
begin
end.
DBConn.pas
unit DBConn;
interface
uses
FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def,
FireDAC.Phys, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Stan.Param,
FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.VCLUI.Wait,
FireDAC.Comp.UI, FireDAC.Phys.ODBCBase, FireDAC.Phys.ASA, Data.DB,
FireDAC.Comp.DataSet, FireDAC.Comp.Client;
type
TConn = class
FDConnection: TFDConnection;
FDQuery: TFDQuery;
constructor Create;
destructor Destroy; override;
end;
var
Conn: TConn;
{ TConn }
implementation
constructor TConn.Create;
begin
FDConnection := TFDConnection.Create(nil);
//Set database connection parameters
with FDConnection do begin
close; Params.Clear;
Params.Add('DriverID=ASA');
Params.Add('Database=');
Params.Add('Server=');
Params.Add('USER_NAME=');
Params.Add('PASSWORD=');
open;
end;
FDQuery := TFDQuery.Create(nil);
with FDQuery do begin
Connection := FDConnection;
close; unprepare; SQL.Clear;
SQL.Add('Select first LAST_NAME');
SQL.Add('From USERS');
SQL.Add('Order By LAST_NAME');
prepare; open; //this causes the deadlock
writeln(Output, FieldByName('LAST_NAME').AsString);
end;
end;
destructor TConn.Destroy;
begin
FDConnection.Close;
FDConnection.Free;
inherited;
end;
end.
This is a FireDAC limitation. See http://docwiki.embarcadero.com/RADStudio/XE8/en/DLL_Development_(FireDAC)#FireDAC_DLL_Unloading
VCL will reliably work in Dll only if your main Exe and Dll are compiled in the same version of Delphi with runtime packages enabled for them.
I can't figure out how to detect memory leaks in a statically or even dynamically linked dll. I just want to detect the leaks in the dll, and I don't want to share the memory manager between the dll, and the app. Additionally the dll is linked with runtime packages
My sample dll looks like this:
library dll;
uses
fastmm4,
System.SysUtils,
System.Classes;
{$R *.res}
procedure MyInit; stdcall;
Begin
TObject.Create;
End;
exports MyInit;
begin
end.
application dpr:
program app;
uses
//fastmm4,
Vcl.Forms,
main in 'main.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Note: If I uncomment fastmm4, than I can detect the memleak caused by the application (TStringList.Create), but not the leak in the dll.
And in the application main unit:
unit main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
LDLLHandle: HModule;
LShowProc: TProcedure;
end;
var
Form1: TForm1;
{$ifdef static}
procedure MyInit; stdcall; external 'dll.dll';
{$endif}
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
TStringList.Create;
{$ifdef static}
MyInit;
{$else}
LDLLHandle := LoadLibrary('dll.dll');
if LDLLHandle <> 0 then
begin
try
LShowProc := GetProcAddress(LDLLHandle, 'MyInit');
if Assigned(LShowProc) then
LShowProc;
finally
FreeLibrary(LDLLHandle);
end;
end;
{$endif}
end;
end.
I expect from FastMM to generate a report when FreeLibrary is called, or on program exit, if the dll is statically loaded, but nothing happens.
In the FastMM4Options.inc I additionally just set FullDebugMode and ClearLogFileOnStartup, and the FastMM_FullDebugMode.dll is in the output directory.
I created a repository on github. What am I missing?
The reason that your DLL is not reporting leaks stems from this code in the FastMM shutdown:
CheckBlocksOnShutdown(
{$ifdef EnableMemoryLeakReporting}
True
{$ifdef RequireIDEPresenceForLeakReporting}
and DelphiIsRunning
{$endif}
{$ifdef RequireDebuggerPresenceForLeakReporting}
and ((DebugHook <> 0)
{$ifdef PatchBCBTerminate}
or (Assigned(pCppDebugHook) and (pCppDebugHook^ <> 0))
{$endif PatchBCBTerminate}
)
{$endif}
{$ifdef ManualLeakReportingControl}
and ReportMemoryLeaksOnShutdown
{$endif}
{$else}
False
{$endif}
);
In your options, RequireDebuggerPresenceForLeakReporting is defined. What's more, in the DLL, DebugHook is equal to 0, presumably because you are debugging the application rather than the DLL. This means that you call CheckBlocksOnShutdown passing False. And that False disables reporting of leaks.
You can resolve this by undefining RequireDebuggerPresenceForLeakReporting.
I just test it with version Fast Memory Manager 4.97 on Delphi2010 - win7
FastMM4 is the first unit in the 'uses' clause of the .dpr (project and dll)
'ShareMM' option is enabled
'AttemptToUseSharedMM' option is enabled
'EnableMemoryLeakReporting' option is enabled
Add FastMM_FullDebugMode.dll in the folder of the exe
There is also a test demo 'Dynamically Loaded DLL'
This demo is without the ShareMem.
I must set the option 'ShareMM' and 'AttemptToUseSharedMM' enabled and add the FastMM_FullDebugMode.dll to have a leak report of FastMM.