释放 DLL 时发生访问冲突

Access violation when releasing DLL

我有一个使用计算机名称的函数,它在外部 DLL 中。并且在我的程序中调用了这个函数,但是使用完函数后无法释放DLL

DLL + 函数

function NAMEPC: String;
var lpBuffer : PChar;
    nSize : DWord;
const Buff_Size = MAX_COMPUTERNAME_LENGTH + 1;
begin
  nSize := Buff_Size;
  lpBuffer := StrAlloc(Buff_Size);
  GetComputerName(lpBuffer,nSize);
  Result := String(lpBuffer);
  StrDispose(lpBuffer);
end;

exports
 NAMEPC;

// *******************************************

调用DLL的函数

function CALLNAMEPC: String;
var Handle: THandle;
    mFDolly: function: String;
begin
 Handle := LoadLibrary(PChar('DLL.dll'));
 try
   mFDolly := GetProcAddress(Handle, 'NAMEPC');

   if Assigned(mFDolly) then
    Result := mFDolly

 else
  Application.MessageBox(PChar('ERROR!'), PChar('Microsoft Windows'), MB_ICONERROR);
 finally
  FreeLibrary(Handle);
 end;
end;

// *******************************************

运行函数

ShowMessage(CALLNAMEPC);

// *******************************************

使用下面的注释行,它工作正常,以解除访问冲突。

FreeLibrary(Handle);

在正常情况下,return 托管 String 跨越 DLL 边界是不安全的。您需要确保分配内存的内存管理器与释放内存的内存管理器相同,您的示例中并非如此。

您需要:

  • 将 DLL 更改为包 (BPL),然后让调用者使用 LoadPackage() 而不是 LoadLibrary()。包不会遇到这个内存问题,但它们确实会遇到另一个问题——调用者和 BPL 必须在相同的编译器版本中编译。如果将一个编译器升级到另一个编译器版本,则也必须升级另一个。这种方法还会阻止您的 DLL 在 non-Delphi/CB 环境中使用(不是说它现在可以,因为它不能,因为它使用的是 Delphi 特定的功能)。

  • 更改 DLL 和调用程序以使用 RTL 的共享内存管理器。这也是Delphi/CB-specific.

  • 重写 DLL 函数以跨不同的编译器工作version/vendors。

在最后一种情况下,更改函数的签名以使用标准调用约定,如 cdeclstdcall,而不是 Delphi 的默认 register 约定,并 return 按原样分配 PChar ,要求调用者在使用完后释放它。导出另一个函数以释放调用者可以使用的内存,例如:

function NAMEPC: PChar; stdcall;
var
  nSize : DWord;
const
  Buff_Size = MAX_COMPUTERNAME_LENGTH + 1;
begin
  nSize := Buff_Size;
  Result := StrAlloc(Buff_Size);
  if Result <> nil then
    GetComputerName(Result, nSize);
end;
    
procedure FreeNAMEPC(Ptr: PChar); stdcall;
begin
  StrDispose(Ptr);
end;

exports
  NAMEPC,
  FreeNAMEPC;
function CALLNAMEPC: String;
var
  Handle: THandle;
  p_NAMEPC: function: PChar; stdcall;
  p_FreeNAMEPC: procedure(Ptr: PChar); stdcall;
  P: PChar;
begin
  Result := '';

  Handle := LoadLibrary(PChar('DLL.dll'));
  if Handle = 0 then
    RaiseLastOSError;

  try
    p_NAMEPC := GetProcAddress(Handle, 'NAMEPC');
    if p_NAMEPC = nil then
      RaiseLastOSError;

    p_FreeNAMEPC := GetProcAddress(Handle, 'FreeNAMEPC');
    if p_FreeNAMEPC = nil then
      RaiseLastOSError;
    
    P := p_NAMEPC();
    if P = nil then
      raise Exception.Create('ERROR from NAMEPC!');

    try
      Result := P;
    finally
      p_FreeNAMEPC(P);
    end;

  finally
    FreeLibrary(Handle);
  end;
end;

或者,通过使用调用者可以直接使用的 OS 提供的内存管理器分配内存,即 LocalAlloc()/LocalFree()CoTaskMemAlloc()/CoTaskMemFree(),例如:

function NAMEPC: PChar; stdcall;
var
  nSize : DWord;
const
  Buff_Size = MAX_COMPUTERNAME_LENGTH + 1;
begin
  nSize := Buff_Size;
  Result := PChar(LocalAlloc(LMEM_FIXED, nSize * SizeOf(Char)));
  if Result <> nil then
    GetComputerName(Result, nSize);
end;
    
exports
  NAMEPC;
function CALLNAMEPC: String;
var
  Handle: THandle;
  p_NAMEPC: function: PChar; stdcall;
  P: PChar;
begin
  Result := '';

  Handle := LoadLibrary(PChar('DLL.dll'));
  if Handle = 0 then
    RaiseLastOSError;

  try
    p_NAMEPC := GetProcAddress(Handle, 'NAMEPC');
    if p_NAMEPC = nil then
      RaiseLastOSError;
    
    P := p_NAMEPC;
    if P = nil then
      raise Exception.Create('ERROR from NAMEPC!');

    try
      Result := P;
    finally
      LocalFree(P);
    end;

  finally
    FreeLibrary(Handle);
  end;
end;

或者,让调用者分配自己的缓冲区,然后将其传递给 DLL 以填充数据,例如:

function NAMEPC(Buffer: PChar; nSize: DWord): DWord; stdcall;
var
  C: Char;
begin
  Result := $FFFFFFFF;
  if Buffer = nil then
  begin
    nSize := 0;
    if not GetComputerName(@C, nSize) then
    begin
      if GetLastError = ERROR_BUFFER_OVERFLOW then
        Result := nSize;
    end;
  end else
  begin
    if GetComputerName(Buffer, nSize) then
      Result := nSize;
  end;
end;
    
exports
  NAMEPC;
function CALLNAMEPC: String;
var
  Handle: THandle;
  p_NAMEPC: function(Buffer: PChar; nSize: Dword): DWord; stdcall;
  Buf: array[0..16] of Char;
  Len: Dword;
begin
  Result := '';

  Handle := LoadLibrary(PChar('DLL.dll'));
  if Handle = 0 then
    RaiseLastOSError;

  try
    p_NAMEPC := GetProcAddress(Handle, 'NAMEPC');
    if p_NAMEPC = nil then
      RaiseLastOSError;
    
    Len := p_NAMEPC(@Buf[0], Length(Buf));
    if Len = $FFFFFFFF then
      raise Exception.Create('ERROR from NAMEPC!');

    SetString(Result, Buf, Len);

    { alternatively:

    Len := p_NAMEPC(nil, 0);
    if Len = $FFFFFFFF then
      raise Exception.Create('ERROR from NAMEPC!');

    SetLength(Result, Len);

    Len := p_NAMEPC(PChar(Result), Len);
    if Len = $FFFFFFFF then
      raise Exception.Create('ERROR from NAMEPC!');

    SetLength(Result, Len);
    }

  finally
    FreeLibrary(Handle);
  end;
end;