在上一篇文章(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的示例代码待上传