从其它平台迁移而来


由于长期使用Delphi开发,又与硬件打交道比较多,不可避免地要与标准C动态库进行对接,而往往厂家提供的SDK又偏偏没有Delphi的,无奈也就只好自己改写.h头文件了。写得多了,也就有了一点点心得,在这里就分享出来,也好与大家互相交流、学习。

知识点

  • 标准C动态库使用的都是单字节字符。

  • Delphi 2007以前默认使用的是单字节字符,即Ansi编码,也就是说Char = AnsiCharPChar = PAnsiCharstring = AnsiStringDelphi 2009以后使用的是双字节字符,即Unicode编码,也就是说Char = WideCharPChar = PWideCharstring = WideString。为了保证改写后的.pas文件适用于Dephi的各个版本,应避免使用CharPCharstring这种类型不明确的数据类型(通常情况下使用AnsiCharPAnsiCharAnsiString即可,但特殊情况要特殊处理)。

  • @string[1]才是字符串首地址。

  • Delphi中可以把AnsiString当作缓冲区来使用,某些情况下比array of Byte要方便得多。

  • 对字符串变量第一次使用SetLengh时会重新分配内存,第二次使用时,若设定的长度比第一次小,则只会进行截断而并不改变已写入的内容,该特性在使用API返回字符串时非常好用。

  • Delphi中的record是进行过字节对齐的,执行效率高,但占用空间比看到的会略大;packed record是未进行过字节对齐的,执行效率略低,但占用空间与看到的保持一致。也就是说,Delphi中packed record才是与C中的struct等同。

  • 在Delphi中packed record配合case可以实现C中的union,具体是否等同还要看实际定义的字节是否一致(需要对每种数据类型占用的空间十分熟悉)。

  • 标准C动态库的API函数或回调函数,在Delphi中均要使用stdcall;来修饰,以确保传参顺序一致。

数据类型对应关系

C/C++ 类型 Delphi 基本类型 Delphi Window 单元类型 说明
char ShortInt / Int8 8位有符号整型
char* PShortInt
unsigned char / BYTE Byte / UInt8 UCHAR 8位无符号整型,字节型
unsigned char* PByte LPBYTE / PUCHAR
short SmallInt / Int16 SHORT 16位有符号整型
short* PSmallInt PSHORT
unsigned short Word / UInt16 WORD 16位无符号整型
unsigned short* PWord PUSHORT
int / long Integer / Longint / Int32 LONG 32位有符号整型
int* / long* PInteger / PLongInt PLONG
unsigned / unsigned int / unsigned long Cardinal / LongWord / UInt32 DWORD / UINT / ULONG / ULONG32 32位无符号整型
unsigned int* / unsigned long* PCardinal / PLongWord / PUint32 PDWORD / PUINT / PULONG
long long / __int64 Int64 LONG64 / LONGLONG 64位有符号整型
long long* / __int64* PInt64 PLONG64
unsigned long long / unsigned __int64 UInt64 ULONG64 / ULONGLONG / DWORD64 64位无符号整型
unsigned long long* / unsigned __int64* PUInt64 PULONG64 / PULONGLONG / PDWORD64
float Single / Float32 32位单精度浮点型
float* PSingle
double Double / Float64 64位双精度浮点型
double* PDouble
long double Extended 10字节浮点型
char AnsiChar 单字节字符
char* PAnsiChar LPSTR / LPCSTR
char** PPAnsiChar
wchar_t / WCHAR WideChar WCHAR 双字节字符
wchar_t* PWideChar PWChar / LPWSTR / LPCWSTR
wchar_t** PPWideChar
任意1字节类型 Boolean / ByteBool 1字节布尔型
任意1字节类型指针 PBoolean / PByteBool
任意2字节类型 WordBool 2字节布尔型
任意2字节类型指针 PWordBool
BOOL LongBool BOOL 4字节布尔型
BOOL* PLongBool PBOOL
void* Pointer PVOID / LPVOID / LPCVOID 无类型指针
void** PPointer PPVOID

升华

有了以上知识,把.h头文件改为.pas单元是不成问题了,但改写后的API是否好用,还要打个问号。

比如有以下API:

1
BOOL __stdcall GetFileName(char * strFileName, int * iLen);

按前面提到的改为.pas单元:

1
2
3
4
//声明
function GetFileName(strFileName: PAnsiChar; iLen: PInteger): LongBool; stdcall;
//调用
b := GetFileName(ps, @l);

改写没有任何问题,但是不如以下这种直观、方便,毕竟有不少人对指针和地址还是相当恐惧的。

1
2
3
4
//声明
function GetFileName(strFileName: PAnsiChar; var iLen: Integer): LongBool; stdcall;
//调用
b := GetFileName(ps, l);

假如以上API为:

1
BOOL __stdcall GetPic(char * pBuff, int * iLen);

那么,生硬的改成

1
function GetPic(pBuff: PAnsiChar; iLen: PInteger): LongBool; stdcall;

就不如改为

1
2
3
4
5
function GetPic(pBuff: PByte; var iLen: Integer): LongBool; stdcall;
//
function GetPic(pBuff: Pointer; var iLen: Integer): LongBool; stdcall;
//甚至
function GetPic(var pBuff: TBytes; var iLen: Integer): LongBool; stdcall;

改写不仅仅是为了改写,更是为了调用。当然,若是再加一层封装来给其它单元调用,甚至直接封装成类,那是再好不过了!