AC# 程序调用我用 Delphi 编写的 DLL:
[DllImport("ABCDEF.dll")]
private static extern void OpenDrawer();
Run Code Online (Sandbox Code Playgroud)
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.
Run Code Online (Sandbox Code Playgroud)
到目前为止它有效,但我想知道它是否干净和正确,因为我注意到它DLL_PROCESS_DETACH
永远不会被调用(我通过写入日志文件来检查它),因此打印机句柄保持打开状态。我认为这没什么大不了的,但我不确定内存中是否有错误。是否有可能DLL_PROCESS_DETACH
因为 DLL 本身加载了另一个 DLL 而没有被调用?
(注意:我不直接通过 C# 调用 EpsStmApi.dll 是有原因的,这与本主题无关。)
首先,您报告的内容无法轻易复制。DLL_PROCESS_DETACH
当进程终止时,调用一个简单的 Delphi DLL 的简单 C# 控制台应用程序确实会触发。
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.
Run Code Online (Sandbox Code Playgroud)
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
[DllImport(@"Project1.dll")]
static extern void foo();
static void Main(string[] args)
{
foo();
}
}
}
Run Code Online (Sandbox Code Playgroud)
类似地,如果此 DLL 托管在一个可执行文件中,该可执行文件使用 加载它LoadLibrary
然后使用 卸载它FreeLibrary
,则DllMain
带有原因的调用DLL_PROCESS_DETACH
会触发。很可能您只是在诊断中出错了。
如果您希望在加载和卸载 DLL 时执行任务,那么可能更简单的方法是将代码添加到单元initialization
和finalization
节中。但是,仍然会调用该代码,DllMain
这严重限制了您可以执行的操作,导致我进入下一点。
你违反了规则DllMain
。的文档DllMain
说
入口函数应该只执行简单的初始化或终止任务。它不能调用 LoadLibrary 或 LoadLibraryEx 函数(或调用这些函数的函数),因为这可能会在 DLL 加载顺序中创建依赖循环。
我想你已经逃脱了,至少现在是这样。但事情很容易改变。将代码移动到initialization
并且finalization
没有任何改变,因为它们仍然是从DllMain
.
我强烈建议您删除所有DllMain
代码。如果您希望延迟加载 DLL,请使用delayed
或将 DLL 的加载移动到另一个执行初始化的导出函数中。