SpartacusIn21

专注:c++,python,d3d,设计模式,人工智能,并行计算

zlib报“LNK2001:无法解析的外部符号”错误

  这个错误一般是由使用导出dll时未加载对应的lib文件导致的,但是工程在正确配置了lib文件的情况下仍然报这个错误,经查,是由于dll导入工程和dll导出工程的函数调用约定不一致导致的。

一、函数调用约定

  首先,我们由函数的调用约定说起,microsoft的vc默认的是__cdecl方式,而windows API则是__stdcall,如果用vc开发dll给其他语言用,则应该指定__stdcall方式。堆栈由谁清除这个很重要,如果是要写汇编函数给C调用,一定要小心堆栈的清除工作,如果是__cdecl方式的函数,则函数本身(如果不用汇编写)则不需要关心保存参数的堆栈的清除,但是如果是__stdcall的规则,一定要在函数退出(ret)前恢复堆栈。

windows有如下五种调用约定:

1.__cdecl
       所谓的C调用规则。按从右至左的顺序压参数入栈,由调用者把参数弹出栈。切记:对于传送参数的内存栈是由调用者来维护的。返回值在EAX中因此,对于象printf这样变参数的函数必须用这种规则。编译器在编译的时候对这种调用规则的函数生成修饰名的饿时候,仅在输出函数名前加上一个下划线前缀,格式为_functionname
2.__stdcal
        按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,切记:函数自己在退出时清空堆栈,返回值在EAX中。  __stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。如函数int func(int a, double b)的修饰名是_func@12
3.__fastcall
       __fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。这个和__stdcall很象,唯一差别就是头两个参数通过寄存器传送。注意通过寄存器传送的两个参数是从左向右的,即第一个参数进ECX,第2个进EDX,其他参数是从右向左的入stack。返回仍然通过EAX。
4.__pascal
       这种规则从左向右传递参数,通过EAX返回,堆栈由被调用者清除。

5.__thiscall

        仅仅应用于"C++"成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

二、解决方法

  之所以会报LNK2001,是因为编译zlib(dll)的工程默认有预处理器定义ZLIB_WINAPI,ZEXPORT在zconf.h中的定义为WINAPI:

/* If building or using zlib with the WINAPI/WINAPIV calling convention,
* define ZLIB_WINAPI.
* Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
*/
# ifdef ZLIB_WINAPI
# ifdef FAR
# undef FAR
# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
# define ZEXPORT WINAPI
# ifdef WIN32
# define ZEXPORTVA WINAPIV
# else
# define ZEXPORTVA FAR CDECL
# endif
# endif
#define WINAPI      __stdcall

所以,zlib(dll)的导出函数调用约定为__stdcall,和zlib(dll)的导入函数调用约定__cdecl不一致。

针对zlib编译dll文件编译出错,有以下两种解决办法:

1. 在zlivc工程中去掉预处理器定义ZLIB_WINAPI(预处理定义:vs工程属性->C/C++->预处理器)

去掉该定义后,宏定义ZEXPORT在zconf.h文件中实际定义如下:

#ifndef ZEXPORT
#  define ZEXPORT
#endif

所以导出函数unzGoToNextFile

extern int ZEXPORT unzGoToNextFile (unzFile  file)

等价于

extern int unzGoToNextFile (unzFile  file)

也等价于(vs默认是__cdecl函数调用约定)

extern int __cdecl unzGoToNextFile (unzFile  file)

所以在新的工程中导入zlib相关的库文件和dll文件后,编译dll的工程和引入dll的工程的函数调用约定均为__cdecl方式,导出函数能够正确解析了,编译自然就通过了。由上宏定义注释可知,标准编译ZLIB1.DLL是没有ZLIB_WINAPI预处理定义的,函数调用约定也是__cdecl,所以这里推荐该解决方案。

2. 在引入zlib工程中添加预处理器定义ZLIB_WINAPI

在引入dll工程中添加ZLIB_WINAPI定义后,宏定义ZEXPORT在zconf.h文件中实际定义如下:

#    define ZEXPORT WINAPI
#define WINAPI      __stdcall

与导出zlib的dll工程均为__stdcall函数调用约定,编译也能顺利通过。

 

posted on 2017-05-04 21:19  SpartacusIn21  阅读(1613)  评论(0编辑  收藏  举报

导航