从.h头文件到.pas单元

从其它平台迁移而来 由于长期使用Delphi开发,又与硬件打交道比较多,不可避免地要与标准C动态库进行对接,而往往厂家提供的SDK又偏偏没有Delphi的,无奈也就只好自己改写.h头文件了。写得多了,也就有了一点点心得,在这里就分享出来,也好与大家互相交流、学习。 知识点 标准C动态库使用的都是单字节字符。 Delphi 2007以前默认使用的是单字节字符,即Ansi编码,也就是说Char = AnsiChar、PChar = PAnsiChar、string = AnsiString;Delphi 2009以后使用的是双字节字符,即Unicode编码,也就是说Char = WideChar、PChar = PWideChar、string = WideString。为了保证改写后的.pas文件适用于Dephi的各个版本,应避免使用Char、PChar、string这种类型不明确的数据类型(通常情况下使用AnsiChar、PAnsiChar、AnsiString即可,但特殊情况要特殊处理)。 @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 升华 有了以上知识,把....

2020-05-04 23:42:53 · 2 分钟 · 慢步道人

Delphi开发守则

从其它平台迁移而来 前言 接触编程已十年有余,使用Delphi谋生也已五年有余,不敢说阅码无数,实实在在看过的代码也是有几箩筐的,但见过的写得好的、写得漂亮的、写得优美的代码,少之又少。 由于pascal语言简单易学的优点,以及DelphiIDE快速开发的方便,致使随便来个阿猫阿狗搞几下就能搞出来个马马虎虎的东西,于是众多程序猿便借势野蛮生长,个个都长得很有个性,产出的代码也自然是个性十足。事实上,其它语言的这种现象也不少。 我无意发起圣战,毕竟每只猿都有自己的追求,而我,不过是长成了一只有点洁癖的猿。以下是我的洁癖,也是我的追求,算是总结,也算是对自己的警醒。 正文 排版 良好的排版能使代码看起来清晰愉悦,统一的排版能使团队合作愉快,也更能显出版本控制优势。 简单来说,cnPack提供的排版功能就不错。我喜欢在默认的基础上做如下调整: 关键字小写。因为小写比大写更易阅读。 begin 位于下一行。因为能突出代码块的起始位置。 当超过90列时自动换行于80列。因为我的屏幕有点小,而且我个人也比较懒,再加上一点点历史原因。 字符串拼接等不希望cnPack自动排版的地方,可在末尾加//单行注释进行妨碍。写过长SQL语句的都知道我在说什么。 注释 注释很重要,但注释也可以很美妙。比如:interface区主要使用xml风格的注释,implementation区主要使用默认风格的注释,具体如下: 函数、过程、类方法、类属性、结构体方法的声明使用xml风格的注释,注释在上,声明在下。当你把鼠标放上去的时候你就知道我是对的。 枚举成员、类字段、结构体字段使用//单行注释,注释在右,且同一代码块尽量缩进对齐。无他,我有洁癖。 xml风格的注释,我喜欢的格式如下,至于怎么设置,我想这不是问题。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 {func} /// <summary>|</summary> /// <param name="n1"></param> /// <param name="n2"></param> /// <returns></returns> {param} /// <param name="|"></param> {remarks} /// <remarks>|</remarks> {returns} /// <returns>|</returns> {seealso} /// <seealso>|</seealso> {summary} /// <summary>|</summary> {value} /// <value>|</value> 命名 什么拼音首字母,什么1 2 3 4 5,我是极其痛恨的!...

2020-03-12 22:39:03 · 1 分钟 · 慢步道人

从Delphi到Go——接口

从其它平台迁移而来 由于没有太多编写接口的经验,此处仅简单说明语法。后期对接口有更多认知和经验后再进行详细记录。 Delphi Delphi的接口是侵入式接口,并且是单继承的,但类可以同时实现多个接口,类声明时需要显示声明实现了哪些接口。 声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type //直接声明 IMyInterface1 = interface function Func1: Integer; //函数 procedure Proc1(Value: Integer); //过程 property MI: Integer read Func1 write Proc1; //属性 end; //从已有接口继承 IMyInterface2 = interface(IMyInterface1) procedure Proc2; end; //含有 GUID 的接口可以公开给其它进程调用 IMyInterface3 = interface ['{3E51374A-D0E8-4C84-AA30-9634409E45DD}'] procedure Proc3; end; Delphi已经提供了基接口IInterface,自己声明的接口最好从IInterface继承。 实现 1 2 3 4 5 6 7 8 9 10 11 type //含接口的类的声明 TMyClass = class(基类, 接口) public procedure Proc; //接口方法 end; //接口实现 procedure TMyClass....

