windows的动态库有几个需要注意的地方,因为相比较linux的动态库而言,需要设置的地方实在太多了。
1、首先需要注意的是运行时库的模式的选择一定要一致的!
原因在于每个dll库的堆管理器都是建立在运行时库crt的副本的基础之上,而每一个库必然有一个运行时库的副本。这里涉及到几个选择问题。
第一,选择mt还是md的问题,
1)mt表示的是mutilt-thread single,表示的多线程的静态运行时库。如果选择这种模式,需要注意的是每个动态库在程序运行时使用的数据以及堆都是建立在各自的运行时库的副本的基础之上的,换句话说,就是各自使用各自的堆内存,因此每个库使用自己的内存都是老死不相往来的,只有在使用非动态库分配的内存(也即程序主体分配的内存)时才能真正做到通信和访问。如果谁要是越界,就可能存在这样一个风险,例如如果存在这样一种用法:动态库A提供的接口不仅给动态库B使用,还直接给程序主体使用;而此时若动态库A提供的接口中返回一个在其堆中分配的内存块的地址,然后是传给动态库B使用的,但是释放时则是在动态库B传给程序主体的main函数中去释放的,这是由于main使用的动态库A的运行时库的副本是与动态库B所依赖的动态库A的运行时库的副本是不一致的,因此就会出现指针的非法使用,释放了不正确的内存块地址,造成堆损坏。---这种用法的一种更隐蔽的错误用法是:并没有将动态库A的指针返回出去,而是提供了一系列可以操作内部内存堆块的函数接口;但是仍然会出现这样一种错误,即在上述动态库B中使用调用动态库A的接口分配内存去完成某些操作并存储数据,而在程序主体的main函数中去调用动态库A的接口去读取认为已经存储的数据然后操作该数据改写,但是此时由于两个接口调用的副本不一致!!!所以预期认为是一致的数据,其实是不一致的!!!最终造成的就是内存访问越界,野指针造成的错误简直可以是千奇百怪!!!
2)md表示multi-thread dll,表示的是多线程的动态运行时库。如果选用的是这种模式,那么同样的一个动态库,使用的都将是同一个运行时库的副本。上述例子中的动态库B和程序主体main函数调用动态库A中的接口时,使用的即同一个堆内存,这样就不会存在这种问题了。
第二、mdd还是md?或者mtd还是mt?
这里的mdd和mtd多出来的d就是debug。因为mdd和md的两个不同的功能库是可以正常编译链接的,和上述的mt和md一样,但是这样有什么风险?关键在于选定了不同的运行时库debug版本和release版本,可以看到不同模式下的运行时库的lib文件的都以及已经不同了,按照微软MSDN上提供的说明,这些lib文件会在运行时库链接到obj文件中去指示程序如何去解析查找外部的函数符号,既然是debug的,那么显然有很多保护措施,除了上述的解析符号相关,还有内存操作和分配方面的保护在每次分配内存的时候会多分配一些字节用来存储当前分配的信息,这些都会造成运行时的差异。在stackoverflow上也有人问这样混用有什么风险,没有具体说明明确的风险,但是这种内存操作检查和内存泄漏检查显然是有可能造成内存不一致的。另外这里值得怀疑的是,如果使用不同模式的运行时库,是不是也说明可能出现之前说的mt和md之间那种区别,因为他们映射到内存中的显然不是同一个dll!!!
所以最好的办法是:如果是mdd的就全用mdd的吧,或者全部md的,调试?谁告诉你md的这种release版本就不能添加生成调试信息,是的,一样可以然后继续苦逼的调试,坑爹的微软啊,搞这么多事干什么。
2、编译器版本一定要是一致的。vs2005 or vs2008 or vs2010 or vs2013编译出来的动态库?
就是说所有的库都要是一致的编译器编出来的,为什么?因为每个编译器的运行时库的版本都不一致,那么就会出现一个问题,不同编译器版本编译出来的运行时库其实共享的是不同的运行时库版本。如果运行时起来,很有可能接口是兼容的,在程序最终生成打包时是一样能正常编译链接运行的。但是运行时,不同库是使用的不同的运行时库的lib导致的兼容性问题,一样会让你的库出现莫名其妙的兼容问题,是的,就是这么坑爹。之前看过一篇英文文档说是可以将每次编译时的运行时库保存下来随库一起发布,让后续每个使用该库的都使用这个运行时库来进行编译链接,但是这样会不会太胡闹了一些,库多的时候,涉及到第三方巨多的时候,估计你就哭了,反正我觉得管理起来应该还是很有难度的。不过,这个方法我也没有找到哪里可以把IDE中的运行时库固定下来的地方,我们还是老老实实的在程序编译时对号口令一致用某个编译器就好了!!!
3、各个库的位数要一致。32位还是64位的库?
没错,现在内存一多,大家都开始向大家64迁移了,这似乎不差钱的内存啊,反正是客户的,哼哼。但是这样32位和64位很有可能会被大家忽略,一个不小心就变成了32位库和64位库齐飞。由于各种偏差可能会编译链接仍然可以顺利通过,但是跑起来之后你就等着哭吧。如何查看?有一个命令,同linux上可以通过file命令查看dll是否为32位的,如果是32位的库会有提示80x86 32-bit的显示字样。但是lib文件暂时没找到可以查看的地方。
4、函数符号表的lib文件和动态库dll文件是否一致(不仅外面名字一致,内部也要一致,要表里如一)
调试过程中出现了这样一个问题lib和dll文件名是一模一样的,除了文件后缀名。但是程序撒欢开跑的时候,每次都在加载动态库的时候说找不到对应的动态库。例如,libdependmd.lib和libdependmd.dll运行时,总是说找不到libdepend.dll。最后发现的原因是,由于加载的是 libdependmd.lib,而生成时编译输出的文件其实是 libdepend.lib但是给库时候某个小伙伴听说要区分md和mt就把库名改了,但是库名改了不是就没问题了,其实lib库文件里会把dll的名字也带在里面,链接时使用的lib就是这样告诉其他要使用的库,它所对应的库名的。具体查看方法是,可以用ultra工具打开lib文件,查看文件尾部其对应的asscii码中会有对应的dll的名称,再也不用担心lib和dll名称不一致了!!!
上述五个步骤基本可以把动态库中各种模式不匹配造成的问题排除了,后续工作就看你自己了!