weibing

随想Visual C++ “导出/导入类的严重危害”

VC中有一个关键字__declspec(dllexport), 其目的很简单,就是导出符号,供其它可执行模块使用,主要用于动态链接库(DLL), 然而也可用于EXE模块

与此相对应,__declspec(dllimport), 将指明由动态链接库导入符号,除了导入全局变量/类的静态成员变量,很多时候可以忽略, 这个时候寻找符号的优先级将有所不同
 

当这两个关键字配合类使用,将会产生比较尴尬的问题了

 

DLL:

struct __declspec(dllexport) A

{

    void TestInline()

{

    printf(from dll\n);

}

};

 

EXE:

struct __declspec(dllimport) A

{

void TestInline()

{

printf(from exe\n);

}

};

 

int main()

{

    A obj;

    obj.TestInline(); //输出 from dll!

 

    return 0;

}

 

危害1:

A中的内联函数全部失效

 

这当然可以从符号链接表中发现链接符号的踪迹来表明内联函数失效了,不过有一个更简单的方法,我们可以在EXE的代码中把TestInline内联函数的定义代码加以简单修改,让其输出其它的字符串测试,这个时候运行会发现,输出的字符串仍然是from dll(注意编译为Release发行版, DEBUG版会将内联函数作为普通函数处理), 这就说明了,内联函数被完全忽略了。如果这个时候,还能想到TestInline的定义代码还有什么作用的话,那就是白白消耗编译的时间,编译期间编译器辛辛苦苦生成的代码,链接器会毫不留情地忽略掉。

 

很显然,忽略掉内联函数的代码,会有一个直接的副作用,那就是使应用程序的运行期变缓慢,特别是内联函数被大量在循环内调用,而这个内联当成普通函数处理了

 

C++运行库中有大量的内联成员函数,除了模板类以外(当然,非完全特化的模板类不可能支持导出的),基本上都是以导出类的形式导出,这样也就意味着,动态链接C++运行库的程序,这类内联函数全部失效,编译时间可一点没少。

 

危害2:

私有成员函数,以及原本不想导出的函数(没有多少重用价值,导出后还要承担维护版本兼容性的责任),也被”不知不觉”
地导出了,对于私有(private)成员函数, 导出到外面有什么用呢?占据了符号表的一个位置,可是谁又能调用到呢?
如果要想调用,看来要强制把private变为public了,但是VC的符号形成机制包含了public/private/protected,
也就是通过了编译也无法通过链接,唯一可能的办法就是,修改DLL头文件,在类里面增加”friend”, 在EXE端,
改变共享库DLL头文件的办法,很显然不是个正规方案

另外,导出了这些“无法调用”的函数,也影响了链接器的优化。对于这些函数,如果DLL模块内部也没有调用到,
本可以完全把这些代码优化掉,但是由于导出,链接器将无能为力, 这也直接增加了DLL文件的体积

由此想到的:

MFC/ATL动态库似乎了解到这个问题,所以,它们宁可一个一个成员函数进行符号导出,也没有进行整个类的导出,
为什么它们不把这个方式推荐给VC动态运行库呢?

VC传统静态链接运行库的方式虽然有某种弊端,但是却可以完全避开上述提到的问题,而且脱离了版本型DLL的依赖(MSVCR71.dll, MSVCP71.dll, MSVCR90.dll, MSVCP90.dll), 对于这类DLL的依赖,将给以后程序升级带来隐含并难以解决的问题,大家可以考虑如下情形

版本1:A.exe依赖于B.dll, 并且它们同时依赖于msvcp71.dll

版本2: 由于A采用了新的VC版本进行编译,A.exe现在依赖于msvcp90.dll了,B.dll没有升级,仍然依赖msvcp71.dll

这个时候,进程中的内存结构产生了微妙的变化,举例来说,原来msvcp71.dll中的全局变量(例如cout)同时被A.exe和B.dll使用。现在呢,A.exe使用的是msvcp90.dll中的cout, B.dll使用的还是msvcp71.dll中的cout, 这种耦合的变化,将会导致程序执行逻辑的变化(如果你运气好,也许会没有问题的),例如,如果你在A中设置了cout的格式,然后调用B的接口,最后B的接口通过cout输出,版本2与版本1将会拿到不同的执行效果(源代码可一点没变哦,仅仅是换了个编译器编译)。大型程序逻辑将是非常复杂的,看来还是不要升级编译器进行编译的好,因为这将导致dll依赖关系的变化,这真是个无奈的选择

 

混合编译选项 /clr看来要考虑允许静态链接运行库了(现在的VC版本只能动态链接运行库的),因为运行库DLL存在版本和效率缺陷

 

大家有没有发现LINUX操作系统下的so文件一般比windows下的要大很多,其中一个原因是这个模型相当于默认全部导出,即使是全局函数也是一样,哪怕你临时写一个没意义的函数,忘记去掉了,也一并导出,虽然你并没有给外界头文件,告诉这个接口如何被调用,但是这个接口已经事实存在了。虽然这样会简化一些工作,但是个人觉得,是非常不负责任的,特别是现代的应用大都是多任务模块化的,这样做潜在地多吃掉不少内存和硬盘,而且对于版本兼容性,将带来麻烦。我对LINUX了解不多,不知道是否有支持指定导出函数的方法呢?

