- 一、文件目录
- 1、main.cpp
- 2、CmakeLists.txt
- 3、base
- 二、构建、编译、运行
- 1、构建
- 2、编译
- 3、运行
- 三、相关说明
- 1、本文主要为了讲清楚c++导出类和如何使用
- 2、虚基类base.h
- 3、派生类addClass
- 4、本文的base.dll,更确切的名字应该是叫做add.dll,
- 5、不需要lib
- 6、using关键字
- 7、reinterpret_cast关键字
- 8、过程
- 9、使用depend查看base.dll
- 原本cmake不打算再写了,但是心里对c++类的导出和使用还有些疑问,所以最近几天又找了些相关资料,写了个demo。
- 严格意义的讲,本文并非讲解cmake相关知识,而算是之前涉及到动态库导出知识【cmake实战六】如何使用编译的库(动态库dll)的续写。
- 动态库dll的导出c++类,分为显示调用,和隐式调用,网上资料隐式调用较多,显示调用的相对来说少些。
- 动态库dll导出c++类的隐式调用,由于网上很多且相对简单,本文就不讲了,感觉都是在编译阶段链接,简单来说就两点,一是在使用的代码中#pragma comment,二是CmakeLists.txt中增加TARGET_LINK_LIBRARIES,通过是lib链接。详情参考【cmake实战七】如何使用编译的库(动态库dll)2——windows系统
- 关于c++类导出动态库的类,懂了本文的显示调用,隐式调用很好理解
- 本文用到的工程和代码下载地址:c++从动态库(dll)导出类
#include
#include
#include "./base/base.h"
using namespace std;
int main()
{
//方式一
// 1、把dll加载到内存中
HMODULE handle = (HMODULE)::LoadLibrary("C:\Users\jx\Desktop\test10\lib\Debug\base.dll");
//定义一个base类型的指针,以下两种方式等级
using fucptr = base* (*)();
// typedef base* (*fucptr)();
// 2、获取函数地址
fucptr addPtr = (fucptr)GetProcAddress(handle, "getadd");
//3、调用
base* ptr = addPtr();
cout << "add(1,2)=" << ptr->add(1, 2)<<endl;
bool ret = FreeLibrary(handle);
//方式二
// 1、把dll加载到内存中
LoadLibrary("C:\Users\jx\Desktop\test10\lib\Debug\base.dll");
// 2、获取函数地址
typedef int(*sumptr)(int, int);
sumptr sum = nullptr;
HMODULE handle_sum = GetModuleHandle("base.dll");
sum = reinterpret_cast<sumptr>(GetProcAddress(handle_sum, "sum"));
// 3、调用
int result = sum(2, 3);
cout<<"sum(2,3)=" << result << endl;
return 0;
}
2、CmakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)
PROJECT(COM)
ADD_EXECUTABLE(sum main.cpp)
ADD_SUBDIRECTORY(base)
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
3、base
- 3.1、base.h
//base.h
#pragma once
#include
#ifndef DLL_EXPORT
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
class API base
{
public:
virtual int add(int a, int b) = 0;
};
// 正常情况,父类和子类应该分不同的文件,本文为了方便,就不分开文件写了。
class addClass :public base
{
public:
virtual int add(int a, int b) override;
};
extern "C" API base * getadd();
extern "C" API int sum(int a, int b);
- 3.2、base.cpp
//base.cpp
#include "base.h"
using namespace std;
int addClass::add(int a,int b)
{
return a+b;
}
base* getadd()
{
base* ptr = new addClass();
return ptr;
}
int sum(int a,int b)
{
base* ptr = new addClass();
return ptr->add(a, b);
}
- 3.3、CmakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)
SET(TARGET "base")
ADD_LIBRARY(base SHARED base.cpp)
SET(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
二、构建、编译、运行
1、构建
- (test10目录下执行)
cmake -B build
- 生成的解决方案
2、编译
cmake --build build
3、运行
.\sum.exe
三、相关说明
1、本文主要为了讲清楚c++导出类和如何使用
- 所以在代码中并没有对指针判空。
- 利用C++面向对象的继承特性与多态特性对类进行导出为动态链接库DLL及显式调用,该种导出方法可以减少在调用时对头文件的依赖,更好地隐藏信息。
- 正常情况,虚基类base和addClass应该分开不同的文件书写。
- 正常情况,虚基类base头文件被不同的dll和sum.exe引进去。只有定义,没有实现。
- C++类的虚函数和派生类,采用接口跟实现分离。
- 抽象类(都是纯虚函数),派生类(被调用者)跟调用者共用一个抽象类的头文件,派生类的dll中实现此抽象类的派生类,
- 只有我们指定路径下的base.dll还在,我们可以把sum.exe拷贝到此电脑的任意文件中运行,显示调用。
- 我们在运行阶段链接:加载到内存中,找到地址。
- 定义基础类型和函数指针别名
typedef 旧的类型名 新的类型名;
typedef unsigned int uint_t;
using 新的类型 = 旧的类型;
using uint_t = int ;
本文中的两种用法,都是定义一个base类型的指针
//定义一个base类型的指针,以下两种方式等级
using fucptr = base* (*)();
typedef base* (*fucptr)();
7、reinterpret_cast关键字
- main.cpp中方式一和方式二的唯一区别,就是方式二中使用了reinterpret_cast
从指针类型到一个足够大的整数类型
从整数类型或者枚举类型到指针类型
从一个指向函数的指针到另一个不同类型的指向函数的指针
从一个指向对象的指针到另一个不同类型的指向对象的指针
从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针
reinterpret_cast < new-type > ( expression )
- 个特定的物理内存地址赋值给一个指针。为此必须使用
reinterpret_cast *** 作符计算地址值。例子如下:
void *p;
// 将地址 0x5800FF 付给指针 p
p = reinterpret_cast< void* > (0x5800FF);
参考:reinterpret_cast与static_cast
cppreference.com——reinterpret_cast conversion
- 我们通过引入了虚基类的头文件,所以在调用的地方,可以使用虚基类的指针。
- 我们在导出的函数中new了一个派生类的实例。
- 我们使用虚基类的指针指向了导出的函数。
- 通过虚基类的指针访问派生类的对象实例。
- 导出了函数getadd和sum两个函数
参考
1、C++类导出及显式调用方法
2、cppreference.com——reinterpret_cast conversion
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)