<!--StartFragment-->
如果你想要自己编写的动态库可以适用更多的其它编程语言,你可以使用定义文件和WINAPI宏来编写自己的API动态库。你将会发现,使用这样的动态库输出函数就象使用API函数一样。
一、为什么要使用DEF文件?
因为微软的MFC动态库都是使用DEF文件创建的。
这回答够有份量吧,但有点大帽子压人的感觉。下面呢,我从原理上来说说为什么要使用DEF文件。
无论使用C语言或者C++语言来编写动态库,其编译器都会为每个函数甚至变量生成一个对应的修饰名(我是这样翻译的。原文是the decorated names),连接器将编译后的目标代码连接成DLL,其输出函数名或变量名依旧是编译后的修饰名。并且修饰名是与编译器相关的,也就是说你的源程序是C,生成的修饰名是一个样子;如果你的源程序是C++,则生成的修饰名是另一种样子。(关于修饰名的讨论,我将放在一个单独的章节进行,敬请等待。粮草未动,广告先行。真是的....)而我们的应用习惯是直接使用函数名,而非修饰名,我们在用API时一直就是如此。那么,问题就来了,比如你在VB6中使用VC6写的动态库:你先在VB6中使用函数名来描述你要调用的函数,然后写好调用代码,接下来运行,你的VB6这时会告诉你,它在动态库中找不到你刚刚描述过的函数的入口点,你的程序拒绝执行了。怎么办?解决问题的方法至少有两种:1、修改你的VB6代码中对动态库输出函数的描述部分,在别名栏添加动态库输出函数的修饰名。2、修改你的动态库,添加一个DEF文件,并使用DEF文件的EXPORTS项来输出你的动态库函数(在其下简单地列出你要输出的函数名即可)。虽然,这样一来问题是解决了(还没有解决?可能吗?有可能啦。但这里先停一下,随后再说。),但我们有必要将该问题进一步讨论下去。我们写动态库的目的大多是想让我们的动态库不局限于某一种编程语言,是为了更加广泛地被应用,我是喜欢用VB写界面,而用VC来完成更重要的工作,如对数据进行分析、访问硬件端口等,而且VC写好的动态库很少再改动,VB写的界面倒是一改再改。说这么多,目的只有一个,写动态库要着眼于“大局”,要一切符合“标准”。什么是大局?大局就是走可持续发展的道路,就是复用(好象是在做政治报告)。什么是标准呢?就是符合API的标准(也就是使用DEF文件输出函数,就象微软的MFC动态库)。
其实,使用DEF文件来输出函数的一个最主要目的就是:将编译器生成的函数修饰名去掉,用更加自然的、容易理解的、容易记忆的名字,而不是修饰名来输出函数。这里的名字可以不是函数名,这时须使用DEF文件的NAME格式。但由于习惯,大多情况下,只使用函数名,因为这样最简单省事。是否存在偷懒的嫌疑?我的理解,不管是罗列函数名,还是其它输出名,其本质上是一样的,即都是使用了定义文件的NAME格式。直接罗列函数名,就相当于“函数名”=“函数修饰名”,只是可以忽略等号后面的部分,而连接器会自动完成函数入口的匹配和设置工作。而一旦决定使用非函
数名的其它名字输出函数,则必须书写完整的格式,即“函数输出名”=“要输出的函数修饰名”,这里等号后面的部分必须书写正确,否则,连接时就通不过了。举个例子:假设动态库中一个函数描述如下,
int WINAPI TestAdd(int A,int B)
{
return (A+B);
}
其DEF文件的EXPORTS段描述如下,
EXPORTS
TestAdd
Add=?TestAdd@@YGHHH@Z
这里TestAdd和Add实际上指向同一个入口。如果在VB程序中调用TestAdd和Add,其结果是一样的。
二、为什么要使用WINAPI宏?
看看上面的举例,在函数前加了一个WINAPI宏。这一点很重要,它直接关系着函数输出什么样子的修饰名,使用WINAPI宏的TestAdd函数,对应的输出修饰名就是“?TestAdd@@YGHHH@Z”。
为什么要使用WINAPI呢?这牵涉到动态库的另一个特征,调用协议(Calling convention)。如果没有一定的协议,动态库的调用是不可想象的。一般常用的动态库调用协议有:
__cdecl
__stdcall
__fastcall
这些协议各有各的长处,这里暂不一一描述。上面在谈到解决VB程序调用VC写的动态库时,曾列举两种解决方法,但并不一定可以实现,它还取决于所使用的调用协议。VB所遵循的是PASCAL协议,如果在动态库中没有使用相应的协议,则VB程序执行时就会报告“调用协议错”。而PASCAL协议在VC6中已被废弃,取而代之的是__stdcall,即标准调用协议,这也是大多32位编程语言支持的一种通用协议。在WINDOWS.H中WINAPI也是被定义为__stdcall。这里提议使用WINAPI的理由也就在这,它能够表达出更加多的信息----这样定义的输出函数(的调用协议)和 WINDOWS API函数(的调用协议)一样。
其它一些使用WINAPI宏的理由:你只要在所定义的函数前加上该宏,就不必要在每次连接时再去理会各种与调用协议相关的设置。况且,你可能并不需要将所有定义的函数都输出,为了提高执行速度,你可能会将没有输出的函数使用__fastcall来定义,为了使用变参,你可能使用__cdecl来定义某些非输出函数,或者诸如此类的理由......需要提醒的是,VC默认的调用协议是__cdecl。如果你在没有修改调用协议的情况下,直接使用DEF文件输出函数,编译连接都不会出错,但是VB调用的时候肯定出错。而如果使用了WINAPI宏,你不必再去理会这些。编译器自动使用WINAPI的定义替代集成环境里的相关设置,这里函数前的说明优先级最高。
回过头来再看上面有关输出函数的修饰名的讨论,上面提到修饰名与语言有关,另外,它还与调用协议有关。如果需要使用非函数名的名字用来输出,你必须清楚你使用的调用协议及语言种类,也就是你必须清楚修饰名的生成规则,或者你采用一些技巧,让DUMPBIN.EXE工具来帮忙。
三、总结
一句话,如果想建立自己的标准API动态库,建议使用WINAPI描述你要输出的函数,然后使用定义文件输出它。