COM(Component Object Model), 也许有很多其它弊端,但却解决了上述问题,而且可以拿掉DLL文件名中描述版本的丑陋数字

 

个人认为,发展C++的二进制接口标准ABI(Application Binary Interface)是非常重要的,COM应该算一个ABI标准吧。总是静态地依赖源代码作为可重用模块,真的弊端很大,多任务时占用的磁盘和内存也会增加。托管编程模型毫无疑问解决了这个问题,当然也带来了其它方面的一些问题。C++在编程模型上可以说比托管编程模型要简单很多,但缺少一个二进制标准。解决这个问题将比在语言层面增加几个关键字要重要很多

posted on 2011-08-01 23:54 魏兵 阅读(3493) 评论(13)  编辑 收藏 引用

Feedback

# re: 随想Visual C++ “导出/导入类的严重危害” 2011-08-02 02:35 fklz

晕死,显然是你傻逼不会用!真没见过这么傻逼还自作聪明的  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害” 2011-08-02 14:47 Enic

楼上非常正确,,,  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害” 2011-08-02 16:24 乐购网

来好好的学习一下。  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害” 2011-08-02 18:46 空明流转

你用啥没危害。。。  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害” 2011-08-02 19:44 bennycen

看成了“人类的严重危害”  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害”[未登录] 2011-08-03 09:19 剑孤寒

首先,内联函数全部失效并不是导出类的错,而是用的人的错,如果你要内联就不应该用导出类
其次,私有函数导出并不会显著增加dll体积,如果dll体积很重要也是有办法回避这个问题的,另外,私有函数导不导出跟维护没啥关系吧,都是你内部用的,你不用导出类内部函数出问题还不是也要维护
最后,静态链接库如果用了stl也是依赖crt的  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害” 2011-08-03 10:09 cr

不是这么用的。  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害” 2011-08-03 10:48 terminator

傻逼成这样居然还能长篇大论?够寒碜人的。  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害”[未登录] 2011-08-03 19:54 cexer

那些出口就骂人的,都是些畜牲吗?你爹妈没教过你怎么说人话?  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害”[未登录] 2011-08-04 04:37 cexer

搞笑啊,楼上你冒用ID很光彩?看来被人骂傻逼没骂错  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害”[未登录] 2011-08-04 09:11 a

人家理解的不对,大家指出问题、提供解决办法就行,何必骂人呢?骂完了对你有什么好处啊。朋友多了路好走。树个敌人对自己有益处?年轻人怎么这么多火焰呢?这么有活力,还不如去改造这个社会做贡献呢。在团队方面来讲,骂人首先就没有团队精神,不合作啊。  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害” 2011-08-05 13:07 欲三更

1.按照lz的逻辑,F22战机的严重危害是它们太费油——你都使用导出类了,还在乎什么内联函数?

2.stl代码里面什么时候有导出类的符号了?VC的crt说的是C库。

3.这个时候,进程中的内存结构产生了微妙的变化……以我的经验,任何一个头脑正常的工程师都不会做这样的事情!

4.现在谈COM之类的东西,我怎么感觉这是5年前的文章?  回复  更多评论   

# re: 随想Visual C++ “导出/导入类的严重危害” 2011-08-05 20:00 fly C++

@剑孤寒
这个问题我深入考察过MFC动态库与CRT动态库的不同,发现MFC头文件中的内联成员函数是有效的(这可以通过简单修改头文件中的定义代码,看运行时的效果可以证明,你修改的代码产生作用,就说明并没有用到DLL内部的定义),其原因是MFC的头文件并没有使用户通过__declspec(dllimport) class 的方式使用,但是CRT动态库,却是隐含的让使用者采用这种方式,其结果是,当用户动态链接CRT时,其非模板类内联成员函数(模板类不能真正导出定义),实际上都不可能作为内联函数了,因为使用的都是DLL内部的定义代码(这也可以通过上面的方法发现),这将一定程度上影响程序执行效率,并使CRT导出接口庞大(内联函数导出没有意义,因为暴露的头文件已经有定义代码了,为什么还要使用DLL的定义体呢,而且会伤害运行效率)。我碰到过一个程序,静态链接CRT比动态链接CRT要快非常多,并不是程序加载DLL产生的时间延迟,而是程序运行起来之后产生的问题,花了非常多的时间,才确定是这个原因。如果大家有兴趣,我可以贴出一段代码

关于接口维护, 对于C语言写的DLL, 只是导出一部分外界需要用到的函数,如果C++导出了所有内部定义的东西,不管外界是否要用到,而且私有成员函数对于外界也不可能用到,多少感觉有些那个

以上谨代表个人观点的  回复  更多评论   



只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


My Links

Blog Stats

常用链接

留言簿

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