利用VC调用动态链接库中的函数

[导读]自从微软推出16位的Windows操作系统起,此后每种版本的Windows操作系统都非常依赖于动态链接库(DLL)中的函数和数据,实际上Windows操作系统中几乎所有的内容都由DLL以一种或另外一种形式代表着,例如显示的字体和图标存储在GDI DLL中、显示Windows桌面和处理用户的输入所需要的代码被存储在一个User DLL中、Windows编程所需



  2)用.def文件创建工程MyDll

  为了用.def文件创建DLL,请先删除上个例子创建的工程中的MyDll.h文件,保留MyDll.cpp并在该文件头删除#include MyDll.h语句,同时往该工程中加入一个文本文件,命名为MyDll.def,再在该文件中加入如下代码:

  LIBRARY MyDll
  EXPORTS
  Max
  Min

  其中LIBRARY语句说明该def文件是属于相应DLL的,EXPORTS语句下列出要导出的函数名称。我们可以在.def文件中的导出函数后加@n,如Max@1,Min@2,表示要导出的函数顺序号,在进行显式连时可以用到它。该DLL编译成功后,打开工程中的Debug目录,同样也会看到MyDll.dll和MyDll.lib文件。

DllMain 函数
Windows 在加载 DLL 的时候,需要一个入口函数,就如同控制台或 DOS 程序需要 main 函数、 WIN 32
程序需要 WinMain 函数一样。在前面的例子中, DLL 并没有提供 DllMain 函数,应用工程也能成功引用 DLL ,
这是因为 Windows 在找不到 DllMain 的时候,系统会从其它运行库中引入一个不做任何操作的缺省 DllM ain
函数版本,并不意味着 DLL 可以放弃 DllMain 函数。
根据编写规范, Windows 必须查找并执行 DLL 里的 DllMain 函数作为加载 DLL 的依据,它使得 DLL
得以保留在内存里。这个函数并不属于导出函数,而是 DLL 的内部函数。这意味着不能直接在应用工程中 引
用 DllMain 函数,DllMain 是自动被调用的。


(二)MFC AppWizard[dll]方式生成常规/扩展DLL

  在MFC AppWizard[dll]下生成DLL文件又有三种方式,在创建DLL是,要根据实际情况选择创建DLL的方式。一种是常规DLL静态链接到MFC,另一种是常规DLL动态链接到MFC。两者的区别是:前者使用的是MFC的静态链接库,生成的DLL文件长度大,一般不使用这种方式,后者使用MFC的动态链接库,生成的DLL文件长度小;动态链接到MFC的规则DLL所有输出的函数应该以如下语句开始: 

AFX_MANAGE_STATE(AfxGetStaticModuleState( )) //此语句用来正确地切换MFC模块状态

  最后一种是MFC扩展DLL,这种DLL特点是用来建立MFC的派生类,Dll只被用MFC类库所编写的应用程序所调用。前面我们已经介绍过,Extension DLLs 和Regular DLLs不一样,它没有一个从CWinApp继承而来的类的对象,编译器默认了一个DLL入口函数DLLMain()作为对DLL的初始化,你可以在此函数中实现初始化,代码如下:

BOOL WINAPI APIENTRY DLLMain(HINSTANCE hinstDll,DWORD reason ,LPVOID flmpload)
{
 switch(reason)
 {
  ……………//初始化代码;
 }
 return true;
}

  参数hinstDll存放DLL的句柄,参数reason指明调用函数的原因,lpReserved是一个被系统所保留的参数。对于隐式链接是一个非零值,对于显式链接值是零。

  在MFC下建立DLL文件,会自动生成def文件框架,其它与建立传统的Non-MFC DLL没有什么区别,只要在相应的头文件写入关键字_declspec(dllexport)函数类型和函数名等,或在生成的def文件中EXPORTS下输入函数名就可以了。需要注意的是在向其它开发人员分发MFC扩展DLL 时,不要忘记提供描述DLL中类的头文件以及相应的.LIB文件和DLL本身,此后开发人员就能充分利用你开发的扩展DLL了。 

  3、动态链接库DLL的链接

  应用程序使用DLL可以采用两种方式:一种是隐式链接,另一种是显式链接。在使用DLL之前首先要知道DLL中函数的结构信息。Visual C++6.0在VC\bin目录下提供了一个名为Dumpbin.exe的小程序,用它可以查看DLL文件中的函数结构。另外,Windows系统将遵循下面的搜索顺序来定位DLL: 1.包含EXE文件的目录,2.进程的当前工作目录, 3.Windows系统目录, 4.Windows目录,5.列在Path环境变量中的一系列目录。

  (一)隐式链接

  隐式链接就是在程序开始执行时就将DLL文件加载到应用程序当中。实现隐式链接很容易,只要将导入函数关键字_declspec(dllimport)函数名等写到应用程序相应的头文件中就可以了。下面的例子通过隐式链接调用MyDll.dll库中的Min函数。首先生成一个项目为TestDll,在DllTest.h、DllTest.cpp文件中分别输入如下代码:

//Dlltest.h
#pragma comment(lib,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b);
extern "C"_declspec(dllimport) int Min(int a,int b);
//TestDll.cpp
#include<stdio.h>
#include"Dlltest.h"
void main()
{
 int a;
 a=min(8,10)
 printf("比较的结果为%d\n",a);
}

  在创建DllTest.exe文件之前,要先将MyDll.dll和MyDll.lib拷贝到当前工程所在的目录下面,也可以拷贝到windows的System目录下。如果DLL使用的是def文件,要删除TestDll.h文件中关键字extern "C"。TestDll.h文件中的关键字Progam commit是要Visual C+的编译器在link时,链接到MyDll.lib文件,当然,开发人员也可以不使用#pragma comment(lib,"MyDll.lib")语句,而直接在工程的Setting->Link页的Object/Moduls栏填入MyDll.lib既可。

  (二)显式链接

  显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适。不过实现显式链接要麻烦一些。在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态链接库调进来,动态链接库的文件名即是上述两个函数的参数,此后再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同在应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态链接库。下面是通过显式链接调用DLL中的Max函数的例子。

#include <studio.h>
#include<widows.h>
void main(void)
{
 typedef int(*pMax)(int a,int b);
 typedef int(*pMin)(int a,int b);
 HINSTANCE hDLL;
 PMax Max
 HDLL=LoadLibrary("MyDll.dll");//加载动态链接库MyDll.dll文件;
 Max=(pMax)GetProcAddress(hDLL,"Max");
 A=Max(5,8);
 Printf("比较的结果为%d\n",a);
 FreeLibrary(hDLL);//卸载MyDll.dll文件;
}

  在上例中使用类型定义关键字typedef,定义指向和DLL中相同的函数原型指针,然后通过LoadLibray()将DLL加载到当前的应用程序中并返回当前DLL文件的句柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针,函数调用完毕后,使用FreeLibrary()卸载DLL文件。在编译程序之前,首先要将DLL文件拷贝到工程所在的目录或Windows系统目录下。

  使用显式链接应用程序编译时不需要使用相应的Lib文件。另外,使用GetProcAddress()函数时,可以利用MAKEINTRESOURCE()函数直接使用DLL中函数出现的顺序号,如将GetProcAddress(hDLL,"Min")改为GetProcAddress(hDLL, MAKEINTRESOURCE(2))(函数Min()在DLL中的顺序号是2),这样调用DLL中的函数速度很快,但是要记住函数的使用序号,否则会发生错误。


另一种详细解释如下:

前几天有个朋友问道这个问题,结果因为以前从没搞过这个,对vs2005也不熟悉,竟花了2个小时才搞定, 。

特地拿来与大家分享,希望能给像我这样的菜鸟们一些帮助,O(∩_∩)O

【第一步】创建自己的dll

1.打开vs2005,选择菜单【File-New-Project】,在弹出对话框中选择[Visual C++]下的[Win32]-[Win32 Console Application],输入工程名后确认。

2.在弹出的对话框中选择[next],在Application Settiongs中选择Application type为Dll,Additional options选择Empty project,然后点Finish。

这时就创建了一个空的可以生成dll文件的工程。

3.在工程中添加一个头文件(这里为dll_test.h),在头文件中写入如下内容:

 1 #ifndef _DLL_TUTORIAL_H
 2 #define _DLL-TUTORIAL_H
 3 
 4 #include<iostream>
 5 
 6 #if defined DLL_EXPORT
 7   #define DECLDIR _declspec(dllexport)
 8 #else
 9   #define DECLDIR _declspec(dllimport)
10 #endif
11 
12 extern "C"
13 {
14   DECLDIRint Add(int a, int b);
15   DECLDIR void Function(void);
16 }
17
18 #endif

 

这里要说明的是:

在VC中有两个方法来导出dll中定义的函数:

  (1) 使用__declspec,这是一个Microsoft定义的关键字。

  (2) 创建一个模板定义文件(Module-Definition File,即.DEF)。

  第一种方法稍稍比第二种方法简单,在这里我们使用的是第一种方法。

   __declspec(dllexport)函数的作用是导出函数符号到在你的Dll中的一个存储类里去。

当下面一行被定义时我定义DECLDIR宏来运行这个函数。

    #defineDLL_EXPORT

在此情况下你将导出函数Add(int a,int b)和Function().

4.创建一个源文件(名字为dll_test.cpp),内容如下:

 

 1 #include <iostream>
 2 #define DLL_EXPORT
 3 #include "dll_test.h"
 4 
 5 extern "C"
 6 {
 7         // 定义了(DLL中的)所有函数
 8     DECLDIR int Add( int a, int b )
 9     {
10         return( a + b );
11     }
12     
13     DECLDIR void Function( void )
14     {
15         std::cout << "DLL Called!" << std::endl;
16     }
17 }
18 

 

【第二步】使用创建好的DLL

现在已经创建了DLL,那么如何在一个应用程序中使用它呢?

当DLL被生成后,它创建了一个.dll文件和一个.lib,这两个都是使用dll时需要用到的。

