zoukankan      html  css  js  c++  java
  • 动态链接库

    1. 动态链接库的分类

      Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、 MFC Regular DLL(MFC规则DLL)、 MFC Extension DLL(MFC扩展DLL)。

      (1) 非MFC动态库:不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用。

      (2) MFC规则DLL:包含一个继承自CWinApp的类,但其无消息循环。

      (3) MFC扩展DLL:采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。

    2. DLL中导出函数的声明方式

      一种方式是:在函数声明中加上__declspec(dllexport)
      另外一种方式是:采用模块定义(.def)文件声明,(.def)文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。

      (1) 方式一:在函数声明中加上__declspec(dllexport)

    1 #pragma once
    2 
    3 extern "C" __declspec(dllexport) int add(int x, int y);
    .h
    1 #include "stdafx.h"
    2 #include "dllTest.h"
    3 
    4 int add(int x, int y)
    5 {
    6     return (x + y);
    7 }
    .cpp

       (2) 方式二:采用模块定义(.def)文件声明

        ①首先创建 一个DLL程序(DllTestDef)

        ②在*.cpp中添加需要导出的接口函数

     1 #include "stdafx.h"
     2 
     3 int __stdcall Add(int a, int b)
     4 {
     5     return (a + b);
     6 }
     7 
     8 int _stdcall Sub(int a, int b)
     9 {
    10     return (a - b);
    11 }
    .cpp

         ③然后创建一个.def的文件

    1 LIBRARY dllTest
    2 EXPORTS
    3 Add @ 1
    4 Sub @ 2
    .def

    3. DLL的调用方式

      (1)动态调用:

        "LoadLibrary-GetProcAddress-FreeLibrary"系统API提供的三位一体"DLL加载-DLL函数地址获取-DLL释放"方式,这种调用方式称为DLL的动态调用。

     1 #include "stdafx.h"
     2 #include <iostream>
     3 using namespace std;
     4 #include <windows.h>
     5 
     6 typedef int(*lpAdd)(int, int);
     7 typedef int(*lpSub)(int, int);
     8 
     9 int main()
    10 {
    11     HMODULE hDll = NULL;
    12     lpAdd pAdd = NULL;
    13     lpSub pSub = NULL;
    14 
    15     hDll = LoadLibrary(TEXT("dllTest.dll"));
    16     if (hDll != NULL)
    17     {
    18         pAdd = (lpAdd)GetProcAddress(hDll, "Add");
    19         pSub = (lpSub)GetProcAddress(hDll, "Sub");
    20 
    21         if (pAdd != NULL)
    22         {
    23             cout << "3 + 5 = " << pAdd(3, 5) << endl;
    24         }
    25 
    26         if (pSub != NULL)
    27         {
    28             cout << "2 + 4 = " << pSub(2, 4) << endl;
    29         }
    30     }
    31 
    32     getchar();
    33     return 0;
    34 }
    View Code

       (2)静态调用:

        静态调用,也称为隐式调用,由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(Windows系统负责对DLL调用次数的计数),调用方式简单,能够满足通常的要求。通常采用的调用方式是把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须在源文件中声明一下。

     1 #include "stdafx.h"
     2 #include <iostream>
     3 using namespace std;
     4 
     5 #pragma comment(lib, "..\x64\Release\dllTest.lib")
     6 extern "C" __declspec(dllimport) int add(int x, int y);
     7 
     8 int main()
     9 {
    10     cout << "4 + 7 = " << add(4, 7) << endl;
    11 
    12     getchar();
    13     return 0;
    14 }
    View Code

     4. DllMain函数

        Windows在加载DLL的时候,需要一个入口函数,如同控制台或 DOS 程序需要 main 函数、WIN32程序需要 WinMain 函数一样。

        在前面的例子中,DLL并没有提供 DllMain函数, 应用工程也能成功引用 DLL,这是因为Windows在找不到DllMain的时候, 系统会从其它运行库中引入一个不做任何操作的缺省 DllMain函数版本,并不意味着 DLL可以放弃 DllMain函数。

        根据编写规范, Windows必须查找并执行 DLL里的 DllMain函数作为加载 DLL的依据,它使得 DLL得以保留在内存里。这个函数并不属于导出函数,而是 DLL 的内部函数。这意味着不能直接在应用工程中引用 DllMain函数, DllMain是自动被调用的 。

     1 BOOL APIENTRY DllMain( HMODULE hModule,
     2                        DWORD  ul_reason_for_call,
     3                        LPVOID lpReserved
     4                      )
     5 {
     6     switch (ul_reason_for_call)
     7     {
     8     case DLL_PROCESS_ATTACH:
     9     case DLL_THREAD_ATTACH:
    10     case DLL_THREAD_DETACH:
    11     case DLL_PROCESS_DETACH:
    12         break;
    13     }
    14     return TRUE;
    15 }
    DllMain

      5. 关于调用约定

      C/C++缺省的调用方式是__cdecl方式,Windows API使用__stdcall调用方式,在DLL导出函数中,为了跟Windows API保持一致,建议使用__stdcall调用方式。

            __cdecl方式与__stdcall对函数名最终生成符号的方式不同。若采用C编译方式(在C++中需将函数声明为extern "C")。

      __stdcall调用约定在输出函数名钱加下划线,后面加“@”符号和参数的字节数,形如_functionname@number;

      而__cdecl调用约定仅在输出函数名前加下划线,形如_functionname。

    6. DLL导出变量

         DLL定义的全局变量可以被调用进程访问,DLL也可以访问调用进程的全局数据。

        (1) 在DLL中导出变量有两种方法:

       方法一:用模块定义文件(.def)进行导出声明  

     1 // dllmain.cpp : 定义 DLL 应用程序的入口点。
     2 #include "stdafx.h"
     3 
     4 int dllGlobalVar;
     5 
     6 BOOL APIENTRY DllMain( HMODULE hModule,
     7                        DWORD  ul_reason_for_call,
     8                        LPVOID lpReserved
     9                      )
    10 {
    11     switch (ul_reason_for_call)
    12     {
    13     case DLL_PROCESS_ATTACH:
    14         dllGlobalVar = 123;
    15         break;
    16     case DLL_THREAD_ATTACH:
    17     case DLL_THREAD_DETACH:
    18     case DLL_PROCESS_DETACH:
    19         break;
    20     }
    21     return TRUE;
    22 }
    23 
    24 
    25 
    26 LIBRARY dllExportVariable_def
    27 EXPORTS
    28 dllGlobalVar DATA
    View Code

      特别要注意的是用extern int dllGlobalVar声明所导入的并不是DLL中全局变量本身,而是其地址,应用程序必须通过强制指针转换来使用DLL中的全局变量。这一点,从*(int*)dllGlobalVar可以看出。因此在采用这种方式引用DLL全局变量时,千万不要进行这样的赋值操作:

                    dllGlobalVar = 1;
      其结果是dllGlobalVar指针的内容发生变化,程序中以后再也引用不到DLL中的全局变量了。

      而通过_declspec(dllimport)方式导入的就是DLL中全局变量本身而不再是其地址了,笔者建议在一切可能的情况下都使用这种方式。

      方法二:用__declspec进行导出声明

    1 __declspec(dllexport) extern int dllGlobalVar = 88;
    View Code

       (2) 调用DLL中导出的变量:

      同样,应用程序调用DLL中的变量也有两种方法。 

      第一种是隐式链接: 

     1 #include <iostream>
     2 using namespace std;
     3 
     4 #pragma comment(lib, "..\x64\Debug\dllExportVariable_declspec.lib")
     5 extern _declspec(dllimport) int dllGlobalVar;
     6 
     7 int main()
     8 {
     9     cout << "dllGlobalVar = " << dllGlobalVar << endl;
    10 
    11     dllGlobalVar = 88;
    12     cout << "dllGlobalVar = " << dllGlobalVar << endl;
    13 
    14     getchar();
    15     return 0;
    16 }
    View Code

      第二种是显式链接: 

     1 #include <iostream>
     2 using namespace std;
     3 
     4 #include <windows.h>
     5 
     6 int main()
     7 {
     8     int my_int;
     9     HINSTANCE hInstLibrary = LoadLibrary(TEXT("dllExportVariable_def.dll"));
    10 
    11     if (hInstLibrary != NULL)
    12     {
    13         my_int = *(int*)GetProcAddress(hInstLibrary, "dllGlobalVar");
    14         cout << "my_int = " << my_int << endl;
    15     }
    16     FreeLibrary(hInstLibrary);
    17 
    18     getchar();
    19     return 0;
    20 }
    View Code

      Note:一般不建议从DLL中导出全局变量,对于希望从DLL获取资源以实现资源共享的情景,最好是通过导出一个Get函数获得,这样操作起来更方便而且更安全。

    7. DLL导出类

      一、导出类的简单方式

        这种方式是比较简单的,同时也是不建议采用的不合适方式。 

        只需要在导出类加上__declspec(dllexport),就可以实现导出类。对象空间还是在使用者的模块里,dll只提供类中的函数代码。

        不足的地方是:使用者需要知道整个类的实现,包括基类、类中成员对象,也就是说所有跟导出类相关的东西,使用者都要知道。通过Dependency Walker可以看到,这时候的dll导出的是跟类相关的函数:如构造函数、赋值操作符、析构函数、其它函数,这些都是使用者可能会用到的函数。

        这种导出类的方式,除了导出的东西太多、使用者对类的实现依赖太多之外,还有其它问题:必须保证使用同一种编译器。导出类的本质是导出类里的函数,因为语法上直接导出了类,没有对函数的调用方式、重命名进行设置,导致了产生的dll并不通用。

     

        简单方式导出类的DLL示例:

     1 #pragma once
     2 
     3 //相关的类都必须导出
     4 class _declspec(dllexport) CBase
     5 {
     6 public:
     7     void Test1();
     8 private:
     9     int m_var1;
    10 };
    11 
    12 //相关的类都必须导出
    13 class _declspec(dllexport) CData
    14 {
    15 public:
    16     void Test2();
    17 private:
    18     int m_var2;
    19 };
    20 
    21 //要导出的类
    22 class _declspec(dllexport) CExportClass : public CBase
    23 {
    24 public:
    25     CExportClass(int i = 0);
    26 
    27     void TestFun();
    28     CData GetDataObj() { return m_DataObj; }
    29 
    30 private:
    31     int m_i;
    32     CData m_DataObj;
    33 };
    .h
     1 CExportClass::CExportClass(int i) : m_i(i)
     2 {
     3 }
     4 
     5 void CExportClass::TestFun()
     6 {
     7     cout << "This is TestFun() from CExportClass class!" << endl;
     8 }
     9 
    10 void CBase::Test1()
    11 {
    12     cout << "This is Test1() from CBase class!" << endl;
    13 }
    14 
    15 void CData::Test2()
    16 {
    17     cout << "This is Test2() from CData class!" << endl;
    18 }
    .cpp

       调用示例:

     1 #include "stdafx.h"
     2 #include <Windows.h>
     3 #include "..dllTest_ExportClass(NotRecommend)dllTest_ExportClass(NotRecommend).h"
     4 
     5 #pragma comment(lib, "..\x64\Debug\dllTest_ExportClass(NotRecommend).lib")
     6 
     7 int main()
     8 {
     9     CExportClass obj(55);
    10     obj.Test1();
    11     obj.TestFun();
    12 
    13     CData DataObj = obj.GetDataObj();
    14     DataObj.Test2();
    15 
    16     system("pause");
    17     return 0;
    18 }
    View Code

       二、导出类的较好方式

        这种方式和COM类似,它的结构是这样的:导出类是一个派生类,派生自一个抽象类(都是纯虚函数)。使用者只需知道这个抽象类的结构。

        DLL最少需要提供一个用于获取类对象指针的接口。使用者和DLL提供者共用一个抽象类的头文件。使用者依赖于DLL的东西很少,只需要知道抽象类的接口,以及获取对象指针的导出函数,对象内存空间的申请是在DLL模块中做的,释放也在DLL模块中完成(需要在最后调用释放对象的函数)。

        这种方式比较好,通用,产生的DLL没有特定的环境限制。除了对DLL导出类有好处外,它采用接口和实现分离,也可以使得工程结构更清晰,使用者只需要知道接口,不需要知道实现。

        

        测试示例代码下载地址: https://files.cnblogs.com/files/YQ2014/dllTest_ExportClass%28NotRecommend%29.zip   

      

      参考资料:

      http://www.cnblogs.com/cswuyg/archive/2011/10/06/DLL2.html

      http://www.codeproject.com/KB/cpp/howto_export_cpp_classes.aspx

      

      导出类的DLL要小心DLL Hell问题。

           详细的可以参考:DLL导出类避免地狱问题的完美解决方案

  • 相关阅读:
    [原创]SQL经验
    DotNetBar技巧经验集合
    正则表达式的那些小角落
    [转]验证数字的正则表达式集
    项目受源代码管理。向源代码管理注册此项目时出错。建议不要对此项目进行任何更改
    DateGridView的一些技巧
    个人的CodeSmith和.NetTiers的学习心得及经验总结
    常用代码
    mysql 数据库常用命令
    XP2防火墙拒绝网上邻居访问的解决
  • 原文地址:https://www.cnblogs.com/YQ2014/p/9797205.html
Copyright © 2011-2022 走看看