Reputation: 3879
A C# program calls a DLL which I have written in Delphi:
[DllImport("ABCDEF.dll")]
private static extern void OpenDrawer();
The DLL in Delphi has the function (among of other things) to open the drawer for an Epson POS printer.
Its DLLMain contains DLL_PROCESS_ATTACH
, which loads EpsStmApi.dll, and its DLL_PROCESS_DETACH
method frees EpsStmApi.dll again, as well as closes the printer handle, if one exists. The DLL-function OpenDrawer will call the DLL function and save the printer handle as global variable (this is important for the performance).
library Test;
var
hEpsonDLL: cardinal = 0; // DLL handle for EpsStmApi.dll
hMonPrinter: integer = 0; // Printer handle. Stays open while the DLL is loaded, for performance
type
TFuncBiOpenMonPrinter = function (nType: Integer; pName: LPSTR): Integer; stdcall;
TFuncBiOpenDrawer = function (nHandle: Integer; drawer: Byte; pulse: Byte): Integer; stdcall;
TFuncBiCloseMonPrinter = function (nHandle: Integer): Integer; stdcall;
var
funcBiOpenMonPrinter : TFuncBiOpenMonPrinter;
funcBiOpenDrawer : TFuncBiOpenDrawer;
funcBiCloseMonPrinter : TFuncBiCloseMonPrinter;
procedure OpenDrawer; stdcall; export;
begin
if hEpsonDLL <> 0 then
begin
// DLL missing. Probably no Epson printer installed.
exit;
end;
if hMonPrinter = 0 then
begin
// Initialize the printer.
@funcBiOpenMonPrinter := GetProcAddress(hEpsonDLL, 'BiOpenMonPrinter') ;
if Assigned(funcBiOpenMonPrinter) then hMonPrinter := funcBiOpenMonPrinter(TYPE_PRINTER, 'EPSON TM-T88V Receipt');
end;
if hMonPrinter <> 0 then
begin
// Try to open the drawer
@funcBiOpenDrawer := GetProcAddress(hEpsonDLL, 'BiOpenDrawer') ;
if Assigned(funcBiOpenDrawer) then funcBiOpenDrawer(hMonPrinter, EPS_BI_DRAWER_1, EPS_BI_PULSE_400);
end;
end;
procedure DllMain(reason: Integer);
begin
if reason = DLL_PROCESS_ATTACH then
begin
// Note: Calling BiOpenMonPrinter here will cause a deadlock.
hEpsonDLL := LoadLibrary('EpsStmApi.dll') ;
end
else if reason = DLL_PROCESS_DETACH then
begin
// Destroy the printer object
if hMonPrinter <> 0 then
begin
@funcBiCloseMonPrinter := GetProcAddress(hEpsonDLL, 'BiCloseMonPrinter') ;
if Assigned(funcBiCloseMonPrinter) then funcBiCloseMonPrinter(hMonPrinter);
end;
// Free the library
if hEpsonDLL <> 0 then FreeLibrary(hEpsonDLL);
end;
end;
exports
OpenDrawer;
begin
DllProc := DllMain;
DllMain(DLL_PROCESS_ATTACH);
end.
It works so far, but I wonder if it is clean and correct, because I noticed that DLL_PROCESS_DETACH
will never be called (I checked it by writing to log files), therefore the printer handle stays open. I do not think it is a big deal, but I am unsure if something is not correct in-memory.
Is it possible that DLL_PROCESS_DETACH
does not get called because the DLL itself has another DLL loaded?
(Note: There is a reason why I don't call EpsStmApi.dll via C# directly, which is not relevant to this topic.)
Upvotes: 0
Views: 1822
Reputation: 612844
To begin with, what you report cannot be readily reproduced. A simple C# console application that calls a trivial Delphi DLL does indeed fire DLL_PROCESS_DETACH
when the process terminates.
library Project1;
uses
Windows;
procedure foo; stdcall;
begin
end;
procedure DllMain(reason: Integer);
begin
if reason = DLL_PROCESS_ATTACH then begin
OutputDebugString('DLL_PROCESS_ATTACH');
end else if reason = DLL_PROCESS_DETACH then begin
OutputDebugString('DLL_PROCESS_DETACH');
end;
end;
exports
foo;
begin
DllProc := DllMain;
DllMain(DLL_PROCESS_ATTACH);
end.
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
[DllImport(@"Project1.dll")]
static extern void foo();
static void Main(string[] args)
{
foo();
}
}
}
Similarly if this DLL is hosted in an executable that loads it with LoadLibrary
and then unloads it with FreeLibrary
, the DllMain
call with reason DLL_PROCESS_DETACH
does fire. Most likely you are simply mistaken in your diagnosis.
If you wish to perform tasks when the DLL is loaded and unloaded, then perhaps a simpler way is to add code to a unit initialization
and finalization
sections. However, that code is still invoked from DllMain
which severely limits what you can do, leading me to my next point.
You are breaking the rules for DllMain
. The documentation for DllMain
says
The entry-point function should perform only simple initialization or termination tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order.
I guess you are getting away with this, at least for now. But things could easily change. Moving the code to initialization
and finalization
changes nothing because they are still invoked from DllMain
.
I strongly advise you to remove all of your DllMain
code. If you wish to delay load the DLL then do so using delayed
or move the loading of the DLL into another exported function that performs initialisation.
Upvotes: 6