Daniel Marschall
Daniel Marschall

Reputation: 3879

DLL_PROCESS_DETACH won't get called?

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

Answers (1)

David Heffernan
David Heffernan

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

Related Questions