2020-01-30 12:52:16 · 1 分钟 · 慢步道人

简单数学运算的比较

从其它平台迁移而来 人到中年,总是不免生出些危机感,长年使用Delphi,心中自是不踏实,闲来便看两眼java,不想却发现个从未注意过的小问题。 java 1 2 3 4 5 double a = 1 / 10; //0.0 double b = 1.0 / 10; //0.1 double c = 1 / 10.0; //0.1 double d = 1 - 9.0 / 10; //0.09999999999999998 double e = 1 - 9 / 10; //1.0 Delphi 1 2 3 4 5 a := 1 / 10; //0.1 b := 1.0 / 10; //0.1 c := 1 / 10....

2020-01-01 22:37:24 · 2 分钟 · 慢步道人

Delphi中的延时

从其它平台迁移而来 开发过程中经常会需要使用到延时功能,Delphi中有不少实现延时的方法,网上已有不少文章做过说明和分析,但本着实践出真知的态度,还是亲自动手研究一番心里比较踏实。 常用的延时方法 Sleep Sleep(n),延时n毫秒,延时过程中程序不响应,一般延时较小时使用。 在主线程中使用,延时较大(100+)的话会起程序假死,一般在子线程中使用较多。 无论在主线程还是子线程中,延时较长的话(如 2000 ms),一般不一次性Sleep(2000),而是分多次循环Sleep。有时为了能在延时过程中响应外部消息,还会加上Application.ProcessMessages;,如: 1 2 3 4 5 6 //延时 2000 ms for i := 0 to 19 do begin Sleep(100); Application.ProcessMessages; end; Timer Timer为定时器,用于周期性地执行某个处理。也可用来实现延时,延时过程中不会引起程序假死, GetTickCount GetTickCount返回从操作系统启动到当前所经过的毫秒数,一般用于计算代码段的用时。配合循环使用也可达到延时的功能。 1 2 3 4 n := GetTickCount; repeat Application.ProcessMessages; //若延时过程中需要响应消息可加上此句 until GetTickCount >= n + ms; //ms为延时的毫秒数 注意:使用以上代码进行延时的过程中,CPU使用率会异常地高(事实上,不加限制一直跑的循环都会导致CPU使用率过高)。 小结 以上是对Sleep、Timer和GetTickCount用于延时的简单说明,个人经验:通常较小延时的场景用Sleep,较大延时的场景用Timer,评估代码段耗时的场景用GetTickCount。至于为什么这样用,以前是不清楚的,但通过对三者的精度分析,目前已知晓来龙去脉。 延时精度分析 上文已经提到,一般用GetTickCount来分析代码段耗时,但由于本次GetTickCount在被测行列,故另寻他法。 本次测试假定系统时间是足够精确的,因此使用Now分别在延时前后获取系统当前时间来进行耗时评估。 为使测试更具代表性,每个测试点测试100次,取算术平均值。 在1ms~100ms内,测试点步长为1ms,在100ms~1000ms内,测试点步长为10ms。 为尽可能减小干扰,测试过程中未使用Application.ProcessMessages;,也未使用并行。 测试结果 XE10编译,Win10下运行,经过近5个小时的测试,结果终于出炉了。 延时(ms) Sleep GetTickCount Timer 1 1.70 15.58 15.64 2 2....

2019-12-13 23:40:41 · 8 分钟 · 慢步道人

从Delphi到Go——方法

从其它平台迁移而来 结构体的方法 Delphi Delphi结构体的方法与类的方法几乎是一致的,主要区别是内存的管理方式和可见性不同。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 //定义 type TMyStruct = record No: Integer; Name: string; function ToString: string; end; //实现 function TMyStruct.ToString: string; begin Result := Format('No:%d, Name:%s', [Self.No, Self.Name]); end; //调用 var ms: TMyStruct; s: string; begin s := ms.ToString; end; Go 方法其实就是加了接收器的函数,语法如下: 1 2 3 func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) { 函数体 } Go结构体的方法无需声明,直接实现即可。...

