C++动态链接库的制作

输入函式__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" __declspecdllexport )

    DLL_EXPORT VOID  ExportFun()
    {
        ...
    }

  是不是很简单啊。如果你要导出整个类或者全局变量,你需要这样做:

// 输出类的前缀
#define  DLL_CLASS_EXPORT   __declspecdllexport )

// 输出全局变量的前缀
#define  DLL_GLOBAL_EXPORT   extern __declspecdllexport )

 完成了这些以后,我们就要在主程序中调用这些个函数了,用下面的方法:

    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();

大功告成!!


猜你喜欢

转载自blog.csdn.net/u011555996/article/details/80293812