- 论坛徽章:
- 5
|
以前一直用MinGW,导出的DLL一直都没有出问题,但是这次居然出了很严重的问题,
主要是遇到了以前从来没有遇到过的 stdcall 调用约定。
假设一个函数叫做 Function,根据调用约定的常识,如果是cdecl(C的调用约定)
,则在内部的链接名字就是_Function,而导出到DLL的名字却会根据导出的方式而变,无
论如何,真正在DLL里面的导出符号都是没有下划线前缀的。
然而,导出就意味着导入,如果我们通过__declspec(dllimport)期望从动态库导入
文件,那么事情会很不一样了,在这种情况下,期待被导入的函数并不叫_Function甚至
Function,他会期望一个叫做__imp_Function的名字。
通常这并不是什么大问题,因为 MinGW 不仅可以从 DLL 直接链接,而且在产生 DLL
的时候,也可以再产生一个.a的标准格式的导入库(VC则是至始至终都会产生这个导出库
的)。在导出库里,则会有__imp_Function这么一个静态的小函数,这个函数存在的目的
就是动态地去调用DLL里面真正导出了的函数。因为这么一个原因,如果我们在声明函数
时带上了 __declspec(dllexport) ,则我们就失去了直接链接DLL的能力了,则必须链接
ld帮我们生成的.a导入库了。
而ld里面跟这个有关的参数就那么几个,我们是通过gcc把参数传递给ld的,因此我
们需要在参数前加入-Wl,前缀,并且用,作为参数的分隔符:
-Wl,--output-def=<def文件名> : 产生一个跟导出有关的def文件。
-Wl,--out-implib=<.a文件名> :产生一个用于提供__imp_符号的.a导入库。
另外,如果在产生DLL文件的时候,并没有产生.a文件,也是有办法的。首先,可以
通过一个额外的工具 pexports 通过 DLL 得到 .def 文件,然后可以通过 MinGW 附带的
dlltool工具将.def 转换为.a文件。
通常,对于C调用标准的DLL文件,MinGW是可以直接链接到它的,完全不需要
__declspec(dllexport) 这样的修饰,再加上上面的了解,基本上对DLL的使用就很清晰
了。
然而,如果一遇上__stdcall约定的函数,那么整个局面就复杂起来了。
首先,其实 __stdcall 约定才是Windows下面DLL的标准调用约定。如果采用 def 文
件的导出方式的话,那么__stdcall函数和普通函数其实没有任何区别,其导出的符号名
依然是Function,坏就坏在,__stdcall函数本身会清理调用它时传递的参数,因此,一
些编译器就必须得知道,这个函数调用以后,到底会把栈指针减去多少,从而预先把栈指
针加上去。很不幸的,GCC就是这么一种编译器……
根据从这里(http://www.willus.com/mingw/yongweiwu_stdcall.html)得到的一张
表,可以看出,对于__stdcall函数,各种不同的编译器有完全不同的命名方法,而且就
算是相同的编译器,如果导出的方式不同(dllexport?或者def文件?)其最终导出的符
号也不同。
但是,总的来说,落实到DLL上面去,也就是两种符号而已:Function,和
Function@n,这里的n是一个数字,代表Function执行完以后,会弹多少栈元素。
对于MinGW,要和对方进行交互,我们需要考虑下面的四种情况:
1. 如何导出 Function 名字
2. 如何导出 Function@n 名字
3. 如何链接到 Function 名字
4. 如何链接到 Function@n 名字
这里所有的讨论都没有涉及到之前提到的导出库,对于导出库的问题,我们稍后来解
释。
导出 Function 名字还是很简单的:只要你能提供一个 def 文件,那么就可以将
stdcall函数导出为Function名字了,不过这里还有问题:gcc会给你一个警告,说会将
导出的Function链接到Function@n,提供-Wl,--enable-stdcall-fixup则会去掉这个警告
,默认执行转换,当然如果disable,则就会因为找不到需要导出的符号而链接失败了。
导出 Function@n 的名字,一方面也可以通过 def 文件,但是这样你就得手工去算
函数会使用多少栈空间了,也许有工具可以做到这一点,但是我暂时还不知道怎么办(可
能跟nm啊objdump啊什么的有关,希望知道的童鞋能告知一下),但是,最简单的方式还
是采用__declspec(dllexport)的方法了。
那么,如果在这种情况下,依然想通过def做点事儿,怎么办呢?上面的网站给出了
一些方法来得到def文件,并修改后重新链接,但是反正最简单的方法还是需要
__declspec(dllexport)的,这里我们就不讨论了。
那么,现在就是链接问题了:如何链接到有@n后缀和没有@n后缀的函数去呢?
首先,如果导出的DLL里面有@n后缀,那么事情就非常简单了,只需要直接链接即可
,但是如果DLL里面没有@n后缀,那么你直接的链接肯定是会链接失败的,这里发现一个
方法,就是依然采用-Wl,--enable-stdcall-fixup,这样可以链接到没有@n后缀的函数去
。
但是,我这里遇到了更麻烦的问题……
首先,上面的所有讨论,都基于一个特点:我们直接链接的是DLL,而且我们没有采
用任何__dllspec(dllexport)修饰,这意味着,我们不打算链接到__imp_这样的符号上去
,但是,如果DLL上面的某个符号没有导出名字,怎么办?
我最近就遇到了这样的问题,主要是Google的ANGLE项目,即利用DirectX来实现
OpenGL ES 2.0 接口的一个库。它的libglesv2.dll这个dll里面,有六个符号是没有导出
的,只有位置,没有名字,因此,如果直接链接这个dll,链接是一定会出错的,因为没
有名字嘛。
那么,链接我们产生的libglesv2.dll.a行不行呢?不行。因为为了保证DLL导出的符
号的顺序,这个dll的符号导出全部采用了def的方式,而def里面没有@n后缀!
在这种情况下,我们产生的libglesv2.dll.a里面的符号,也是没有后缀的,即使是
__imp_符号,也没有@n的后缀,完全无法链接。
我最后找到了Mozilla的网站,看样子火狐用的也是这个库来提供WebGL支持,他们的
解决方法是,直接在def文件后面加两行:
- ; GCC has problems with linking to undecored stdcall functions,
- ; so we explicitly add aliases for APIs used by EGL
- glGetProcAddress@4=glGetProcAddress
- glBindTexImage@4=glBindTexImage
-
复制代码 给DLL里面的某些符号起了别名,这样才导入成功。感觉这个办法治标不治本啊,万
一隐藏符号太多,岂不是必须一个一个的这么去加么= =而且这样,本质上是把隐藏符号
给变得不隐藏了呗,最终还是可以在DLL文件的导出表里面看到这两个加了@的符号。
所以,虽然问题解决了,但是我还是留存了两个疑问,记在这里,如果有牛人知道答
案的可以告知一下,或者以后我找到了答案,我再回来补完。
1. MinGW如何链接.a导入库里面没有@n后缀的名字?
2. 如何让MinGW即使是在有def文件的情况下,也始终在.a导入库里面产生@n和不带
@n的名字呢?已经试过了--add-stdcall-alias,没效果,最理想的的状态是这样的
,对于每个符号,在.a里面有四个符号导出:Function, Function@n,
__imp_Function, __imp_Function@n,这样就不愁找不到符号的问题了。
|
|