一个C#程序调用一个我用Delphi编写的DLL:
[DllImport("ABCDEF.dll")]
private static extern void OpenDrawer();
Delphi中的DLL具有(除其他功能外)打开Epson POS打印机抽屉的功能。
它的DLLMain包含DLL_PROCESS_ATTACH
,它加载EpsStmApi.dll,它的DLL_PROCESS_DETACH
方法再次释放EpsStmApi.dll,并关闭打印机句柄(如果存在)。 DLL函数OpenDrawer将调用DLL函数并将打印机句柄另存为全局变量(这对于性能很重要)。
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.
到目前为止,它可以正常工作,但是我想知道它是否干净正确,因为我注意到永远不会调用DLL_PROCESS_DETACH
(我通过写入日志文件进行检查),因此打印机句柄保持打开状态。我认为这没什么大不了的,但是我不确定内存中是否有不正确的东西。
可能因为DLL本身已加载另一个DLL而没有调用DLL_PROCESS_DETACH
吗?
(注意:我没有直接通过C#调用EpsStmApi.dll的原因,这与本主题无关。)
答案 0 :(得分:4)
首先,您报告的内容无法轻易复制。当进程终止时,调用简单的Delphi DLL的简单C#控制台应用程序确实会触发DLL_PROCESS_DETACH
。
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();
}
}
}
类似地,如果此DLL托管在用LoadLibrary
加载并随后用FreeLibrary
卸载的可执行文件中,则原因为DllMain
的{{1}}调用会触发。很可能您只是在诊断中被误了。
如果您希望在加载和卸载DLL时执行任务,那么也许更简单的方法是将代码添加到单元DLL_PROCESS_DETACH
和initialization
节中。但是,该代码仍从finalization
调用,这严重限制了您可以执行的操作,这使我进入了下一个要点。
您正在违反DllMain
的规则。 DllMain
的文档说
入口点功能应仅执行简单的初始化或终止任务。它一定不能调用LoadLibrary或LoadLibraryEx函数(或调用这些函数的函数),因为这可能会以DLL加载顺序创建依赖关系循环。
我想至少在现在,您正在摆脱这种情况。但是事情很容易改变。将代码移至DllMain
和initialization
不会发生任何变化,因为它们仍是从finalization
调用的。
我强烈建议您删除所有DllMain
代码。如果希望延迟加载DLL,请使用DllMain
或将DLL的加载移至另一个执行初始化的导出函数中。