不会调用DLL_PROCESS_DETACH吗?

时间:2018-08-02 07:00:24

标签: delphi dll

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

1 个答案:

答案 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_DETACHinitialization节中。但是,该代码仍从finalization调用,这严重限制了您可以执行的操作,这使我进入了下一个要点。

您正在违反DllMain的规则。 DllMain的文档说

  

入口点功能应仅执行简单的初始化或终止任务。它一定不能调用LoadLibrary或LoadLibraryEx函数(或调用这些函数的函数),因为这可能会以DLL加载顺序创建依赖关系循环。

我想至少在现在,您正在摆脱这种情况。但是事情很容易改变。将代码移至DllMaininitialization不会发生任何变化,因为它们仍是从finalization调用的。

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