首先建立__cdecl 调用约定函数的动态链接库。
FirstDll.cpp
#include <windows.h> 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
链接:
link FirstDll.obj lib.obj /nodefaultlib kernel32.lib libc.lib /dll
生成两个文件:FirstDll.lib,FirstDll.dll,前者为后者的导入库
主函数调用,这里先考虑静态调用方式。
//Main.cpp #include <stdio.h> #include <stdlib.h> 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 <stdio.h> #include <stdlib.h> #include <windows.h> 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++导出函数名