• 【cmake实战十】c++从动态库(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)导出类

    一、文件目录

    在这里插入图片描述

    1、main.cpp

    #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;
    
    
       //方式三,sub在头文件中既有定义又有实现,所以可以直接调用。但是,sum不可以,因为sum在头文件中只有声明,没有实现。
    	int c = sub(3, 1);
    	cout <<"sub(3,1)=" << c << endl;
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    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")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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);
    
    API int sub(int a, int b)
    {
    	return a - b;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二、构建、编译、运行

    1、构建

    • (test10目录下执行)
    cmake -B build
    
    • 1
    • 生成的解决方案

    在这里插入图片描述

    2、编译

    cmake --build build
    
    • 1

    3、运行

    .\sum.exe
    
    • 1

    在这里插入图片描述

    三、相关说明

    1、本文主要为了讲清楚c++导出类和如何使用

    • 所以在代码中并没有对指针判空。

    2、虚基类base.h

    • 利用C++面向对象的继承特性与多态特性对类进行导出为动态链接库DLL及显式调用,该种导出方法可以减少在调用时对头文件的依赖,更好地隐藏信息。
    • 正常情况,虚基类base和addClass应该分开不同的文件书写。
    • 正常情况,虚基类base头文件被不同的dll和sum.exe引进去。只有定义,没有实现。

    3、派生类addClass

    • C++类的虚函数和派生类,采用接口跟实现分离。
    • 抽象类(都是纯虚函数),派生类(被调用者)跟调用者共用一个抽象类的头文件,派生类的dll中实现此抽象类的派生类,

    4、本文的base.dll,更确切的名字应该是叫做add.dll,

    5、不需要lib

    • 只有我们指定路径下的base.dll还在,我们可以把sum.exe拷贝到此电脑的任意文件中运行,显示调用。
    • 我们在运行阶段链接:加载到内存中,找到地址。

    6、using关键字

    • 定义基础类型和函数指针别名
    typedef 旧的类型名 新的类型名;
    typedef unsigned int  uint_t;
    
    using 新的类型 = 旧的类型;
    using uint_t = int ;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    本文中的两种用法,都是定义一个base类型的指针

    //定义一个base类型的指针,以下两种方式等级
    	using fucptr = base* (*)();
    	typedef base* (*fucptr)();
    
    • 1
    • 2
    • 3

    7、reinterpret_cast关键字

    • main.cpp中方式一和方式二的唯一区别,就是方式二中使用了reinterpret_cast

    从指针类型到一个足够大的整数类型
    从整数类型或者枚举类型到指针类型
    从一个指向函数的指针到另一个不同类型的指向函数的指针
    从一个指向对象的指针到另一个不同类型的指向对象的指针
    从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
    从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

    reinterpret_cast < new-type > ( expression )	
    
    • 1
    • 个特定的物理内存地址赋值给一个指针。为此必须使用
      reinterpret_cast操作符计算地址值。例子如下:
    void *p;
    // 将地址 0x5800FF 付给指针 p
    p = reinterpret_cast< void* > (0x5800FF);
    
    • 1
    • 2
    • 3

    参考:reinterpret_cast与static_cast
    cppreference.com——reinterpret_cast conversion

    8、过程

    • 我们通过引入了虚基类的头文件,所以在调用的地方,可以使用虚基类的指针。
    • 我们在导出的函数中new了一个派生类的实例。
    • 我们使用虚基类的指针指向了导出的函数。
    • 通过虚基类的指针访问派生类的对象实例。

    9、base.h中的sub可以直接被main.cpp调用?

    • sub在头文件中既有定义又有实现,所以可以直接调用。但是,sum不可以,因为sum在头文件中只有声明,没有实现。

    10、使用depend查看base.dll

    • 导出了函数getadd和sum两个函数
    • sub的名字并非我们定义的,有文章说是由于c++的重载时换了名字,未经证实
      在这里插入图片描述

    参考
    1、C++类导出及显式调用方法

    2、cppreference.com——reinterpret_cast conversion

  • 相关阅读:
    Docker如何安装seafile
    【python】遇上COS美图怎么办?当然是大胆冲呀~
    Express 4 快速入门 - 基本路由
    【论文笔记】—曝光不足图像增强—Supervised—DeepUPE—2019-CVPR
    FFplay文档解读-19-音频过滤器四
    iPhone苹果15手机圆点怎么设置让屏幕上显示出来圆形图标?
    高新技术企业领域划分
    【示波器专题】示波器的频响方式
    Java项目:SSM药品进货销售管理系统
    Apache Commons FileUpload框架的简介说明
  • 原文地址:https://blog.csdn.net/junxuezheng/article/details/126908851