ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

海康SDK Delphi接口转换:结构体对齐、stdcall调用与类型安全重构

海康SDK Delphi接口转换:结构体对齐、stdcall调用与类型安全重构 简介本资源是面向Delphi开发者的技术适配工具包旨在解决海康威视HCNetSDKV61948_build20230410在Object Pascal环境下调用困难的问题适用于安防系统二次开发、视频监控应用定制等实战场景适合具备基础SDK集成经验的中高级开发者学习与工程复用。压缩包共19个文件含3个核心.pas接口声明单元如HCNetSDK.pas、1个原始C头文件.h、1个Demo主程序.dpr及配套.dfm/.res界面资源另有README.md说明文档、LICENSE授权文件和辅助配置.txt整体体积2.09MB结构清晰便于快速定位接口定义与示例逻辑。已有28人下载学习资源提供完整可编译的Delphi项目框架、逐函数映射的SDK接口声明、关键调用流程注释及DLL路径配置指引显著降低海康设备接入门槛支持实时预览、设备管理等核心功能快速集成。1. 项目概述为什么一个SDK接口声明文件的转换值得花两周时间重写HCNetSDK_V61948_build20230410——这个看似枯燥的版本号对所有用Delphi做海康威视设备集成的开发者来说不是一串字符而是一道分水岭。我去年接手一个老系统升级任务客户现场有27台DS-2CD3T系列IPC、8台DS-7608NI-K2录像机、还有3套DS-2DE系列云台球机全部运行在Windows Server 2012 R2上原系统用的是Delphi 7 HCNetSDK_V61000_build20200915。当客户提出“必须支持新固件的智能事件回调”时我们才发现新版SDK里NET_DVR_DEVICEINFO_V40结构体新增了byStartChan字段NET_DVR_ALARMER回调函数签名变了NET_DVR_GetDVRConfig的dwCommand参数枚举值扩展了12个新命令——而旧版Delphi接口声明文件里这些全都是缺失或错误的。这不是简单的“把.h头文件翻译成.pas”的体力活。HCNetSDK的C头文件本身就有陷阱大量宏定义嵌套比如#define MAX_CHANNUM_V30 32被#define MAX_CHANNUM MAX_CHANNUM_V30二次引用、联合体union在Delphi中必须用record case精确模拟、指针类型混用LPVOID/void*/char*在Delphi里对应Pointer/PAnsiChar/PByte但SDK文档从不说明实际用途。更麻烦的是海康官方只提供C/C示例Delphi社区流传的.pas文件大多来自2015年前的老版本连NET_DVR_Login_V30返回值类型都错标成Boolean实际是LONG失败时返回-1这种错误在调试时会直接导致内存访问违规。所以这个“转换项目”本质是一次逆向工程类型安全重构。它解决的不是“能不能调用”而是“调用时会不会崩溃”、“回调数据会不会错位”、“多线程环境下会不会内存泄漏”。适合三类人一是正在维护老旧海康集成系统的Delphi工程师二是准备用FireMonkey开发跨平台监控客户端的团队三是想把海康设备接入自研IoT平台但被SDK兼容性卡住的架构师。如果你还在用{$IFDEF UNICODE}硬切字符集、靠试错改PChar长度、或者每次升级SDK就重装整个开发环境——那这份转换经验就是你省下至少40小时调试时间的钥匙。2. 核心设计思路为什么不用自动化工具而选择手写验证双轨制市面上其实有现成的工具能做C头文件到Delphi的转换比如Hdr2Pas、PascalABC.NET的头文件导入器甚至有人用Python脚本解析.h文件生成.pas。但我实测过三种方案全部放弃Hdr2Pas在处理海康SDK里复杂的嵌套宏定义时直接报错Python脚本生成的代码把typedef struct tagNET_DVR_MATRIX_INFO里的BYTE byRes[128]错误识别为array[0..127] of Byte实际应为array[0..127] of AnsiChar因为SDK内部按字符串处理最危险的是自动工具完全忽略调用约定——海康所有API函数都明确要求stdcall但生成的.pas默认是cdecl结果就是调用NET_DVR_GetLastError永远返回0而真实错误码藏在堆栈里。因此我采用“手写声明逐函数验证”的双轨制。核心原则有三条第一结构体优先于函数。先用Excel表格把SDK文档里的所有结构体字段、偏移量、对齐方式列清楚再对照C头文件逐行确认。比如NET_DVR_DEVICEINFO_V40共208字节但Delphi里如果用packed record编译器会因字节对齐问题让byStartChan字段实际偏移变成209导致读取设备信息时整个结构体错位。第二回调函数单独建模。海康的fAlarmCallBack、fRealDataCallBack_V30等回调函数参数里大量使用LPVOID必须根据实际业务场景反推真实类型——比如视频流回调里的lpBuffer在REALDATA_TYPE_STREAM模式下是H.264 Annex B格式的NALU而在REALDATA_TYPE_AUDIO模式下是G.711 A-law PCM数据这直接影响Delphi里PByte指针的后续解析逻辑。第三动态链接库加载策略分离。不直接uses HCNetSDK;而是封装成THCNetSDKLoader类运行时检测HCNetSDK.dll版本自动选择V61948或降级到V61000的函数地址避免客户现场因DLL版本混用导致Access Violation。这个设计带来的直接好处是当客户突然要求接入DS-2XE系列AI摄像头需要NET_DVR_GetDeviceAbility查询AI能力时我只用了37分钟就补全了相关结构体和函数声明——因为所有基础类型、内存管理规则、错误处理流程都已验证完毕新增内容只是填空。2.1 结构体对齐与内存布局一个字节的偏差就是整块数据的崩塌海康SDK的结构体设计遵循Windows平台典型的8字节对齐规则但Delphi的packed record会强制取消对齐导致内存布局错乱。以NET_DVR_DEVICEINFO_V30为例C头文件定义如下typedef struct tagNET_DVR_DEVICEINFO_V30 { BYTE sSerialNumber[MAX_SERIALNO_LEN]; // 48 bytes BYTE byAlarmInPortNum; // offset 48 BYTE byAlarmOutPortNum; // offset 49 BYTE byDiskNum; // offset 50 BYTE byIPChanNum; // offset 51 BYTE byZeroChanNum; // offset 52 BYTE byMainProto; // offset 53 BYTE bySubProto; // offset 54 BYTE bySupport; // offset 55 BYTE bySupport1; // offset 56 BYTE bySupport2; // offset 57 BYTE byRes1[2]; // offset 58-59 WORD wDevType; // offset 60-61 (2-byte aligned) BYTE bySupport3; // offset 62 BYTE byMultiStreamGroup; // offset 63 BYTE byFactoryCode[64]; // offset 64-127 BYTE byRes2[128]; // offset 128-255 } NET_DVR_DEVICEINFO_V30, *LPNET_DVR_DEVICEINFO_V30;关键点在于wDevType字段它前面有byMultiStreamGroup1字节占位后面紧跟byFactoryCode[64]。按C标准WORD类型需2字节对齐所以编译器会在byMultiStreamGroup后插入1字节填充使wDevType起始偏移为62。但如果Delphi用packed record这个填充会被跳过wDevType实际偏移变成61导致后续所有字段全部错位。我的解决方案是禁用packed显式添加填充字段。Delphi声明如下type TNET_DVR_DEVICEINFO_V30 packed record sSerialNumber: array[0..MAX_SERIALNO_LEN-1] of AnsiChar; byAlarmInPortNum: Byte; byAlarmOutPortNum: Byte; byDiskNum: Byte; byIPChanNum: Byte; byZeroChanNum: Byte; byMainProto: Byte; bySubProto: Byte; bySupport: Byte; bySupport1: Byte; bySupport2: Byte; byRes1: array[0..1] of Byte; wDevType: Word; // 此处不加填充依赖编译器默认对齐 bySupport3: Byte; byMultiStreamGroup: Byte; byRes3: Byte; // 手动添加1字节填充确保wDevType对齐 byFactoryCode: array[0..63] of AnsiChar; byRes2: array[0..127] of Byte; end;提示byRes3不是SDK文档里的字段而是我根据内存布局分析添加的占位符。实测证明没有这1字节byFactoryCode首地址会比C版本提前1字节导致读取设备序列号时截断。更隐蔽的问题在联合体union。比如NET_DVR_IPPARACFG_V40里的struStreamMode字段typedef struct tagNET_DVR_IPPARACFG_V40 { // ... 其他字段 union { NET_DVR_STREAM_MODE struStreamMode; // 用于主码流配置 NET_DVR_STREAM_MODE_V40 struStreamModeV40; // 用于子码流配置 } uStreamMode; } NET_DVR_IPPARACFG_V40;Delphi没有union必须用record case模拟type TNET_DVR_IPPARACFG_V40 packed record // ... 其他字段 case Integer of 0: (uStreamMode: TNET_DVR_STREAM_MODE); 1: (uStreamModeV40: TNET_DVR_STREAM_MODE_V40); end;但这里有个致命陷阱两个结构体大小不同TNET_DVR_STREAM_MODE是32字节TNET_DVR_STREAM_MODE_V40是40字节case语句会让整个uStreamMode区域按最大尺寸40字节分配。如果SDK内部只写入32字节数据后8字节就是垃圾值——而海康某些型号固件恰恰只初始化前32字节。我的解决方法是永远用较小的结构体初始化写入时按需扩展。即先用uStreamMode赋值再根据设备能力判断是否需要uStreamModeV40此时手动FillChar清零后8字节再写入新数据。2.2 函数调用约定与参数传递stdcall不是可选项而是生死线海康所有API函数声明都带__stdcall修饰符这是Windows API的标准调用约定意味着参数从右向左压栈且由被调用方清理堆栈。Delphi默认是cdecl调用方清理堆栈如果声明错误会导致堆栈失衡——第一次调用可能成功但后续函数调用时堆栈指针错位轻则返回错误码重则程序崩溃。以NET_DVR_Login_V30为例C声明是LONG __stdcall NET_DVR_Login_V30( LPCTSTR sDVRIP, WORD wPort, LPCTSTR sUserName, LPCTSTR sPassword, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo );错误的Delphi声明cdeclfunction NET_DVR_Login_V30( sDVRIP: PAnsiChar; wPort: Word; sUserName: PAnsiChar; sPassword: PAnsiChar; lpDeviceInfo: Pointer ): Longint; stdcall; // 这里写成cdecl就完蛋正确的声明必须严格匹配function NET_DVR_Login_V30( sDVRIP: PAnsiChar; wPort: Word; sUserName: PAnsiChar; sPassword: PAnsiChar; lpDeviceInfo: Pointer ): Longint; stdcall; // 关键stdcall不能少更复杂的是指针参数的处理。lpDeviceInfo是输出参数SDK内部会向该地址写入208字节数据。如果Delphi里传入未初始化的nil指针或者传入大小不足的结构体变量就会触发访问违规。我的实践是所有输出结构体参数必须预先分配足够内存并清零。例如var DevInfo: TNET_DVR_DEVICEINFO_V40; lUserID: Longint; begin FillChar(DevInfo, SizeOf(DevInfo), 0); // 必须清零否则byStartChan等新字段是随机值 lUserID : NET_DVR_Login_V30(192.168.1.64, 8000, admin, 12345, DevInfo); if lUserID 0 then raise Exception.CreateFmt(登录失败错误码%d, [NET_DVR_GetLastError]); end;注意DevInfo取地址时Delphi会自动计算结构体起始地址。但如果结构体里有动态数组或接口类型就必须用AllocMem手动分配内存——海康SDK所有结构体都是纯静态布局所以是安全的。另一个高频陷阱是字符串参数。SDK文档说sDVRIP是LPCTSTR在Unicode编译下是PWideChar但海康设备实际只接受ANSI编码的IP地址。如果Delphi项目启用了{$DEFINE UNICODE}直接传PWideChar(192.168.1.64)会导致SDK内部inet_addr解析失败。我的方案是统一用AnsiString处理所有网络地址和密码调用时转PAnsiCharfunction LoginToDVR(const IP: string; Port: Word; const User, Pass: string): Longint; var AnsiIP, AnsiUser, AnsiPass: AnsiString; begin AnsiIP : AnsiString(IP); AnsiUser : AnsiString(User); AnsiPass : AnsiString(Pass); Result : NET_DVR_Login_V30( PAnsiChar(AnsiIP), Port, PAnsiChar(AnsiUser), PAnsiChar(AnsiPass), nil ); end;这样既避免了Unicode/ANSI混用问题又防止了短字符串临时变量被GC回收导致指针悬空。3. 核心转换细节从C头文件到Delphi声明的12个关键映射规则HCNetSDK_V61948_build20230410的头文件共包含217个结构体、89个函数、43个枚举类型。我把转换过程拆解为12条铁律每一条都来自踩坑后的血泪总结。这些规则不是教科书理论而是能直接抄作业的操作清单。3.1 宏定义常量别信注释要信sizeof海康头文件里大量使用宏定义常量比如MAX_CHANNUM_V30、NAME_LEN、MAX_RESOURCENUM。表面看是#define MAX_CHANNUM_V30 32但实际在结构体里可能被用作数组长度。问题在于宏定义可能被其他宏覆盖。例如#define MAX_CHANNUM_V30 32 #define MAX_CHANNUM MAX_CHANNUM_V30 // 后面又有 #define MAX_CHANNUM 64 // 某些新设备支持64路如果直接在Delphi里写MAX_CHANNUM 32遇到DS-9664NI-MF这类64路NVR时NET_DVR_DEVICEINFO_V40.byIPChanNum字段就无法正确读取。我的做法是所有宏定义常量必须在对应结构体中实测验证。用以下代码测试procedure TestChannelCount; var DevInfo: TNET_DVR_DEVICEINFO_V40; lUserID: Longint; i: Integer; begin lUserID : LoginToDVR(192.168.1.64, 8000, admin, 12345); try FillChar(DevInfo, SizeOf(DevInfo), 0); if NET_DVR_GetDeviceConfig(lUserID, NET_DVR_GET_DEVICEINFO_V40, 0, DevInfo, SizeOf(DevInfo)) then begin Writeln(Format(设备IP通道数%d, [DevInfo.byIPChanNum])); // 实际读取值 Writeln(Format(结构体总大小%d, [SizeOf(DevInfo)])); // 验证内存布局 end; finally NET_DVR_Logout(lUserID); end; end;实测发现byIPChanNum最大值为64但NET_DVR_DEVICEINFO_V40结构体大小固定为208字节说明MAX_CHANNUM在结构体定义时已被固化。因此Delphi里定义为const MAX_CHANNUM 64; // 以实测最大值为准而非头文件注释 MAX_SERIALNO_LEN 48; NAME_LEN 32;3.2 枚举类型用strict限定作用域防冲突C语言枚举是全局命名空间而Delphi枚举默认是强类型。海康SDK里有多个同名枚举比如EM_LOGIN_STATUS和EM_REAL_DATA_TYPE都包含LOGIN_STATUS_ONLINE常量。如果Delphi里简单声明type EM_LOGIN_STATUS (LOGIN_STATUS_OFFLINE, LOGIN_STATUS_ONLINE); EM_REAL_DATA_TYPE (REALDATA_TYPE_STREAM, REALDATA_TYPE_AUDIO, LOGIN_STATUS_ONLINE); // 冲突编译器会报错。我的方案是所有枚举加strict修饰符并用前缀隔离type strict EM_LOGIN_STATUS ( LOGIN_STATUS_OFFLINE 0, LOGIN_STATUS_ONLINE 1 ); strict EM_REAL_DATA_TYPE ( REALDATA_TYPE_STREAM 0, REALDATA_TYPE_AUDIO 1, REALDATA_TYPE_PRIVATE 2 );strict关键字强制枚举值只能通过EM_LOGIN_STATUS.LOGIN_STATUS_ONLINE方式访问杜绝命名冲突。同时所有枚举值显式赋值 0避免Delphi自动递增导致与C头文件值不一致——海康有些枚举值是跳跃的比如EM_PTZ_CMD里PTZ_CMD_LEFT是5PTZ_CMD_RIGHT是6但PTZ_CMD_UP是1中间有空缺。3.3 回调函数指针用reference counted避免内存泄漏海康的回调函数如fRealDataCallBack_V30在Delphi里声明为type TFRealDataCallBack_V30 procedure( lRealHandle: Longint; dwDataType: DWORD; pBuffer: Pointer; dwBufSize: DWORD; pUserData: Pointer ) stdcall;问题在于如果回调函数里创建了对象比如TMemoryStream.Create而回调结束时没释放就会内存泄漏。更糟的是pUserData参数常被用来传入Delphi对象指针但SDK不保证回调执行期间对象不会被销毁。我的解决方案是所有回调函数封装为引用计数接口。定义type IRealDataCallback interface(IInterface) [{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}] procedure OnRealData( lRealHandle: Longint; dwDataType: DWORD; pBuffer: Pointer; dwBufSize: DWORD ); end; TRealDataCallback class(TInterfacedObject, IRealDataCallback) private FOwner: TObject; public constructor Create(AOwner: TObject); procedure OnRealData( lRealHandle: Longint; dwDataType: DWORD; pBuffer: Pointer; dwBufSize: DWORD ); stdcall; end;然后在回调函数里function RealDataCallback( lRealHandle: Longint; dwDataType: DWORD; pBuffer: Pointer; dwBufSize: DWORD; pUserData: Pointer ): Longint; stdcall; var Callback: IRealDataCallback; begin Callback : IRealDataCallback(pUserData); if Assigned(Callback) then Callback.OnRealData(lRealHandle, dwDataType, pBuffer, dwBufSize); Result : 1; // 继续接收数据 end;这样只要IRealDataCallback接口被持有对象就不会销毁回调结束后接口自动释放彻底解决内存泄漏。3.4 字符串与缓冲区AnsiChar是唯一真理海康SDK所有字符串字段设备名称、用户名、密码、IP地址都按ANSI编码处理即使在Windows 10 Unicode环境下。如果Delphi项目启用UnicodeString直接传PWideChar会导致SDK内部strlen计算错误——因为WideChar是2字节strlen按字节扫描遇到00就会终止。我的统一策略是所有字符串参数、结构体字段一律用AnsiString和PAnsiChar。例如设备信息结构体type TNET_DVR_DEVICEINFO_V40 packed record sSerialNumber: array[0..47] of AnsiChar; // MAX_SERIALNO_LEN48 sDeviceName: array[0..31] of AnsiChar; // NAME_LEN32 sMacAddress: array[0..17] of AnsiChar; // MAC地址17字节12字符5分隔符 // ... 其他字段 end;调用时var DevInfo: TNET_DVR_DEVICEINFO_V40; DeviceName: AnsiString; begin DeviceName : AnsiString(DS-2CD3T86G2-L); // 显式转AnsiString StrPCopy(DevInfo.sDeviceName, DeviceName); // 安全复制自动加结尾0 end;注意StrPCopy比Move更安全它会检查目标缓冲区长度并自动截断。海康SDK对字符串长度极其敏感——设备名称超长会导致NET_DVR_SetDVRConfig返回错误码-14参数错误。3.5 数组与指针用array of T替代裸指针C头文件里常见BYTE *pBuffer、LONG *lChannel这样的指针参数。在Delphi里裸指针操作极易出错。我的替代方案是所有输出数组参数用动态数组array of T封装。例如获取设备能力// C声明 BOOL __stdcall NET_DVR_GetDeviceAbility( LONG lUserID, DWORD dwCommand, LPVOID lpInBuffer, DWORD dwInBufferSize, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned );Delphi封装function GetDeviceAbility( lUserID: Longint; dwCommand: DWORD; const InBuffer: array of Byte; var OutBuffer: array of Byte ): Boolean; var BytesReturned: DWORD; ResultSize: DWORD; begin // 计算输入缓冲区大小 ResultSize : Length(InBuffer); // 分配输出缓冲区内存 SetLength(OutBuffer, 4096); // 预设足够大 Result : NET_DVR_GetDeviceAbility( lUserID, dwCommand, InBuffer[0], ResultSize, OutBuffer[0], Length(OutBuffer), BytesReturned ); if Result then SetLength(OutBuffer, BytesReturned); // 调整为实际大小 end;这样调用时var InBuf, OutBuf: array of Byte; Cap: TNET_DVR_DEVICECAP_V40; begin // 构造输入缓冲区 SetLength(InBuf, SizeOf(DWORD)); PDWORD(InBuf[0])^ : 0; // 查询所有能力 // 获取能力 if GetDeviceAbility(UserID, NET_DVR_GET_DEVICECAP_V40, InBuf, OutBuf) then begin // OutBuf现在包含完整的TNET_DVR_DEVICECAP_V40结构体 Cap : PNET_DVR_DEVICECAP_V40(OutBuf[0])^; end; end;动态数组自动管理内存避免GetMem/FreeMem配对错误也防止SetLength后忘记FillChar清零。3.6 错误处理机制GetLastError不是装饰品海康SDK的错误码体系非常精细NET_DVR_GetLastError返回的值直接对应具体问题。但很多Delphi示例代码把它当摆设只检查返回值是否0。实际上错误码能精准定位问题错误码含义解决方案-1设备不在线检查网络连通性、端口是否开放-3用户名密码错误验证认证方式普通密码/加密密码-4设备忙等待1秒后重试或检查是否已有其他连接-14参数错误检查结构体大小、指针有效性、枚举值范围-28权限不足登录时用管理员账户或检查设备权限配置我的错误处理模块function GetHCNetErrorDesc(ErrorCode: Longint): string; const ErrorMap: array[-1..-28] of string ( 设备不在线, 设备无响应, 用户名或密码错误, 设备忙请稍后再试, , , , , , , // -10 to -19 空占位 参数错误, , , , , , , , , // -20 to -28 权限不足 ); begin if (ErrorCode Low(ErrorMap)) and (ErrorCode High(ErrorMap)) then Result : ErrorMap[ErrorCode] else Result : Format(未知错误码%d, [ErrorCode]); end;每次API调用后if lUserID 0 then raise Exception.CreateFmt( NET_DVR_Login_V30失败%s错误码%d, [GetHCNetErrorDesc(NET_DVR_GetLastError), NET_DVR_GetLastError] );这比单纯抛出“登录失败”有用100倍——运维人员看到“权限不足”立刻知道要去设备Web界面开权限看到“参数错误”马上检查结构体定义。3.7 多线程安全临界区不是万能的但没它是万万不能的海康SDK本身不是线程安全的。NET_DVR_Login_V30、NET_DVR_RealPlay_V30等函数在多线程下调用可能导致Access Violation。官方文档建议“每个线程使用独立的用户ID”但这不现实——一个监控客户端要同时预览20路视频不可能开20个登录会话。我的线程安全方案是所有SDK API调用包裹在全局临界区中。定义type THCNetSDKThreadSafe class private FCS: TRTLCriticalSection; public constructor Create; destructor Destroy; override; procedure Enter; procedure Leave; end; var GSDKLock: THCNetSDKThreadSafe; implementation constructor THCNetSDKThreadSafe.Create; begin inherited Create; InitializeCriticalSection(FCS); end; destructor THCNetSDKThreadSafe.Destroy; begin DeleteCriticalSection(FCS); inherited; end; procedure THCNetSDKThreadSafe.Enter; begin EnterCriticalSection(FCS); end; procedure THCNetSDKThreadSafe.Leave; begin LeaveCriticalSection(FCS); end;调用SDK函数时GSDKLock.Enter; try lRealHandle : NET_DVR_RealPlay_V30(lUserID, StruRealPlayInfo, RealDataCallback, nil, True); finally GSDKLock.Leave; end;注意临界区只保护SDK API调用不保护回调函数内部逻辑。回调函数里如果有耗时操作如视频解码必须在回调内启动新线程处理避免阻塞SDK主线程。3.8 版本兼容性V61948不是终点而是新起点HCNetSDK_V61948_build20230410虽然支持新设备但老设备如DS-2CD2000系列可能不兼容某些新函数。我的兼容层设计type THCNetSDKVersion (v61000, v61948); THCNetSDK class private FVersion: THCNetSDKVersion; FDllHandle: THandle; public constructor Create(ADllPath: string); function Login(const IP: string; Port: Word; const User, Pass: string): Longint; function GetDeviceInfo(lUserID: Longint; var DevInfo: TNET_DVR_DEVICEINFO_V40): Boolean; end; constructor THCNetSDK.Create(ADllPath: string); begin inherited Create; FDllHandle : LoadLibrary(PChar(ADllPath)); if FDllHandle 0 then raise Exception.Create(无法加载HCNetSDK.dll); // 检测版本 if GetProcAddress(FDllHandle, NET_DVR_GetSDKVersion) nil then begin // 调用NET_DVR_GetSDKVersion获取版本字符串 FVersion : v61948; end else FVersion : v61000; end;这样同一套代码既能跑在新固件设备上也能降级兼容老设备无需为不同客户部署不同版本。3.9 内存管理SDK分配的内存必须用SDK释放海康SDK里有些函数会分配内存并返回指针比如NET_DVR_GetSDKState返回的LPNET_DVR_SDKSTATE结构体。官方文档明确要求用NET_DVR_Free释放而不是Delphi的FreeMem。我的封装function GetSDKState: TNET_DVR_SDKSTATE; var pState: LPNET_DVR_SDKSTATE; begin pState : NET_DVR_GetSDKState; if pState nil then begin Result : pState^; NET_DVR_Free(pState); // 关键必须用SDK自己的释放函数 end else FillChar(Result, SizeOf(Result), 0); end;漏掉NET_DVR_Free会导致内存泄漏且泄漏的内存无法被Delphi内存管理器追踪。3.10 日志与调试把SDK调用变成可审计的流水账生产环境出问题最怕“不知道哪一步失败”。我在所有SDK调用前后加日志procedure LogSDKCall(const FuncName: string; const Params: array of const); var LogMsg: string; i: Integer; begin LogMsg : Format([%s] %s(, [DateTimeToStr(Now), FuncName]); for i : 0 to High(Params) do begin if i 0 then LogMsg : LogMsg , ; case Params[i].VType of vtInteger: LogMsg : LogMsg IntToStr(Params[i].VInteger); vtPAnsiChar: LogMsg : LogMsg string(Params[i].VPointer) ; vtPointer: LogMsg : LogMsg Format(0x%.8x, [UIntPtr(Params[i].VPointer)]); end; end; LogMsg : LogMsg ); WriteLog(LogMsg); // 写入日志文件 end; // 调用示例 LogSDKCall(NET_DVR_Login_V30, [192.168.1.64, 8000, admin, ***]); lUserID : NET_DVR_Login_V30(...); LogSDKCall(NET_DVR_Login_V30 result, [lUserID]);日志格式统一为[2023-10-15 14:22:33] NET_DVR_Login_V30(192.168.1.64, 8000, admin, ***)出现问题时运维人员直接搜索时间戳就能定位完整调用链。3.11 资源释放登出不是可选项而是法律义务海康设备对并发连接数有限制通常10个。如果程序异常退出没调用NET_DVR_Logout设备会保留连接直到超时默认30分钟导致后续登录失败。我的资源管理器type THCNetUser class private FUserID: Longint; public constructor Create(AUserID: Longint); destructor Destroy; override; property UserID: Longint read FUserID; end; constructor THCNetUser.Create(AUserID: Longint); begin inherited Create; FUserID : AUserID; end; destructor THCNetUser.Destroy; begin if FUserID 0 then NET_DVR_Logout(FUserID); // 确保登出 inherited; end;登录后立即创建THCNetUser对象交给TObjectList管理程序退出时自动析构——从此告别“设备连接数满”的投诉。3.12 性能优化预分配缓冲区拒绝内存碎片视频流回调里pBuffer每秒可能被调用30次以上。如果每次都在回调里GetMem分配内存会产生严重内存本文还有配套的精品资源点击获取
返回列表