2019-12-02 22:56:02 · 2 分钟 · 慢步道人

从Delphi到Go——异常处理

从其它平台迁移而来 Delphi try…finally…end 1 2 3 4 5 6 //创建、打开、加锁等 try //具体处理 finally //释放、关闭、解锁等 end; raise 1 raise Exception.Create('异常信息'); //手动抛出异常 try…except…end 1 2 3 4 5 6 7 8 9 try //可能产生异常的语句块 except //异常的相关处理 on E: Exception do begin //对应类型的异常的处理 end; end; Go defer 加defer的语句会延迟到函数调用结束返回时才执行,相当于finally...end区。存在多个defer语句时,最先出现的总是最后才执行。 1 2 3 4 5 func F(){ //打开、加锁等 defer //关闭、解锁等 //具体处理 } panic 1 panic(异常信息) recover 1 2 3 4 5 6 7 func FF(){ defer func(){ e := recover() //异常处理 }() //可能产生异常的语句块,或调用 panic() 抛出异常 } 虽然panic/recover组合可以模拟try....

2019-12-01 22:35:02 · 1 分钟 · 慢步道人

从Delphi到Go——函数的可变参数

从其它平台迁移而来 Delphi 事实上,Delphi并没有什么可以直接为函数传递可变参数(数量可变、类型可变)的语法,但是并不是说不可能实现,最常用的Format()函数就是最好的例子。 虽然不能直接传递可变参数,但是通过一种叫做可变类型的开放数组即可实现为函数传递数量不定、类型不一的可变参数。 可变类型 可变类型不是变体类型,而是一个记录类型TVarRec,在System单元中的定义如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 TVarRec = record { do not pack this record; it is compiler-generated } case Integer of 0: (case Byte of vtInteger: (VInteger: Integer); vtBoolean: (VBoolean: Boolean); vtChar: (VChar: _AnsiChr); vtExtended: (VExtended: PExtended); {$IFNDEF NEXTGEN} vtString: (VString: _PShortStr); {$ENDIF !...

2019-11-27 22:24:05 · 1 分钟 · 慢步道人

从Delphi到Go——匿名函数

从其它平台迁移而来 早期的Delphi版本是没有匿名函数的,不过可以定义一个函数类型来实现类似的功能;后期的版本已经支持匿名函数,随用随写。Go天生就支持匿名函数。 Delphi 函数类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 //声明函数类型 type TMyProc = procedure(A: Integer); //过程 TMyFunc = function(x: Integer): Integer; //函数 //定义符合函数类型的函数 procedure MyProc(A: Integer); begin ShowMessage(IntToHex(A)); end; function MyFunc1(x: Integer): Integer; begin Result := x + x; end; function MyFunc2(x: Integer): Integer; begin Result := x * x; end; //使用 var mp: TMyProc; mf: TMyFunc; begin mp := MyProc; mf := MyFunc1; mp(mf(99)); end; //作为参数进行传递,这才是函数类型最主要的使用方法 procedure Test(x: Integer; Func: TMyFunc); begin ShowMessage(Func(x)....

2019-11-03 12:14:24 · 1 分钟 · 慢步道人

从Delphi到Go——列表

从其它平台迁移而来 Delphi中最基本的列表是TList类和TList<T>泛型类,还有线程安全的TThreadList类和TThreadList<T>泛型类,底层实现是数组。Go用的是container/list包,内部实现是双向链表。 Delphi TList TList里存的是指针,使用时注意处理好指针即可。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 //声明 var l: TList; //构造 l := TList.Create; //添加 l.Add(p); //元素个数 n := l.Count; //列表容量 cap := l.Capacity; //取值 p1 := l.Items[0]; p2 := l.Extract(p1); //找到指针p1并从列表中取出,列表中将不再有p1,若其后还有元素,则前移填充空缺。 p := l.First; //取第一个元素 p := l.Last; //取最后一个元素 //查找元素的索引 i := l.IndexOf(p); //修改 l....

2019-10-30 21:58:48 · 2 分钟 · 慢步道人