不知道cnblogs的机制是什么,根据后台管理的页面浏览与RSS的统计,“随笔”的阅读量要高于“文章”。不过就内容来说,“文章”的价值要远高于“随笔”的,尤其是对初学者来说。因为随笔一般只针对几个语法知识点,而“文章”的内容则要更基本一些——当然也许这样也导致“文章”会更抽象、晦涩一些,不是很好读懂。一两个语法知识点只能够帮助你更好的应用Delphi,而基础知识对于基于过程的编程语言来说,大体上是通用的——也就是说,到目前为止,“随笔”更多讲的是Delphi特性的东西,而“文章”讲共性的东西更多一些。如果你对自己的基础并不完全自信的话,希望你能读一下目前已经有的“文章”:

作为一门基于过程的语言,Delphi的内容无非包括以下几种:类型,变量,函数。熟练掌握它们,再对Delphi的类库等有足够的了解,就已经可以写出不错的程序了。如果要弄一些更接近操作系统的内容,那就要对WinAPI有一定程度的认识,熟练使用MSDN能快速找到需要的函数;喜欢GUI的话,就要了解基本的GDI对象,理解Windows的消息机制,对VCL的封装模型也要有一定概念。这个时候,就基本上可以独立解决大部分问题,算得上是半个“高手”了。不对是使用类、使用DLL,还是应用接口等等,完全还是围绕类型、变量、函数这几样东西转,只不过形式稍微复杂一些。

我把对Delphi的使用分为三个层级:初级时,对类型、变量、过程只有初步的认识,还不能熟练的匹配;中级时,则要能够有清晰的认识,能够熟练匹配,并且意识到脱离不开它们;到了高级,就可以更意识流一些,眼里看到的只有内存,类型、变量、过程都是为了方便使用才存在的,能够熟练的在看似不同的类型之间进行正确的转换。如果不太高级是什么意思的话,这里可以举个例子:某次写一个小程序,需要计算文件的MD5。开始我直接使用了CnVCL(过去叫CnPack)的CnMD5单元,在处理一个大文件的时候,发现处理速度明显要比用Total Commander的一个插件慢。于是我又对比了一下.Net的库,发现同样也比CnMD5快很多。由于.net直接使用了AdvApi32.dll的导出函数,于是我根据网上搜到的导出函数原型,写出了以下的Delphi声明:

type
	TAdv32MD5	= record
		TotalSize		: Int64;
		buffDigest	: array[0..3]of LongWord;
		buffInput		: array[0..63]of Byte;
		Digest	: array[0..3]of LongWord;
	end;

const
	AdvApi32	= 'AdvApi32.dll';

procedure aaMD5Init(var MD5Context: TAdv32MD5); stdcall;
	external AdvApi32 name 'MD5Init';
procedure aaMD5Update(var MD5Context: TAdv32MD5; const Buffer; Size: LongWord); stdcall; 
	external AdvApi32 name 'MD5Update';
procedure aaMD5Final(var MD5Context: TAdv32MD5); stdcall;
	external AdvApi32 name 'MD5Final';

这样当然是可以正确使用的。不过我用的是D2010,2006后版本的Delphi支持记录类型的成员函数,可以封装一下。于是我利用这个语法支持,写下了另一个版本的声明,使用起来会方便很多:

type
	TMD5dig	= record
		Digest	: array[0..3]of LongWord;
		function ToString: string;
	end;

	TAdv32MD5	= record
	private
		TotalSize		: Int64;
		buffDigest	: array[0..3]of LongWord;
		buffInput		: array[0..63]of Byte;
	public
		digest	: TMD5dig;
		constructor Create(const Buffer; Size: LongWord);
		procedure Hash(const Buffer; Size: LongWord);
		class function MD5(const Buffer; Size: LongWord): TMD5dig; static;
		
		procedure Initialize; stdcall;
		procedure Update(const Buffer; Size: LongWord); stdcall;
		procedure FinalDigest; stdcall;
	end;

const
	AdvApi32	= 'AdvApi32.dll';

{ TMD5dig }

function TMD5dig.ToString: string;
const	HexChars	: array[0..15]of Char = '0123456789ABCDEF';
var
	i, b	: LongWord;
	pSrc	: PAnsiChar;
	pDst	: PChar;
begin
	SetLength(Result, 32);
	pDst	:= Pointer(Result);
	pSrc	:= @Self;
	for i:=0 to 15 do
	begin
		b	:= Ord(pSrc[i]);
		pDst[i*2+0]	:= HexChars[b shr $4];
		pDst[i*2+1]	:= HexChars[b and $f];
	end;
end;

{ TAdv32MD5 }

constructor TAdv32MD5.Create(const Buffer; Size: LongWord);
begin
	Initialize;
	Update(Buffer, Size);
end;

procedure TAdv32MD5.FinalDigest;
	external AdvApi32 name 'MD5Final';

procedure TAdv32MD5.Hash(const Buffer; Size: LongWord);
begin
	Initialize;
	Update(Buffer, Size);
	FinalDigest;
end;

procedure TAdv32MD5.Initialize;
	external AdvApi32 name 'MD5Init';

class function TAdv32MD5.MD5(const Buffer; Size: LongWord): TMD5dig;
var	tmp: TAdv32MD5;
begin
	tmp.Hash(Buffer, Size);
	Result	:= tmp.digest;
end;

procedure TAdv32MD5.Update(const Buffer; Size: LongWord);
	external AdvApi32 name 'MD5Update';

观察Initialize、Update以及FinalDigest的声明与实现部分,并没有声明一组导入函数再用成员函数去调用它们,会不会感到很意外?


此外,我还会单独讲讲字符串与编码,尤其是Unicode的问题。编码这种东西其实并不复杂,而且从Win2000开始,Windows内部就已经使用Unicode了。D2009终于原生支持Unicode版的控件了,这一天来的实在太晚了,导致大量Delphi程序员到现在还搞不清它。我始终认为,像Unicode这种东西,对拉丁或者西里尔文使用者来说意义并不大,但作为东亚国家的程序员或者码农——别怪我埋汰你,要是这么简单的东西还整不明白,也就甭笑话国足冲不出亚洲了——你跟他们在各自领域的水平半斤八两。字符串的内容稍微复杂一些,并且由于从D2009开始的变化,我也会对前后的变化进行讲解。


还要再强调一下:我主要以讲解D2010的语法为主,附带内容会提到不同版本间的语法变化(虽然我是从D4开始接触Delphi的,不过那个时候实在是个连OOP都搞不清楚的小白,所以最早提到的也只有D5到D6的变化)。千万不要觉得D2010很神秘,或者编译器本身还有很多bug,影响日常使用——我可以很明确的告诉你,目前D2010的编译器bug要么是新语法的,要么就是从老版本中带来的。当然,部分涉及网络或数据库的类库,在功能上可能会有因Unicode产生的问题,因为我几乎不写网络或数据库功能的程序(就算写大概也会选择C#),所以我无法确定D2010在这些方面到底有没有问题。由于Unicode是未来的趋势,并且据Embarcadero的路线图,未来Delphi可能会支持其它操作系统。相信我,如果连Win32平台的D2010都用不明白的话,你不可能用明白以后的新版本的。