Home > Back-end >  Access violation when releasing DLL
Access violation when releasing DLL

Time:12-03

I have a function that takes the computer's name, it is in an external DLL. And in my program I call this function, but I can't release the DLL after using the function.

DLL function

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;

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

Function that calls the 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;

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

Running the function

ShowMessage(CALLNAMEPC);

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

With the commented line below, it works ok, to release the Access violation.

FreeLibrary(Handle);

CodePudding user response:

Under normal conditions, it is not safe to return a managed String across the DLL boundary. You need to ensure the same memory manager that allocates the memory is the same manager to free it, which is not the case in your example.

You need to either:

  • change the DLL into a Package (BPL), and then have the Caller use LoadPackage() instead of LoadLibrary(). Packages don't suffer from this memory issue, but they do suffer from another issue - both Caller and BPL must be compiled in the same compiler version. If you upgrade one to another compiler version, you have to upgrade the other, too. This approach also prevents your DLL from being usable in non-Delphi/CB environments (not that it can right now anyway, because it can't, because it is using Delphi-specific features).

  • change both the DLL and Caller to use the RTL's shared memory manager. This is also Delphi/CB-specific.

  • rewrite the DLL function to work across different compiler version/vendors.

In the last case, change the function's signature to use a standard calling convention, like cdecl or stdcall, rather than Delphi's default register convention, and to return the allocated PChar as-is, requiring the Caller to free it when done using it. Either export another function to free the memory that the Caller can use, eg:

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;

Or, by allocating the memory using an OS-provided memory manager which the Caller can use directly, ie LocalAlloc()/LocalFree() or CoTaskMemAlloc()/CoTaskMemFree(), eg:

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;

Or, by having the Caller allocate its own buffer, and then pass it in to the DLL to be populated with data, eg:

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;
  • Related