__cdecl与__stdcall 调用约定在动态链接库调用中不同的表现

首先建立__cdecl 调用约定函数的动态链接库。

FirstDll.cpp

#include 

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    return TRUE;
}

DLL入口函数。

lib.h

#ifndef LIB_H
#define LIB_H
extern "C" int __declspec(dllexport) add(int x, int y);
#endif

申明导出函数。

lib.cpp

#include "lib.h"

int add(int x, int y)
{
	return x + y;
}
实现函数,采用默认的__cdecl调用约定。

编译:

cl FirstDll.cpp /c
cl lib.cpp /c

这里用缺省C运行时库libc.lib,相当于/ML

链接:

link FirstDll.obj lib.obj /nodefaultlib kernel32.lib libc.lib /dll

生成两个文件:FirstDll.lib,FirstDll.dll,前者为后者的导入库

主函数调用,这里先考虑静态调用方式。

//Main.cpp

#include 
#include 
extern "C" int add(int , int );
int main()
{
    int a, b;
    a = b = 1;

    int c = add(a, b);
    printf("%d\n", c);
    system("pause");

    return 0;
}

编译:

cl Main.cpp /c

链接:

link MainD.obj FirstDll.lib /nodefaultlib kernel32.lib libc.lib

调用成功,屏幕输出2,再考虑动态链接:

//MainD.cpp

#include 
#include 
#include 
int main()
{
	int a, b;
	a = b = 1;
    HMODULE hMod = LoadLibrary("FirstDll.dll");
    typedef int (*pfunc)(int,int);
    pfunc add = (pfunc)GetProcAddress(hMod, "add");
	int c = add(a, b);
	FreeLibrary(hMod);
	printf("%d\n", c);
	system("pause");

	return 0;
}

可以得到同样的结果,用dumpbin查看FirstDll.dll 中的函数:

dumpbin FirstDll.dll /exports > info.txt

可以看到

    ordinal hint RVA      name


          1    0 00001010 add


也就是说 cdecl 调用约定没有对函数进行重命名。接下去考虑 stdcall 调用约定。

分别调整lib.h,lib.cpp

//lib.h
#ifndef LIB_H
#define LIB_H
extern "C" int __declspec(dllexport) __stdcall add(int x, int y);
#endif

lib.cpp
#include "lib.h"

int __stdcall add(int x, int y)
{
	return x + y;
}
其中,显示申明为stdcall调用约定。

编译链接后同样生成lib文件和dll文件。

调整Main.cpp中的函数申明语句为:

extern "C" int __stdcall add(int , int );

编译链接后可以使用,但是用dumpbin查看此时的dll则有

          1    0 00001010 _add@8

这里显示stdcall以将函数重命名。其中8指的是参数占8个字节。


那么再考虑动态链接:

将MainD.cpp中的

    typedef int (*pfunc)(int,int);
改为

    typedef int (__stdcall *pfunc)(int,int);
运行时出错,而用"_add@8"函数名调用成功。说明显式调用dll函数在stdcall调用约定时需要额外的步骤。


这里考虑两种解决方案:

(1) 使用 link.exe 的 EXPORT 参数;

(2) 使用 DEF 文件。


先考虑第一种方案,调整 FirstDll 的链接命令:

link FirstDll.obj lib.obj /nodefaultlib kernel32.lib libc.lib /dll /export:add=_add@8

替换掉原先的lib和dll,此时调用GetProcAddress中的函数名为"add" 和 "_add@8" 都可以调用成功。


再考虑第二种方案,使用DEF文件:

写一个 t.der 文件:

LIBRARY "FirstDll"
EXPORTS
    add @ 1
此外可以调整lib.h中的导出函数申明:

int __stdcall add(int x, int y);

即省略 extern "C" (2013.5.28 批注:静态调用时函数申明也应当省略,并且确保dll的编译器和主调程序的编译器为同一,以确保编译器生成的函数名相同,链接器可以找到外部函数)和 __declspec(dllexport) ,调整FirstDll的链接命令:

link FirstDll.obj lib.obj /nodefaultlib kernel32.lib libc.lib /dll /def:t.def

此时GetProcAddress 中 "add" 为有效函数名,调用成功。除此之外,还可以用函数序号调用:

    pfunc add = (pfunc)GetProcAddress(hMod, (LPCSTR)1);

无论是显式动态加载链接(LoadLibrary , GetProcAddress),还是隐式申明函数、链接导入库FirstDll.lib ,都能使用此DLL文件。


小结:当导出函数使用stdcall调用约定时,推荐的方案为:定义一个DEF文件,重新命名函数,同时在dll头文件与调用文件中申明应相同,即同时存在或不存在extern "C" 。


对于 extern "C"

附:lib.h 中申明,lib.cpp 包含 lib.h ,lib.cpp 无论申明与否都按C语言导出函数名

lib.h 中不申明,lib.cpp 包含 lib.h ,lib.cpp 申明则编译不通过

抛开 lib.h ,直接在 lib.cpp 中申明按C语言导出函数名

除此之外按C++导出函数名


你可能感兴趣的:(Win32,SDK,C/C++,编译相关)