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的示例代码待上传

posted @ 2021-08-14 22:47  zhaotianff  阅读(776)  评论(0编辑  收藏  举报