调用DLL获取计算机名后执行FreeLibrary释放时出现Access violation错误
问题根因
这个问题是Delphi跨模块传递托管类型的典型错误,核心原因有两个:
- Delphi的原生
String是托管内存类型,内存分配、引用计数、释放逻辑都绑定对应模块的内存管理器。你DLL侧构造的返回值String是用DLL的内存管理器申请的内存,当这个字符串的引用计数归0要释放时,会尝试用主程序的内存管理器释放不属于它管理的内存块,本身就存在内存访问风险。 - 你在字符串还没被主程序清理的前提下就先调用
FreeLibrary卸载了DLL,如果后续字符串释放逻辑依赖DLL侧的内存管理器代码/数据段,就会直接触发访问违规。注释FreeLibrary后DLL一直驻留内存,所以不会触发崩溃,但会遗留DLL资源泄漏的问题。
修复方案
推荐使用兼容性最好的预分配缓冲区传参方案,完全规避跨模块托管内存传递的问题:
1. 修改DLL侧实现
// 显式指定stdcall通用调用约定,由主程序分配缓冲区,DLL仅负责写入内容 function NAMEPC(lpBuffer: PChar; var nSize: DWORD): BOOL; stdcall; const Buff_Size = MAX_COMPUTERNAME_LENGTH + 1; begin Result := False; // 先校验传入的缓冲区大小是否足够 if nSize < Buff_Size then Exit; Result := GetComputerName(lpBuffer, nSize); end; exports NAMEPC;
2. 修改主程序调用逻辑
function CALLNAMEPC: String; type // 声明和DLL侧匹配的函数原型,必须严格对应参数和调用约定 TNamePCFunc = function(lpBuffer: PChar; var nSize: DWORD): BOOL; stdcall; var hDll: THandle; pFunc: TNamePCFunc; arrBuffer: array[0..MAX_COMPUTERNAME_LENGTH] of Char; dwSize: DWORD; begin Result := ''; hDll := LoadLibrary('DLL.dll'); if hDll = 0 then Exit; try pFunc := GetProcAddress(hDll, 'NAMEPC'); if Assigned(pFunc) then begin dwSize := MAX_COMPUTERNAME_LENGTH + 1; if pFunc(@arrBuffer[0], dwSize) then Result := StrPas(@arrBuffer[0]); end else Application.MessageBox('ERROR!', 'Microsoft Windows', MB_ICONERROR); finally // 现在DLL仅做写入操作,不需要持有任何传给主程序的内存,释放不会有问题 FreeLibrary(hDll); end; end;
如果不想改函数签名,也可以选择在DLL和主程序的工程文件所有引用单元的最顶部引入ShareMem单元,让两个模块共享同一份内存管理器,也能解决问题,但该方案要求DLL和主程序必须使用相同版本的Delphi编译,不支持跨语言调用。
内容的提问来源于stack exchange,提问作者David Kennedy
相关产品推荐
相关产品推荐

