__stdcall,__cdcel,extern c 和导出函数名

        无论c或c++都会对导出函数改名或不改名,无论你是静态还是动态调用一个导出函数,都可能碰上改名后导致的调用失败(甚至可能是调用约定不同而导致清理堆栈出错,造成崩溃),下面分析一下改名和调用约定之间的关系。

        一.先说extern c。
        其含义是指,按c的方式编译代码。在vs7及以后的vs中,有编译选项可以直接选择“编译为:c代码”(在工程属性----》c/c++----》advance----》编译为),其效果和用extern c包裹起来一样。vs默认是编译为c++方式的。

        二.简述下__stdcall和__cdcel。
        __stdcall是从右往左压参数,由被调者清理参数的相关堆栈,windows的系统函数都是这种方式。__cdcel也是从右往左压参数,但由调用者清理参数的相关堆栈,vs编译环境默认就是__cdcel(即,函数前如果不写调用约定,那就是__cdcel)。

        三.函数的改名问题。
        一个函数编译后的名字,其实由按c还是c++编译,调用约定两者共同决定。两者互相搭配,就有四种情况。下面假定一个函数名字在代码中叫FunName,
       1.c方式(即,你用extern c括起来的那些函数,或是改编译选项则影响所有函数)和__cdcel,编译后名字为:_FunName,如果是导出函数,则在Dll的导出函数中叫FunName
        2.c方式和__stdcall,编译后名字为_FunName@x,如果是导出函数,则在Dll中也叫_FunName@x(x是所有参数占空间的总大小)。
        3.c++方式和_cdcel,编译后名字为:_FunName@xxxxxxxxx,如果是导出函数,则在Dll中叫_FunName@xxxxxxxxx(由于c++编译后的名字,包含了参数个数,类型,返回值等信息,所以改名后会比较长,这里不做详细讨论)。
        4.c++方式和__stdcall,编译后名字为:_FunName@xxxxxxxxx,如果是导出函数,则在Dll中叫_FunName@xxxxxxxxx。

        四.导出函数的问题。
        Dll中的导出函数,其名字信息,其实位于两个位置,其一,在该dll对应的lib中;其二,在该Dll中。当我们使用静态的方式去调用一个导出函数时,我们会在其lib中寻找该函数的地址信息;当我们使用动态的方式(LoadLibrary和GetProcAddress)调用一个导出函数时,我们会在其Dll中寻找该函数的地址信息。
        我们都知道windows的系统函数,是extern c和__stdcall的。这种方式下,一个导出函数是会被改名的,无论在lib和dll中都是_FunName@x形式,但windows通过def模块,修改了dll中的函数名为FunName,而在其lib中还是_FunName@x。lib中名字为_FunName@x,就确保了我们静态调用时,能链接到匹配的名字。dll中函数名字为FunName,就保证了动态调用时,可以传递正常的函数名给GetProcAddress(否则传一个改名后的函数名会多么恶心)时,能在dll中找到匹配的名字。
        所以,调用约定和extern c存在与否,导致的改名,影响了我们能否正确调用到一个函数。当然,调用约定在一个函数的调用者和被调者之间一定要一致,否则即便函数名字上面不出问题(可以得到函数的地址了),函数返回时清理堆栈出错,立即就会崩溃!



你可能感兴趣的:(c,windows,dll)