在具体介绍之前先看一下dll的链接方式。

(1)隐式连接

这里有两个方法来载入一个DLL,一个方法是只链接到.lib文件,并将.dll文件放到要使用这个DLL的项目路径中。

因此,创建一个新的空的Win32控制台项目并添加一个源文件。将我们创建好的DLL放入与新项目相同的目录下。同时我们还必须链接到dll_test.lib文件。

可以在项目属性中设置,也可以在源程序中用下面的语句来链接:

#pragma comment(lib, "dll_test.lib")

最后,我们还要在新的win32控制台项目中包含前面的dll_test.h头文件。可以把这个头文件放到新建win32控制台项目的目录中然后在程序中加入语句:

#include "dll_test.h"

新项目代码如下:

#include<iostream>

#include "DLLTutorial.h"

int main()

{

  Function();

  std::cout<<Add(32, 56)<< endl;

  return 0;

}

(2)显示链接

稍微复杂一点的加载DLL的方法需要用到函数指针和一些Windows函数。但是,通过这种载入DLL的方法,不需要DLL的.lib文件或头文件,而只需要DLL即可。

下面列出一些代码:

/****************************************************************/

#include <iostream>

#include <windows.h>

typedef int (*AddFunc)(int,int);

typedef void (*FunctionFunc)();

int main()

{

AddFunc _AddFunc;

   FunctionFunc_FunctionFunc;

   HINSTANCEhInstLibrary = LoadLibrary("DLL_Tutorial.dll");

   if (hInstLibrary== NULL)

{

FreeLibrary(hInstLibrary);

}

   _AddFunc =(AddFunc)GetProcAddress(hInstLibrary, "Add");

   _FunctionFunc =(FunctionFunc)GetProcAddress(hInstLibrary, "Function");

   if ((_AddFunc ==NULL) || (_FunctionFunc == NULL))

{

FreeLibrary(hInstLibrary);

}

   std::cout<< _AddFunc(23, 43) << std::endl;

   _FunctionFunc();

   std::cin.get();

  FreeLibrary(hInstLibrary);

   return(1);

}

/*******************************************************************/

首先可以看到,这里包括进了windows.h头文件,同时去掉了对dll_test.h头文件的包含。原因很简单:因为windows.h包含了一些Windows函数,

它也包含了一些将会用到的Windows特定变量。可以去掉DLL的头文件,因为当使用这个方法载入DLL时并不需要其头文件。

下面你会看到:以下面形式的一小块古灵精怪的代码:

    typedef int(*AddFunc)(int,int);

typedef void (*FunctionFunc)();

    这是函数指针。因为这是一个关于DLL的自学指南,深入探究函数指针超出了本指南的范围;因此,现在我们只把它们当作DLL包含的函数的别名。

    我喜欢在尾部用“Func”命名之。(int,int)部分是这个函数的参数部分,比如,Add函数要获得两个整数;因此,你需要它们

(译注:指(int,int)部分)作为函数指针的参数。Function函数没有参数,因此你让它为空。main()部分中的前面两行是声明函数指针以使得你可

以认为它们等同于DLL内部的函数。我只是喜欢预先定义它们。

      一个HINSTANCE是一个Windows数据类型:是一个实例的句柄;在此情况下,这个实例将是这个DLL。你可以通过使用函数LoadLibrary()获得DLL的

实例,它获得一个名称作为参数。

     在调用LoadLibrary函数后,你必需查看一下函数返回是否成功。你可以通过检查HINSTANCE是否等于NULL(在Windows.h中定义为0或Windows.h包

含的一个头文件)来查看其是否成功。如果其等于NULL,该句柄将是无效的,并且你必需释放这个库。换句话说,你必需释放DLL获得的内存。

      如果函数返回成功,你的HINSTANCE就包含了指向DLL的句柄。一旦你获得了指向DLL的句柄,你现在可以从DLL中重新获得函数。

     为了这样作,你必须使用函数GetProcAddress(),它将DLL的句柄(你可以使用HINSTANCE)和函数的名称作为参数。你可以让函数指针获得由

GetProcAddress()返回的值,同时你必需将GetProcAddress()转换为那个函数定义的函数指针。举个例子,对于Add()函数,你必需将GetProcAddress()

转换为AddFunc;这就是它知道参数及返回值的原因。现在,最好先确定函数指针是否等于NULL以及它们拥有DLL的函数。

     这只是一个简单的if语句;如果其中一个等于NULL,你必需如前所述释放库。一旦函数指针拥有DLL的函数,你现在就可以使用它们了,但是这里有一个

需要注意的地方:你不能使用函数的实际名称;你必需使用函数指针来调用它们。在那以后,所有你需要做的是释放库如此而已。

     现在你知道了DLL的一些基本知识。你知道如何创建它们,你也知道如何用两种不同的方法链接它们。这里仍然有更多的东西需要我们学习,但我把它们留给你们自己探索了和更棒的作者来写了。

 






你可能感兴趣的:(利用VC调用动态链接库中的函数)