DLL_PROCESS_DETACH 不会被调用?

Dan*_*all 0 delphi dll

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 是有原因的,这与本主题无关。)

Dav*_*nan 5

首先,您报告的内容无法轻易复制。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 时执行任务,那么可能更简单的方法是将代码添加到单元initializationfinalization节中。但是,仍然会调用该代码,DllMain这严重限制了您可以执行的操作,导致我进入下一点。

你违反了规则DllMain。的文档DllMain

入口函数应该只执行简单的初始化或终止任务。它不能调用 LoadLibrary 或 LoadLibraryEx 函数(或调用这些函数的函数),因为这可能会在 DLL 加载顺序中创建依赖循环。

我想你已经逃脱了,至少现在是这样。但事情很容易改变。将代码移动到initialization并且finalization没有任何改变,因为它们仍然是从DllMain.

我强烈建议您删除所有DllMain代码。如果您希望延迟加载 DLL,请使用delayed或将 DLL 的加载移动到另一个执行初始化的导出函数中。