在上一文中我们采用对整个类导出不导入的方式来避免local vftable的问题,但是这也带来了另一个问题,那就是类静态成员变量在另一个模块无法导入使用。
首先还是在上一篇中的例子上稍作修改来说明问题吧。如下图:
在TestA类上新增了一个static的int型成员_sTestInt,并在一个内联函数setTestInt中使用这个static成员
在另一个使用TestA类的模块中增加对setTestInt内联函数的调用
编译链接,会发现报上面的链接错误
解决方法很简单,如下:
在需要导出使用的静态成员变量上增加dllimport的声明就可以了
到这里本篇最主要的议题就结束了,下面探讨一下def导出静态变量引起的问题,我们知道在Windows平台另外一种导出的方式就是采用def文件,针对上面的例子,我们采用def导出类静态成员变量试试。
去掉静态成员变量_sTestInt前面的导出宏,新建def文件,并在其中添加_sTestInt的导出符号
编译链接,很完美链接通过,接着启动运行,很不幸,程序崩溃了。
这是为什么呢?我们接着调试找找原因:
汇编代码直接看上去很正常,setTestInt函数被内联为直接对Test::_sTestInt成员赋值,可是这一句执行就会崩溃,那让我们看看Test::_sTestInt这个符号对应的地址中是什么吧。
我们发现Test::_sTestInt中存储的是到实际_sTestInt变量的跳转。我们对这个jmp指令赋值自然是不合法的。为什么会这样呢?
《加密与解密》一书上有上面一段话,意思就是如果导出的符号前没有dllimport暗示的话,编译器在编译时无法区分用到的符号是否来自其它模块,所以就会为其生成桩代码(stub),链接时在桩代码处填入到实际符号的跳转。
现在问题清晰了,要解决也很简单,在用def导出的静态变量前一样需要dllimport的声明。修改后我们再反汇编调试看一下:
现在的赋值就变成了对实际导出变量的赋值,程序运行的很正常
好了,这一篇就这样结束了,再来吐槽下微软吧。在用dllexport方式导出和def文件导出时效果还不一样,dllexport导出时如果没有dllimport导入会有链接错误,而def导出不需要dllimport也可以链接通过,但是运行崩溃。具体dllexport导出和def有什么区别,身边缺少资料,还没细细研究,就个人认为,你就让def导出就和dllexport一样,直接报链接错误好了撒,让它能链接通过,但是运行崩溃,这有点坑人。