动态链接到静态链接故障排除

来源:互联网 发布:nginx 单ip 多域名 编辑:程序博客网 时间:2024/06/05 08:18

背景:

在VS2005下开发,考虑最后发布的文件夹大小,工程采用动态链接,版本发布的时候,内部机器上都装好vs2005,运行都正常。测试方都在无vs2005开发环境下进行,由于程序找不到对应的dll文件,程序报错“由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题”。上一个版本将VS2005下CRT相关的dll都拷入应用程序对应目录,本版本没有考虑到新的开发可能引用新的动态链接库,故而未做新机器上的自测,导致该问题出现。

由于在xp以上的系统,出现此类错误时报错是“由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题”,对定位到底缺少什么哪个dll造成难度(vs2005搞出来的程序报错很无语,记得一般程序少dll,警告窗口都会提示的,可能不是系统的dll不好定位吧)。

 

思路一:静态链接

之前在版本里添加vs2005下CRT的dll是通过网络查找添加后,添加后问题即得到解决,这次缺少的dll具体是哪几个就不好定位了。所以没有采取这个可能最简单但不知道何处下手的方法。

采用静态链接最后的发布包必定是很大的,因为这样的发布包是不依赖于SDK之外的动态库。采用静态链接遇到的最大问题就是各个工程使用的运行时库配置可能不同,工程属性---C/C++---生成代码,关于多线程有四个选项:多线程(MT)、多线程调试(MTd);多线程DLL(MD)、多线程调试DLL(MDd)。这四个选项配置了工程链接时使用的运行时库。会有以下两个矛盾的地方:

1、  如果工程实际使用的库和配置的库一致,而各个工程配置的库不同(如A工程使用了MTd库,B工程使用了MDd),最后链接的时候会出现运行时库关于某个符号重定义。

2、  如果各工程采用同样的运行时库配置,而工程内部文件可能使用的并不是配置的运行时库,则链接会出现很多符号未定义。

为了规避1中运行时库难以避免的重定义,采用2的做法,结果libcairo工程有53个链接错误,webCore工程有200+的链接符号找不到。一开始认为这是工程链接的运行时库缺少对应的实现(后来觉得这个分析太不对,各个运行时库的功能是差不多的,不会少这么多符号),于是回到1保持各个工程单独的运行时配置,出现很多libcmtd.lib和msvcrtd.lib的重定义。

 

 

 

 

通过搜索学习,可以在工程的链接命令行的附加选项加入/VERBOSE:LIB;可以在链接的时候观察系统链接的顺序,而后在工程属性----链接器----输入-----忽略特定库中将有冲突的库忽略,可以避免重定义。但是链接被忽略特定库的工程会有符号未定义,比如A工程用了MTd运行时库,B工程用了MDd运行时库,为了避免重定义忽略了MDd库中的libcmtd.lib,B工程中用到这个库的符号的文件就链接不过。而如果忽略了msvcrtd.lib,B工程也会有对应的文件链接不通过。忽略来忽略去,还是没能把任何一个忽略,因为多工程的配置存在潜在的冲突。

    后来是偶然发现采用2的做法时,不改变libcairo的链接运行时库,工程属性---常规采用在动态dll中使用MFC时,可以顺利链接通过;由此分析并不是运行时库缺少对应符号,而是在静态链接中使用MFC少链接了一些特定的库。试着保持静态链接中使用MFC不变,然后把MT,MTd,MD,MDd四个配置全部试了一下,全部链接不通过,验证了!于是找到对应的符号名,msdn查一下所在lib,在vs2005中搜索,拷到对应工程,在工程属性---链接----输入,加入附加依赖项。终于静态链接通过了,版本也挺大的311M。

 

思路二、release版本静态链接

 

在用debug版本做静态链接的时候,折腾了好长时间,没有办法。换成release版本试一试,于是采用debug版本1的做法,忽略了一些冲突的运行时库,这回居然链接通过,原来少的那些符号只是在debug宏下使用。在裸机上试了一下,PC widget的码头起来了,兴奋了一小会。但是打开widget的时候,可怕的出错窗口弹出来了。在_AfxActivationWndProcMFC激活窗口函数的时候出现非法访问,从代码上一个个定位,结果是出现在widget上加载了webcore的页面响应消息号为50054的消息后出错。网上学习后,确实有消息响应函数未按规定格式声明,在release模式下异常;或者可能是全局变量未初始化。时间原因就没有在这个上面继续追踪,而转到死马当活马医的方法。

最后定位release版本出错的位置,一开始的误解就是release版本不可调试,其实是可以的。Vs2005工程配置---链接器---调试----生成调试信息选是,C/C++---常规---调试信息格式选程序数据库(/Zi);C/C++----优化选禁用。一开始的症状都是在消息函数里处理某个特定消息时出错,也没有进行调试;查找release版本常出错的原因是消息映射ON_MESSAGE将某一个消息映射到不带参数的函数上,

#define  ON_MESSAGE(message,  memberFxn)  \  
           {  message,  0,  0,  0,  AfxSig_lwl,  \  
                       (AFX_PMSG)(AFX_PMSGW)(LRESULT  (AFX_MSG_CALL  CWnd::*)(WPARAM,  LPARAM))&memberFxn  }  这个宏展开的时候是要将两个参数压栈(WPARAM和LPARAM),然后调用函数指针; 如果处理函数没有参数定义,换句话说,函数返回的时候不会平栈,会导致Release版程序非法操作而出错。由于VC在对MFC的Debug版程序进行编译的时候,会在函数的后面加上一段类似上面的代码,那段代码的功能就是检测esp,看看栈是否是平的,如果不平则强行平栈,因此Debug版程序不会出这种错误。查看了工作空间所有自己的消息映射,消息函数都是规范的声明和定义。很失望!

可以调试就方便了,从出错处一级一级往上找,找到空指针是怎么产生的,原来是debug目录下放了一个字库文件,release下没有,导致加载字库face为空,代码又写得不规范,对空指针进行操作;错误很简单,但查起来不轻松。产生如下心得:最好在release版本下开发,链接选项选上产生调试信息,因为debug版本对变量初始化加了很多纠错,不规范的代码在release版本下可能会埋下很多定时炸弹,当代码量大的时候就比较难于查找定位了。

 

思路三、查找动态链接缺少的dll库

 

实在没办法采用这种也许漫漫长夜也许柳暗花明的做法。但谁知道,刚伤心欲绝下了海,就捞到了宝藏。原本我是想把MFC下所有的动态库全部拷到版本下,一个个删除排雷。突然先赌赌能不能做暴发户的心态作祟,只把Microsoft.VC80.DebugMFC下的四个dll拷进去,居然就这么搞定了。