Delphi调用C++ DLL获取HOOKFCNS2结构体指针返回值全零问题排查
我在Delphi中通过LoadLibrary实现动态链接,调用某C++ DLL的get_all_hook_functions_V2()函数。该函数应返回一个填充了函数地址的HOOKFCNS2结构体指针,我需要替换其中的函数地址为自定义函数地址后回传,以此实现回调机制,让库通过自定义函数执行任务。但无论尝试何种方式(除AVS外),获取到的结构体成员均为0或nil。
C语言典型用法
static HOOKFCNS2 hfcns; void ELCALLBACK remap_cal_target(INT16 x, INT16 y) { // ... 处理draw_cal_target_hook的代码 ... } // 重映射校准目标绘制的钩子函数 void setup_remap_hooks(void) { HOOKFCNS2 *hooks = get_all_hook_functions_V2(); memcpy(&hfcns,hooks,sizeof(HOOKFCNS2)); hooks->draw_cal_target_hook = remap_cal_target; setup_graphic_hook_functions_V2(hooks); }
HOOKFCNS2结构体C语言定义
typedef struct { /*!主版本号,当前必须设为1 */ int major; /*!次版本号,当前必须设为0 */ int minor; /*!该值会传递给所有回调函数 */ void *userData; INT16 (ELCALLBACK * setup_cal_display_hook)(void *userData); INT16 (ELCALLBACK * exit_cal_display_hook)(void *userData) ; INT16 (ELCALLBACK * setup_image_display_hook)(void *userData,INT16 width, INT16 height) ; INT16 (ELCALLBACK * image_title_hook)(void *userData,char *title) ; INT16 (ELCALLBACK * draw_image)(void *userData,INT16 width, INT16 height,byte *pixels) ; INT16 (ELCALLBACK * exit_image_display_hook)(void *userData) ; INT16 (ELCALLBACK * clear_cal_display_hook)(void *userData) ; INT16 (ELCALLBACK * erase_cal_target_hook)(void *userData); INT16 (ELCALLBACK * draw_cal_target_hook)(void *userData,float x, float y); INT16 (ELCALLBACK * play_target_beep_hook)(void *userData,EL_CAL_BEEP beep_type) ; INT16 (ELCALLBACK * get_input_key_hook)(void *userData,InputEvent * event); INT16 (ELCALLBACK * alert_printf_hook)(void *userData, const char *msg); int reserved1; int reserved2; int reserved3; int reserved4; }HOOKFCNS2;
转换后的Delphi代码
TYPE Tsetup_cal_display = function(userData: pointer): INT16; stdcall; Texit_cal_display = function(userData: pointer): INT16; stdcall; Tsetup_image_display = function(userData: pointer; width, height: INT16): INT16; stdcall; Timage_title = function(userData: pointer; title: pAnsiChar): INT16; stdcall; Tdraw_image = function(userData: pointer; width, height: INT16; pixels: pbyte): INT16; stdcall; Texit_image_display = function(userData: pointer): INT16; stdcall; Tclear_cal_display = function(userData: pointer): INT16; stdcall; Terase_cal_target = function(userData: pointer): INT16; stdcall; Tdraw_cal_target = function(userData: pointer; x, y: float): INT16; stdcall; TPlay_target_beep = function(userData: pointer; beep_type: INT16): INT16; stdcall; Tget_input_key = function(userData: pointer; key_input: TInputEventPtr): INT16; stdcall; Talert_printf = function(userData: pointer; msg: PAnsiChar): INT16; stdcall; THOOKFCNS2 = record major, minor : int_; UserData: pointer; setup_cal_display_hook: Tsetup_cal_display; // 初始化校准/验证显示,在校准流程前调用 exit_cal_display_hook: Texit_cal_display; // 释放校准后不再需要的资源,调用后不再执行校准相关函数 setup_image_display_hook: Tsetup_image_display; // 初始化相机设置所需资源,返回-1/0/1 image_title_hook: Timage_title; // 更新图像标题 draw_image_hook: Tdraw_image; // 逐行提供图像数据(从上到下) exit_image_display_hook: Texit_image_display; // 通知相机设置完成,释放setup_image_display中分配的资源 clear_cal_display_hook: Tclear_cal_display; // 清除校准显示 erase_cal_target_hook: Terase_cal_target; // 清除上一次draw_cal_target绘制的目标 draw_cal_target_hook: Tdraw_cal_target; // 在指定坐标绘制校准、验证和漂移校正的目标 play_target_beep_hook: TPlay_target_beep; // 发出新目标提示音 get_input_key_hook: Tget_input_key; // 获取输入按键 alert_printf_hook: Talert_printf; // 输出提示信息 reserved1, reserved2, reserved3, reserved4: int_; end; THookFcns2Ptr = THookFcns2^; VAR GDI_HookFcns2: THookFcns2; TYPE // HOOKFCNS2 * get_all_hook_functions_V2 (void) Tget_all_hook_functions_V2 = function: THookFcns2Ptr; stdcall; VAR get_all_hook_functions_V2: Tget_all_hook_functions_V2; // 程序中后续调用 GDI_HookFcns2:= get_all_hook_functions_V2()^;
遇到的问题
调用后GDI_HookFcns2的成员全为(0, 0, nil, nil ..., 0, 0, 0, 0);尝试过GDI_HookFcns2:= THookFcns2Ptr(get_all_hook_functions_V2())^;,结果依旧;将GDI_HookFcns2声明为指针并通过GetMem分配内存也无改善。DLL供应商确认函数返回的不是0或nil,调用前手动设置GDI_HookFcns2.major:=2,调用后该值被重置为0。
问题分析与解决方案
1. 类型匹配错误
- Delphi中的
float对应双精度类型(Double),而C中的float是单精度,需将Delphi函数指针中的float改为Single:Tdraw_cal_target = function(userData: pointer; x, y: Single): INT16; stdcall; - 确认
INT16是Delphi的SmallInt(16位有符号整数),int_是Integer(对应C的32位int),避免类型大小不匹配。
2. 结构体对齐方式不一致
C++编译器(如MSVC)默认使用8字节对齐,Delphi默认对齐方式可能存在差异,需显式指定对齐规则:
{$ALIGN 8} // 与MSVC默认对齐一致 THOOKFCNS2 = record // ... 成员定义 ... end; {$ALIGN DEFAULT} // 恢复默认对齐
对齐方式不一致会导致结构体内存布局错位,读取到错误的成员值。
3. 函数调用约定不匹配
C++默认调用约定为cdecl,若DLL中的get_all_hook_functions_V2()使用cdecl而非stdcall,需修改Delphi的函数类型声明:
Tget_all_hook_functions_V2 = function: THookFcns2Ptr; cdecl;
调用约定错误会导致栈处理异常,返回的指针地址无效。
4. 结构体指针处理方式错误
避免直接复制结构体,先获取指针再验证并操作成员:
var pHooks: THookFcns2Ptr; begin pHooks := get_all_hook_functions_V2(); if Assigned(pHooks) then begin // 验证指针有效性,比如检查主版本号是否为1 if pHooks^.major = 1 then begin GDI_HookFcns2 := pHooks^; // 替换自定义回调函数 pHooks^.draw_cal_target_hook := @MyCustomDrawCalTarget; // 回传修改后的结构体指针 setup_graphic_hook_functions_V2(pHooks); end; end; end;
5. 验证结构体大小一致性
在C和Delphi中分别输出sizeof(HOOKFCNS2)和SizeOf(THOOKFCNS2),确保数值完全相同。若不同,逐一核对每个成员的类型和大小,排查对齐或类型定义错误。
内容的提问来源于stack exchange,提问作者David Chesnet

