• Windows中的库编程(二、导出变量、类及DllMain函数的介绍)


    在上一篇文章(https://www.cnblogs.com/zhaotianff/p/10797057.html)中介绍了如何对全局函数进行导出,

    这篇文章再补充一下如何导出变量、类及DllMain函数的介绍

    导出变量

    说明:

    1、这里的变量指的是全局变量或类静态变量

    2、当导出一个变量或类时,载入此dll的程序将会获得单独的拷贝,因此各程序之间不会影响。

    导出全局变量

    使用模块定义文件导出(.def)

    1.创建一个C++动态链接库工程

    2.添加lib_demo.cpp,添加两个变量

    1 int global_variable = 100;
    2 int global_variable_2 = 102;

    3.添加一个模块定义文件

    输入

    1 LIBRARY
    2 EXPORTS
    3 global_variable CONSTANT
    4 global_variable_2 CONSTANT

    说明:

    使用CONSTANT会提示关键字已过时,可参阅

    https://docs.microsoft.com/en-us/cpp/build/importing-using-def-files?view=msvc-160

    4.编译工程,生成lib_demo.dll和lib_demo.lib

    5.创建一个控制台应用程序lib_demo_def_test,输入以下代码

     1 #include <iostream>
     2 
     3 extern int global_variable;  //因为没有用__declspec(dllimport),所以认为global_variable为指针
     4 extern int __declspec(dllimport) global_variable_2;
     5 
     6 int main()
     7 {
     8     //输出导出变量
     9     std::cout << *(int*)global_variable << std::endl;
    10 
    11     //赋值操作
    12     *(int*)global_variable = 88;
    13 
    14     //输出赋值后的值 
    15     std::cout << *(int*)global_variable << std::endl;
    16 
    17 
    18     //输出导出变量
    19     std::cout << global_variable_2 << std::endl;
    20 
    21     //赋值操作
    22     global_variable_2 = 77;
    23 
    24     //输出赋值后的值 
    25     std::cout << global_variable_2 << std::endl;
    26 }

    使用__declspec(dllexport)方式导出

    1.创建一个C++动态链接库工程

    2.添加lib_demo.h,使用__declspec(dllexport)声明要导出的全局变量

    1 #ifdef indll
    2 #define export_api __declspec(dllexport)
    3 #else
    4 #define export_api __declspec(dllimport)
    5 #endif
    6 
    7 export_api extern int global_variable;
    8 export_api extern int global_variable_2;

    3.添加lib_demo.cpp,定义两个全局变量

    1 #ifndef indll
    2 #define indll
    3 #endif // !indll
    4 
    5 #include"lib_demo.h"
    6 
    7 //定义两个全局变量
    8 int global_variable = 100;
    9 int global_variable_2 = 102;

    说明:

    • 在dll工程中,定义indll,这样export_api就是__declspec(dllexport),而在调用工程中,没有定义indll,export_api就是__declspec(dllimport)
    • lib_demo.h只对要导出的变量进行声明,所以要用extern。lib_demo.cpp中对变量进行定义。
    • 需要先定义indll,再包含头文件

    4.编译工程,生成.dll和.lib文件

    5.创建一个控制台应用程序lib_demo_dllexport_test,输入以下代码

     1 #include <iostream>
     2 #include "../lib_demo_dllexport/lib_demo.h"
     3 
     4 #pragma comment(lib,"..\Debug\lib_demo_dllexport.lib")
     5 
     6 int main()
     7 {
     8     std::cout << global_variable << std::endl;
     9     std::cout << global_variable_2 << std::endl;
    10 
    11     global_variable = 88;
    12     global_variable_2 = 99;
    13 
    14     std::cout << global_variable << std::endl;
    15     std::cout << global_variable_2 << std::endl;
    16 }

    导出类静态变量(__declspec(dllexport)方式)

    1.创建一个C++动态链接库工程

    2.添加lib_demo.h,创建一个CLibTest类

     1 #define export_api __declspec(dllexport)
     2 #else
     3 #define export_api __declspec(dllimport)
     4 #endif
     5 
     6 class CLibTest
     7 {
     8 public:
     9     CLibTest();
    10     ~CLibTest();
    11 
    12 public:
    13     static double ID;   //定义一个类静态变量
    14 };

    3.添加lib_demo.cpp,实现CLibTest类

     1 #define indll
     2 
     3 #include "lib_demo.h"
     4 
     5 CLibTest::CLibTest()
     6 {
     7 
     8 }
     9 
    10 CLibTest::~CLibTest()
    11 {
    12 
    13 }
    14 
    15 double CLibTest::ID = 520.1314;

    4.编译工程,生成.dll和.lib文件

    5.创建一个控制台应用程序lib_demo_export_class_static_test,输入

     1 #include <iostream>
     2 #include<stdlib.h>
     3 #include "../lib_demo_export_class_static/lib_demo.h"
     4 
     5 #pragma comment(lib,"../Debug/lib_demo_export_class_static.lib")
     6 
     7 int main()
     8 {
     9     std::cout.precision(7);
    10     std::cout << CLibTest::ID << std::endl;
    11 }

    导出类

    使用模块定义文件方式导出(.def文件)

    1.创建一个C++动态链接库工程

    2.添加lib_demo.h,声明类CLibTest

     1 #pragma once
     2 
     3 class CLibTest
     4 {
     5 public:
     6     CLibTest();
     7     ~CLibTest();
     8 
     9 public:
    10     int TestMethod1();
    11     double TestMethod2();
    12 };

    3.添加lib_demo.cpp,实现CLibTest

     1 #include"lib_demo.h"
     2 
     3 CLibTest::CLibTest()
     4 {
     5 
     6 }
     7 
     8 CLibTest::~CLibTest()
     9 {
    10 
    11 }
    12 
    13 int CLibTest::TestMethod1()
    14 {
    15     return 520;
    16 }
    17 
    18 double CLibTest::TestMethod2()
    19 {
    20     return 0.1314;
    21 }

    4.打开项目属性页,找到【配置属性】-》【链接器】-》【调试】->【生成映射文件】,设置为【是(/MAP)】

    5.用记事本打开生成路径下的xxx.map文件,我这里的工程名为lib_demo_class_def,所以打开的是lib_demo_class_def.map文件,

    搜索 【lib_demo.obj】。即声明类的头文件名.obj,我这里是lib_demo.h,所以是lib_demo.obj

    6.创建一个模块定义文件,输入

    1 LIBRARY
    2 EXPORTS
    3 ??0CLibTest@@QAE@XZ  ;构造函数
    4 ??1CLibTest@@QAE@XZ  ;析构函数
    5 ?TestMethod1@CLibTest@@QAEHXZ  ;TestMethod1
    6 ?TestMethod2@CLibTest@@QAENXZ  ;TestMethod2

    EXPORTS后面的部分就是上面在.map文件中搜索到的内容

    7.创建一个调用工程lib_demo_class_def_test,输入

     1 #include <iostream>
     2 #include"../lib_demo_class_def/lib_demo.h"
     3 
     4 #pragma comment(lib,"../Debug/lib_demo_class_def.lib")
     5 
     6 int main()
     7 {
     8     CLibTest test;
     9     auto test_result = test.TestMethod1();
    10     auto test_restlt_2 = test.TestMethod2();
    11 
    12     std::cout << test_result << std::endl;
    13     std::cout << test_restlt_2 << std::endl;
    14 }

    使用__declspec(dllexport)方式导出

    1.创建一个C++动态链接库工程

    2.创建lib_demo.h,声明CLibTest类

     1 #pragma once
     2 
     3 class __declspec(dllexport) CLibTest
     4 {
     5 public:
     6     CLibTest();
     7     ~CLibTest();
     8 
     9 public:
    10     int TestMethod1();
    11     double TestMethod2();
    12 };

    3.创建lib_demo.cpp,实现CLibTest

     1 #include"lib_demo.h"
     2 
     3 CLibTest::CLibTest() {}
     4 
     5 CLibTest::~CLibTest() {}
     6 
     7 int CLibTest::TestMethod1()
     8 {
     9     return 520;
    10 }
    11 
    12 double CLibTest::TestMethod2()
    13 {
    14     return 0.1314;
    15 }

    4.创建一个调用工程lib_demo_class_dllexport_test,输入

     1 #include <iostream>
     2 #include"../lib_demo_class_dllexport/lib_demo.h"
     3 
     4 #pragma comment(lib,"../Debug/lib_demo_class_dllexport.lib")
     5 
     6 int main()
     7 {
     8     CLibTest test;
     9     auto result = test.TestMethod1();
    10     auto result_2 = test.TestMethod2();
    11 
    12     std::cout << result << std::endl;
    13     std::cout << result_2 << std::endl;
    14 }

    示例代码

    DllMain函数

    DllMain就是dll的入口函数,就像控制台应用程序 的入口函数是main,Win32桌面应用程序的入口函数是WinMain一样

    在创建C++动态链接库工程时,Visual Studio会自动创建DllMain.cpp,如下

     1 // dllmain.cpp : 定义 DLL 应用程序的入口点。
     2 #include "framework.h"
     3 
     4 BOOL APIENTRY DllMain( HMODULE hModule,
     5                        DWORD  ul_reason_for_call,
     6                        LPVOID lpReserved
     7                      )
     8 {
     9     switch (ul_reason_for_call)
    10     {
    11     case DLL_PROCESS_ATTACH:
    12     case DLL_THREAD_ATTACH:
    13     case DLL_THREAD_DETACH:
    14     case DLL_PROCESS_DETACH:
    15         break;
    16     }
    17     return TRUE;
    18 }

    这个DllMain函数不是必须的,删除DllMain函数,动态链接库也可以正常编译通过。如果动态链接库中有DllMain函数,则隐式链接时就会调用这个函数,显式链接时,调用LoadLibrary和FreeLibrary函数时会调用DllMain函数

    DllMain参数

    HMODULE hModule:该DLL实例的句柄,也就是该DLL映射到进程地址空间后,在该进程地址空间中的位置

    LPVOID lpReserved保留值

    DWORD ul_reason_for_call:调用DllMain函数的原因。有四种值,如下:

    DLL_PROCESS_ATTACH

    当一个DLL文件(通过隐式链接或显式链接的LoadLibrary)被映射到进程的地址空间时,系统调用该DLL的DllMain函数,并把DLL_PROCESS_ATTACH传递给参数ul_reason_for_call。

    这种调用只会发生在第一次映射 时,如果同一个进程再次LoadLibrary已经映射进来的DLL,操作系统只会增加DLL的使用次数,不会再用DLL_PROCESS_ATTACH调用DllMain函数。

    不同进程加载同一个DLL时,每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DLL的DllMain函数。

    DLL_PROCESS_DETACH

    当系统将一个DLL从地址空间中撤销映射时,则会向DllMain传入DLL_PROCESS_DETACH,可以在此处做一些清理工作。

    当使用FreeLibrary时,若该进程的线程的使用计数为0时,操作系统才会使用DLL_PROCESS_DETACH来调用DllMain,如果计数大于0,则只减少该DLL的计数。

    注意:

    1、如果传入DLL_PROCESS_ATTACH调用DllMain函数时,返回的是FALSE,说明DLL没有初始化成功。但仍然会使用DLL_PROCESS_DETACH调用DLL的DllMain函数。

    2、除了FreeLibrary可以解除 DLL的映射之外,当进程结束时,DLL映射也会被解除。使用TerminateProcess函数结束进程的方式除外。(即使用TerminateProcess结束进程不会使用DLL_PROCESS_DETACH调用DllMain函数)

     


    DLL_THREAD_ATTACH

    当进程创建一个线程,则系统会检查当前已映射到该进程空间中的所有DLL映像,并用DLL_THREAD_ATTACH来调用每个DLL的DllMain函数。

    只有当所有DLL都完成了DLL_THREAD_ATTACH的处理后,新线程才会执行它的线程函数。

    比如:

    已经加载 了DLL的进程中有创建线程的代码

    1 CreateThread(NULL,0,ThreadProc,0,0,NULL);
    2 
    3 DWORD WINAPI ThreadProc(LPVOID lpParam)
    4 {
    5     return 0;
    6 }

    当线程创建时,会使用DLL_THREAD_ATTACH参数执行DllMain函数,然后再执行线程函数ThreadProc

    注意:

    主线程不可能用DLL_THREAD_ATTACH来调用DllMain函数,因为主线程必然是在进程初始化的时候,用DLL_PROCESS_ATTACH参数调用DllMain的


    DLL_THREAD_DETACH

    当线程函数执行结束的时候,会用DLL_THREAD_DETACH来调用当前进程地址空间中的所有DLL镜像的DllMain函数。当每个DllMain都处理完成后,系统才会真正地结束线程

    注意:

    如果线程在DLL被卸载前(调用FreeLibrary)结束,则DLL_THREAD_DETACH会被调用,如果线程在DLL被卸载之后结束,则DLL_THREAD_DETACH不会被调用。

    如果要在case  DLL_THREAD_DETACH中释放内存,一定要注意DLL_THREAD_DETACH有没有被执行到,否则会造成内存泄露。

    DllMain的示例代码待上传

    life runs on code

    作者: zhaotianff

    转载请注明出处

  • 相关阅读:
    设计模式之里氏替换原则
    设计模式之依赖倒转原则
    设计模式之接口分离原则
    spring 集成 kafka producer(KafkaTemplate)
    jmeter 分布式
    ant+Jenkins+jmeter
    pycharm+git+github项目上传
    Python_pip下载不下来源解决方案
    linux_python3环境搭建
    Jenkins+Git+Github+Python自动化化接口项目例子
  • 原文地址:https://www.cnblogs.com/zhaotianff/p/15141399.html
Copyright © 2020-2023  润新知