输入函式__declspec(dllimport) 与输出函式__declspec(dllexport) 有什么区别呢?我知道他们不同,但差别在哪呢?我用的全是__declspec(dllexport) , __declspec(dllimport)一般在什么时用呢?说说一般在什么时分别用到它们?
导出函式__declspec(dllexport)在dll中用
导入函式__declspec(dllimport)在要调用dll的程序中用
这是指静态连接
动态链接就不需要__declspec(dllimport)
很多书都有介绍
_declspec(dllexport) 与__declspec(dllimport) 的使用说明
__declspec(XXXXXX)是windows扩展C++的编译宏头
_declspec(dllexport)
声明一个导出函数,是说这个函数要从本DLL导出。我要给别人用。一般用于dll中 。
省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。
__declspec(dllimport)
声明一个导入函数,是说这个函数是从别的DLL导入。我要用。一般用于使用某个dll的exe中 。
不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。
相信写WIN32程序的人,做过DLL,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。但是,MSDN文档里面,对于__declspec(dllimport)的说明让人感觉有点奇怪,先来看看MSDN里面是怎么说的:
不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。
extern "C"
指示编译器用C语言方法给函数命名。
在制作DLL导出函数时由于C++存在函数重载,因此__declspec(dllexport) function(int,int) 在DLL会被decorate,例如被decorate成为 function_int_int,而且不同的编译器decorate的方法不同,造成了在用GetProcAddress取得function地址时的不便,使用extern "C"时,上述的decorate不会发生,因为C没有函数重载,但如此一来被extern"C"修饰的函数,就不具备重载能力,可以说extern 和 extern "C"不是一回事。
C++编译器在生成DLL时,会对导出的函数进行名字改编,并且不同的编译器使用的改变规则不一样,因此改编后的名字会不一样。这样,如果利用不同的编译器分别生成DLL和访问该DLL的客户端代码程序的话,后者在访问该DLL的导出函数时会出现问题。为了实现通用性,需要加上限定符:extern “C”。
但是利用限定符extern “C”可以解决C++和C之间相互调用时函数命名的问题,但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数。
LoadLibrary导入的函数名,对于非改编的函数,可以写函数名;对于改编的函数,就必须吧@和号码都写上,一样可以加载成功,可以试试看。
解决警告 inconsistent dll linkage
inconsistent dll linkage警告是写dll时常遇到的一个问题,解决此警告的方法如下:
一般PREDLL_API工程依赖于是否定义了MYDLL_EXPORTS来决定宏展开为__declspec(dllexport)还是__declspec(dllimport)。展开为__declspec(dllexport)是DLL编译时的需要,通知编译器该函数是需要导出供外部调用的。展开为__declspec(dllimport)是给调用者用的,通知编译器,该函数是个外部导入函数。
对于工程设置里面的预定义宏,是最早被编译器看到的。所以当编译器编译DLL工程中的MYDLL.cpp时,因为看到前面有工程设置有定义MYDLL_EXPORTS,所以就把PREDLL_API展开为__declspec(dllexport)了。
这样做的目的是为了让DLL和调用者共用同一个h文件,在DLL项目中,定义MYDLL_EXPORTS,PREDLL_API就是导出;在调用该DLL的项目中,不定义MYDLL_EXPORTS,PREDLL_API就是导入。
使用dll的两种方式
方法一: load-time dynamic linking (隐式调用)
在要调用dll的应用程序链接时,将dll的输入库文件(import library,.lib文件)包含进去。具体的做法是在源文件开头加一句#include ,然后就可以在源文件中调用dlldemo.dll中的输出文件了。
方法二: run-time dynamic linking (显示调用)
不必在链接时包含输入库文件,而是在源程序中使用LoadLibrary或LoadLibraryEx动态的载入dll。
主要步骤为(以demodll.dll为例):
1) typedef函数原型和定义函数指针。
typedef void (CALLBACK* DllFooType)(void) ;
DllFooType pfnDllFoo = NULL ;
2) 使用LoadLibrary载入dll,并保存dll实例句柄
HINSTANCE dllHandle = NULL ;
...
dllHandle = LoadLibrary(TEXT("dlldemo.dll"));
3) 使用GetProcAddress得到dll中函数的指针
pfnDllFoo = (DllFooType)GetProcAddress(dllHandle,TEXT("DllFoo")) ;
注意从GetProcAddress返回的指针必须转型为特定类型的函数指针。
4)检验函数指针,如果不为空则可调用该函数
if(pfnDllFoo!=NULL)
DllFoo() ;
5)使用FreeLibrary卸载dll
FreeLibrary(dllHandle) ;
使用run-time dynamic linking 比较麻烦,但有它的好处(下面讨论)。MSDN中有一篇文章DLLs the Dynamic Way讨论使用c的宏创建一个基类pDll完成以上复杂的操作,使用时只需定义一个类继承自类pDll并对类和函数使用宏。
以上两种方法都要求应用程序能找到dll文件,Windows按以下顺序寻找dll文件:
如果系统不能找到dll文件,将结束调用dll的进程并弹出一个“启动程序时出错”对话框,告诉你“找不到所需的dll文件-XXX.dll”
一、DLL的创建
创建项目: Win32->Win32项目,名称:MyDLL
选择DLL (D) ->完成.
1、新建头文件testdll.h
testdll.h代码如下:
#ifndef TestDll_H_
#define TestDll_H_
#ifdef MYLIBDLL
#define MYLIBDLL extern "C" _declspec(dllimport)
#else
#define MYLIBDLL extern "C" _declspec(dllexport)
#endif
MYLIBDLL int Add(int plus1, int plus2);
//You can also write like this:
//extern "C" {
//_declspec(dllexport) int Add(int plus1, int plus2);
//};
#endif
2、新建源文件testdll.cpp
testdll.cpp代码如下:
#include "stdafx.h"
#include "testdll.h"
#include <iostream>
using namespace std;
int Add(int plus1, int plus2)
{
int add_result = plus1 + plus2;
return add_result;
}
3、新建模块定义文件mydll.def
mydll.def代码如下:
LIBRARY "MyDLL"
EXPORTS
Add @1
4、vs2010自动创建dllmain.cpp文件,它定义了DLL 应用程序的入口点。
dllmain.cpp代码如下:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
最后,编译生成MyDLL.dll文件和MyDLL.lib文件。
1>------ 已启动生成: 项目: MyDLL, 配置: Debug Win32 ------
1> dllmain.cpp
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
1>------ 已启动生成: 项目: MyDLL, 配置: Debug Win32 ------
1> stdafx.cpp
1> testdll.cpp
1> MyDLL.cpp
1> 正在生成代码...
1> 正在创建库 D:\Visual C++\工程\Libaray\MyDLL\Debug\MyDLL.lib 和对象 D:\Visual C++\工程\Libaray\MyDLL\Debug
标准C/C++的DLL编写
DLL也就是动态链接库,使用DLL编程的好处大家应当都知道了吧,可是怎么样来作呢,今天我就来说说。
首先,你要确定你要导出那些个函数,然后你就在你要导出的函数名前加上下面一句话:
// 输出函数的前缀
#define DLL_EXPORT extern "C" __declspec( dllexport )
DLL_EXPORT VOID ExportFun()
{
...
}
是不是很简单啊。如果你要导出整个类或者全局变量,你需要这样做:
// 输出类的前缀
#define DLL_CLASS_EXPORT __declspec( dllexport )
// 输出全局变量的前缀
#define DLL_GLOBAL_EXPORT extern __declspec( dllexport )
完成了这些以后,我们就要在主程序中调用这些个函数了,用下面的方法:
HINSTANCE hInst = NULL;
hInst = LoadLibrary("*.dll"); // 你的DLL文件名
if (!hInst)
{
MessageBox(hWnd,"无法加载 *.Dll ","Error",MB_OK);
}
还记得上面我声明的那个ExportFun()函数吗?我不能直接得到那个函数,但是可以把那个函数的地址取出来。其实函数地址使用起来和函数是一样的。只不过,为了使用方便,需要定义一个函数指针的类型。如果要指向上面的那个ExportFun(),则它的函数指针的类型定义如下:
typedef void (CALLBACK* LPEXPORTFUN)(void)
之后需要做的是声明一个指针,然后得到DLL中ExportFun()的地址。GetProcAddress函数的第一个参数是之前得到的DLL的实例句柄,后面一个是DLL中那个函数的函数名。
LPEXPORTFUN pFun = NULL;
LPEXPORTFUN pFun = (LPEXPORTFUN)GetProcAddress(hInst, "ExportFun");
好了,到这里已经就要大功告成了,还差最后一步,调用那个函数:
pFun();
大功告成!!