【cmake实战六】如何使用编译的库(动态库dll)——windows系统
一、文件目录
1、main.cpp
#include<iostream>
#include<windows.h>
using namespace std;
void loadDll(string dllname, HMODULE& handle)
{
//handle = (handle_t)::LoadLibrary(dllname.c_str());
handle = (HMODULE)::LoadLibrary(dllname.c_str());
}
bool freeDll(HMODULE& handle)
{
bool re = FreeLibrary(handle);
return re;
}
int main()
{
cout<<"hello world"<<endl;
//handle_t handle = nullptr;
HMODULE handle = nullptr;
loadDll("C:\\Users\\jx\\Desktop\\test06\\lib\\Debug\\haha.dll", handle);
if (nullptr == handle)
{
cout << "fail to load dll" << endl;
}
cout << "sucess to load dll" << endl;
typedef void (*print)();
print hahafunc=(print)GetProcAddress(handle, "haha");
hahafunc();
bool re = freeDll(handle);
if (re)
{
cout << "success to unload dll" << endl;
}
return 0;
}
2、CmakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)
PROJECT(NEWHELLO)
ADD_EXECUTABLE(hello main.cpp)
ADD_SUBDIRECTORY(haha)
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
SET(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
3、haha
- haha.h
#ifndef HAHA_H
#define HAHA_H
#include<iostream>
extern "C" __declspec(dllexport) void haha();
#endif
- haha.cpp
#include "haha.h"
using namespace std;
void haha()
{
cout<<"haha"<<endl;
}
- CmakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)
SET(TARGET "haha")
#ADD_LIBRARY(haha STATIC haha.cpp)
ADD_LIBRARY(haha SHARED haha.cpp)
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
SET(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
二、构建、编译、运行
1、构建(新建build目录,在build目录下执行)
cmake ..
2、编译
devenv.com NEWHELLO.sln /Build "Debug|x64"
3、运行
.\hello.exe
三、知识讲解
1、LoadLibrary
- 进程调用 LoadLibrary 以显式链接到 DLL。 如果函数执行成功,它会将指定的 DLL 映射到调用进程的地址空间中并返回该 DLL 的句柄。 此句柄可以与其他函数(如 GetProcAddress 和 FreeLibrary)一起在显式链接中使用。
- https://docs.microsoft.com/zh-cn/cpp/build/loadlibrary-and-afxloadlibrary?view=msvc-170
- LoadLibrary微软提供,linux下不可用,同linux下的dlopen
2、FreeLibrary
- 当不再需要 DLL 模块时,显式链接到 DLL 的进程会调用 FreeLibrary 函数。 此函数使模块的引用计数递减。 而且,如果引用计数为零,则从进程的地址空间取消映射。
- FreeLibrary微软提供,linux下不可用,同linux下的dlclose
参考- https://docs.microsoft.com/zh-cn/cpp/build/freelibrary-and-afxfreelibrary?view=msvc-170
3、GetProcAddress
- 显式链接到 DLL 的进程会调用 GetProcAddress,以获取 DLL 中导出函数的地址。 可使用返回的函数指针调用 DLL 函数。 GetProcAddress 采用 DLL 模块句柄(由 LoadLibrary返回)作为参数,并采用要调用的函数的名称或函数的导出序号。
- 微软提供,linux下无法使用,同linux下的dlsym
参考:https://docs.microsoft.com/zh-cn/cpp/build/getprocaddress?view=msvc-170
https://www.cnblogs.com/avexer/p/3258291.html
4、__declspec
- dllexportdllimport存储类属性是特定于 C 和 C++ 语言的扩展。 可以使用它们从 DLL 中导出或向其中导入函数、数据和对象。
- 微软提供的,linux下无法使用,同linux下的__attribute__ ((visibility(“default”)))
参考:https://docs.microsoft.com/zh-cn/cpp/cpp/dllexport-dllimport?view=msvc-170
https://blog.csdn.net/yu704645129/article/details/53171315
5、extern “C”
- extern"C"
extern 表示这是个全局函数,可以供各个其他的函数调用; “C” 表示编译时按照 C编译器的方式进行编译,而不是C++。
-The extern keyword in C and C++ extends the visibility of variables and functions across multiple source files.
四、问题
1、去掉extern “C" 会发生什么???
- GetProcAddress(handle, “haha”);会失败
2、是否有办法去掉extern “C"
- 结论,如果导出的c++的接口,不可以
- 结论,或者,只需要把haha更改为c语言就可以了,如下
- haha.h
#ifndef HAHA_H
#define HAHA_H
#include<stdio.h>
//extern "C" __declspec(dllexport) void haha();
__declspec(dllexport) void haha();
#endif
- haha.c
#include "haha.h"
void haha()
{
printf("haha\n");
}
- CmakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)
SET(TARGET "haha")
#ADD_LIBRARY(haha STATIC haha.cpp)
ADD_LIBRARY(haha SHARED haha.c)
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
SET(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
3、为啥c++就需要extern “C”,在
C++的编译方式考虑了函数重载,所以对函数名进行了新的修饰,产生了所谓的破坏性命名。
- 这样实际上,我们在exe中调用的函数名字就是已经被修饰过的,所以我们直接按照原来的函数名自然就找不到了!为什么GetProcAddress返回值总为0?[解决方案]
- c++代码,有extern “C”,使用depend查看haha.dll,函数名字为haha
- c++代码,无extern “C”,使用depend查看haha.dll,函数名字为?haha@@YAXXZ
4、参考问题3的答案,问题2的另外一种方法
把
print hahafunc=(print)GetProcAddress(handle, "haha");
更改为
print hahafunc=(print)GetProcAddress(handle, "?haha@@YAXXZ");