引言
多年前用delphi写一些小程序时,曾用到海康威视的视频产品(当时是MPEG4视频采集卡),但是海康当时没有提供pascal版的SDK(说是因为版权问题被发过律师函,所以不提供,现在FPC没版权限制,可惜Delphi基本成了昔日黄花,使用的人越来越少,估计海康也没心思去弄这个版本,现在提供的除了传统的c/c++版,开始提供java、C#等版本的SDK)。所以我自己翻译头文件,结果弄了几天,成效不大(弄了几千行,整个头文件2W多行…),然后写了个小程序来自动转换,结果水平有限,只能针对性修改大部分的代码,然后又手动弄了两周,才算是修改通过编译,但是有些问题被忽略了,毕竟真正用到的数据结构和函数不多。最近手头要弄个热成像的接口,发觉早先的版本中没有,干脆把原单元对照新版的头文件修改一遍,顺便理一下FPC,毕竟不是专业的,好多知识不熟悉,现在花点时间学习一下。顺便把大华的SDK也弄了一遍,哈
以下就在进行转化过程中出现的问题进行记录一下,也希望能给别人一点参考。
一、预编译指令
讲真的,对编译环境这块我真不熟悉,一直都用IDE,很少去关心这块,原先对头文件中的预编译指令都是直接注释掉,按实际环境配置相应的类型申明。以我当前手头最新的SDK HCNetSDK.h(5.2版)为例,头文件中开头有如下内容:
#ifndef HC_NET_SDK_H
#define HC_NET_SDK_H
#ifndef WINDOWS
#if (defined(_WIN32) || defined(_WIN64))
#include <winsock2.h>
#include <windows.h>
#endif
#endif
#if defined(_WIN64)
#define OS_WINDOWS64 1
#endif
#if defined(LP64)
#define OS_POSIX64 1
#endif
#ifndef __PLAYRECT_defined
#define __PLAYRECT_defined
typedef struct __PLAYRECT
{
int x;
int y;
int uWidth;
int uHeight;
}PLAYRECT;
#endif
…
这个翻译成FPC倒是不难(不过我一直不知道什么会生效,所以我直接在满足条件的地方把定义取出来就不管了):
{KaTeX parse error: Double subscript at position 21: …f _HC_NET_SDK_H_̲} {define HC_NET_SDK_H}
{$endif}
//{KaTeX parse error: Expected group after '_' at position 16: ifndef _WINDOWS_̲} //我用的Lazarus…if (defined(_WIN32) or defined(_WIN64))}
// #include <winsock2.h>
// #include <windows.h>
// {KaTeX parse error: Expected 'EOF', got '}' at position 6: endif}̲ //{endif}
{KaTeX parse error: Expected 'EOF', got '}' at position 19: …defined(_WIN64)}̲ {define OS_WINDOWS64}
{$endif}
{KaTeX parse error: Expected group after '_' at position 12: if defined(_̲_LP64__)} {define OS_POSIX64}
{$endif}
{KaTeX parse error: Expected group after '_' at position 8: ifndef _̲_PLAYRECT_defin…define __PLAYRECT_defined}
type
__PLAYRECT = record
x:integer;
y:integer;
uWidth:integer;
uHeight:integer;
end;
PLAYRECT = __PLAYRECT;
{$endif}
这里最有用的就是这个操作系统的定义了,现在用的是win64,在后面的结构定义中确实会使用这个定义(这个函数内容有点多,删除了一点内容,也是后面进行联合体翻译的示例):
typedef struct tagNET_DVR_ALARMINFO_FIXED_HEADER
{
DWORD dwAlarmType;//报警类型
NET_DVR_TIME_EX struAlarmTime; //发生报警的时间
union
{
BYTE byUnionLen[116]; //分出去8个字节用于扩展时区
struct
{
DWORD dwAlarmChanNum;
DWORD dwPicLen;//Jpeg图片长度
BYTE byPicURL; //图片数据采用URL方式 0-二进制图片数据,1-图片数据走URL方式
BYTE byTarget; /0-不区分识别目标,1-识别目标为人,2-识别目标为车/
BYTE byRes1[2]; //保留
#if (defined(OS_WINDOWS64) || defined(OS_POSIX64))//win64及linux64下指针为8字节
char pDataBuff; //报警图片或者图片URL
#else
char pDataBuff; //报警图片或者图片URL
BYTE byRes3[4];
#endif**
}struAlarmChannel; // dwAlarmType为2,3,6,9,10、13或28时有效
struct
{
DWORD dwAlarmHardDiskNum;
}struAlarmHardDisk; // dwAlarmType为1,4,5时有效
}uStruAlarm;
DWORD* pRes; //用于兼容64位下结构体字节不对齐问题
BYTE byTimeDiffFlag;
char cTimeDifferenceH;
char cTimeDifferenceM;
BYTE byRes; //保留
WORD wDevInfoIvmsChannel; //增加后端透传前端时的通道号
BYTE byRes2[2]; //保留
}NET_DVR_ALRAM_FIXED_HEADER, *LPNET_DVR_ALARM_FIXED_HEADER;
翻译到此处时,发觉else下的代码激活状态,与实际的不符,原来我都直接使用if后的代码,其它都注释掉,嘿嘿
后面看其它高手们提供linux下线程应用的办法,我考虑这个开关应该也是要手动打开的,所以在菜单:project - project options…,弹出对话框options for project…-compiler options - custom options点击【defines】直接添加 OS_WINDOWS64定义或是添加_WIN64(这个定义会定义OS_WINDOWS64):
{KaTeX parse error: Expected 'EOF', got '}' at position 19: …defined(_WIN64)}̲ {define OS_WINDOWS64}
{KaTeX parse error: Expected 'EOF', got '}' at position 6: endif}̲ 然后这个定义就激活了,同样…IFDEF UNIX}{KaTeX parse error: Expected 'EOF', got '}' at position 18: …DEF UseCThreads}̲ cthread…ENDIF}{$ENDIF}
二、数据基本类型定义
因为使用lazarus,许多类型定义比较齐全,不须要扩展定义DWORD、QWORD、ULONG、UINT64等类型,所以头文件中这些内容可以直接注释掉。
三、枚举与集合类型
之前用delphi,枚举元素不能赋序号值,只能按是默认的排序进行,FPC是支持的,可以和C一样对每一个元素赋值,不连续也可以,但是不能乱序和重复,SDK中给出的枚举定义中有,让人头大,只好分成几个定义了。
对于delphi,直接采用枚举定义,肯定与原头文件中的不一致,主要是其序号值不对,可以考虑的解决办法是,将原枚举对象中的元素提取出来定义为常量,然后再用集合定义一个包括原来范围的一个类型,这样在引用时不会出现问题。
头文件中有:
typedef enum tagCharEncodeType
{
ENUM_MEM_CHAR_ENCODE_ERR = -1, //Error
ENUM_MEM_CHAR_ENCODE_NO = 0, //Don’t know.
ENUM_MEM_CHAR_ENCODE_CN = 1, //EUC-CN, GB2312
ENUM_MEM_CHAR_ENCODE_GBK = 2, //GBK
ENUM_MEM_CHAR_ENCODE_BIG5 = 3, //BIG5
ENUM_MEM_CHAR_ENCODE_JP = 4, //JISX0208-1, EUC-JP
ENUM_MEM_CHAR_ENCODE_KR = 5, //EUC-KR
ENUM_MEM_CHAR_ENCODE_UTF8 = 6, //UTF-8
ENUM_MEM_CHAR_ENCODE_ISO8859_1 = 7, //ISO-8859-n: ENUM_MEM_CHAR_ENCODE_ISO8859_1 + n -1
ENUM_MEM_CHAR_ENCODE_UNICODE = 8, //Unicode
}CHAR_ENCODE_TYPE;
转为FPC格式为
type
tagCharEncodeType = (
ENUM_MEM_CHAR_ENCODE_ERR = -1, //Error
ENUM_MEM_CHAR_ENCODE_NO = 0, //Don’t know.
ENUM_MEM_CHAR_ENCODE_CN = 1, //EUC-CN, GB2312
ENUM_MEM_CHAR_ENCODE_GBK = 2, //GBK
ENUM_MEM_CHAR_ENCODE_BIG5 = 3, //BIG5
ENUM_MEM_CHAR_ENCODE_JP = 4, //JISX0208-1, EUC-JP
ENUM_MEM_CHAR_ENCODE_KR = 5, //EUC-KR
ENUM_MEM_CHAR_ENCODE_UTF8 = 6, //UTF-8
ENUM_MEM_CHAR_ENCODE_ISO8859_1 = 7, //ISO-8859-n: ENUM_MEM_CHAR_ENCODE_ISO8859_1 + n -1
ENUM_MEM_CHAR_ENCODE_UNICODE = 8 //Unicode
);
CHAR_ENCODE_TYPE = tagCharEncodeType;
在delphi中,可以这样定义:
const
ENUM_MEM_CHAR_ENCODE_ERR = -1; //Error
ENUM_MEM_CHAR_ENCODE_NO = 0; //Don’t know.
ENUM_MEM_CHAR_ENCODE_CN = 1; //EUC-CN, GB2312
ENUM_MEM_CHAR_ENCODE_GBK = 2; //GBK
ENUM_MEM_CHAR_ENCODE_BIG5 = 3; //BIG5
ENUM_MEM_CHAR_ENCODE_JP = 4; //JISX0208-1, EUC-JP
ENUM_MEM_CHAR_ENCODE_KR = 5; //EUC-KR
ENUM_MEM_CHAR_ENCODE_UTF8 = 6; //UTF-8
ENUM_MEM_CHAR_ENCODE_ISO8859_1 = 7; //ISO-8859-n: ENUM_MEM_CHAR_ENCODE_ISO8859_1 + n -1
ENUM_MEM_CHAR_ENCODE_UNICODE = 8; //Unicode
type
tagCharEncodeType = set of Integer ; //因为含-1,不能用 tagCharEncodeType =set of -1…8;而tagCharEncodeType =set of 10…100则是可以的;
CHAR_ENCODE_TYPE = tagCharEncodeType;
这样即可引用元素名称,也可以引用类型定义。
四、联合体定义
pascal不支持联合体结构,都是用集合体来定义,通过free pascal wiki和网上一些文章,以上面的函数为例,我总结了两个定义方式,一种直接使用嵌套定义:
tagNET_DVR_ALARMINFO_FIXED_HEADER = record
dwAlarmType:longint ;//报警类型
struAlarmTime:NET_DVR_TIME_EX ;//发生报警的时间
uStruAlarm:record
case word of
0:(byUnionLen:array[0…115] of BYTE); //分出去8个字节用于扩展时区
1:(struAlarmChannel:record // 报警类型dwAlarmType为0时有效
dwAlarmChanNum: DWORD;
dwPicLen: DWORD; //Jpeg图片长度
byPicURL: BYTE;
byTarget: BYTE ; //0-不区分识别目标,1-识别目标为人,2-识别目标为车*/
byRes1:array[0…1] of BYTE ; //保留
{KaTeX parse error: Expected 'EOF', got '}' at position 50: …ed(OS_POSIX64))}̲//win64及linux64…else}
pDataBuff:PChar; //报警图片或者图片URL
byRes3:array[0…3] of byte;
{$endif}
end;);
2:(struAlarmHardDisk:record
dwAlarmHardDiskNum: DWORD ;
end;);
end;
pRes:PInteger ; //用于兼容64位下结构体字节不对齐问题
byTimeDiffFlag:BYTE; //时差字段是否有效 0-时差无效, 1-时差有效 */
cTimeDifferenceH: char;
cTimeDifferenceM: char;
byRes: BYTE; //保留
wDevInfoIvmsChannel: WORD; //增加后端透传前端时的通道号
byRes2:array[0…1] of BYTE; //保留
end;
NET_DVR_ALRAM_FIXED_HEADER = tagNET_DVR_ALARMINFO_FIXED_HEADER;
LPNET_DVR_ALARM_FIXED_HEADER =^tagNET_DVR_ALARMINFO_FIXED_HEADER;
还有一种是将个子结构分别定义,然后定义最后的结构:
//子结构定义
tagSTRUALARMCHANNEL = record
dwAlarmChanNum: DWORD ;
dwPicLen: DWORD ; //Jpeg图片长度
byPicURL: BYTE; //图片数据采用URL方式 0-二进制图片数据,1-图片数据走URL方式
byTarget: BYTE; //0-不区分识别目标,1-识别目标为人,2-识别目标为车*/
byRes1:array[0…1] of BYTE ; //保留
{KaTeX parse error: Expected 'EOF', got '}' at position 50: …ed(OS_POSIX64))}̲//win64及linux64…else}
pDataBuff:PChar; //报警图片或者图片URL
byRes3:array[0…3] of byte;
{$endif}
end;
//子结构定义
tagSTRUALARMHARDDISK = record
dwAlarmHardDiskNum: DWORD ;
end;
//联合体定义
taguStruAlarm = record
case word of //uStruAlarm
0:(byUnionLen:array[0…115] of BYTE); //分出去8个字节用于扩展时区
2:(struAlarmChannel:tagSTRUALARMCHANNEL;); //引用上面的子结构定义
3:(struAlarmHardDisk:tagSTRUALARMHARDDISK;); // 引用上面的子结构定义
end;
tagNET_DVR_ALARMINFO_FIXED_HEADER = record
dwAlarmType:longint ;//报警类型
struAlarmTime:NET_DVR_TIME_EX ;//发生报警的时间
uStruAlarm:taguStruAlarm; //引用上面的联合体定义
pRes:PInteger ; //用于兼容64位下结构体字节不对齐问题
byTimeDiffFlag:BYTE; //时差字段是否有效 0-时差无效, 1-时差有效 */
cTimeDifferenceH: char;
cTimeDifferenceM: char;
byRes: BYTE; //保留
wDevInfoIvmsChannel: WORD; //增加后端透传前端时的通道号
byRes2:array[0…1] of BYTE; //保留
end;
NET_DVR_ALRAM_FIXED_HEADER = tagNET_DVR_ALARMINFO_FIXED_HEADER;
LPNET_DVR_ALARM_FIXED_HEADER =^tagNET_DVR_ALARMINFO_FIXED_HEADER;
暂时结束
因为pascal与C类型的类型定义相似,而且有指针类型,所以翻译C头文件还是比较容易的,这次主要是这些头文件太大了,搞的没脾气,还没搞完呢,先记录这么多
海康SDK for FPC及大华SDK for FPC均已经上传到资源中,可以参考。均在Lazarus2.0.10 Win64下编译通过,并测试调用摄像机预览、图像参数调节、音频控制、PTZ操作、视频抓图、视频录像等函数是成功的,其它的函数没有测试过,请慎重使用。
2022.12.25 hefei