You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi调用C++ DLL获取HOOKFCNS2结构体指针返回值全零问题排查

动态调用C++ DLL时Delphi获取HOOKFCNS2结构体成员全为0的问题

我在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 23:49:53