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 ofLoadLibrary()
. 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;