Exporting C++ classes from a DLL

2024-02-12 04:08
文章标签 c++ dll classes exporting

本文主要是介绍Exporting C++ classes from a DLL,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Exporting C++ classes from a DLL

这个文章中的内容和之前的文章中的内容是一致的意思,核心思想是在创建动态库的时候创建一个重虚函数类作为基类接口使用,而在exe中使用这些接口来访问动态库中对这些基类重写的函数,从而达到访问动态库的内容的目的。
目前个人对此问题的理解为:主要是C++中对编译器对代码进行编译之后,函数名称发生变化,跟C相比,C代码中的函数的名称在编译完成之后是不会发生变化的,而C++会发生变化,从而需要一种方法在C++中可以直接访问函数的原型名称,而虚函数在C++中是以指针的形式从在在整个类当中的,不属于任何一个实例。同时下面的代码中,需要注意的是:三个变量中的虚函数指针都是指向同一个地方。
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
//#include "stdafx.h"
#include <iostream>
using namespace std;
class test_base {
public:test_base() {cout << "test_base construct fun" << endl;data = 0x22;}virtual ~test_base() {}void call_base() const {cout << "test_base:call_base():data=" << data<<endl;}virtual void display() const = 0;void testbase(test_base *base){base->call_base();base->display();}
private:	//test_base(const test_base &base);int data;
};class test_son:public  test_base {public:test_son():test_base(){cout << "test_son construct fun" << endl;data = 0xff;}virtual ~test_son() {}void call_base() const{cout << "test_son:call_base():data=" << data << endl;}void display() const{cout << "test_son:display():data=" << data << endl;}void testbase(test_son *base){base->call_base();base->display();}
private:int data;//test_son(const test_son &base);
};void testbase(test_base *base)
{base->call_base();base->display();
}
void testbase(test_son *base)
{base->call_base();base->display();
}
void testson(const test_son son)
{son.display();son.call_base();
}int main()
{
#if 1test_base *son_prt = new test_son();testbase(son_prt);son_prt->testbase(son_prt);cout << "========================================" << endl;test_son *son_prt1 = new test_son();testbase(son_prt1);son_prt1->testbase(son_prt1);cout << "========================================" << endl;test_son m_son =  test_son();testson(m_son);
#endif _getchar_nolock();return 0;
}
可以通过调试来看看对象中的虚函数表指针:



Because of ABI incompatibilities between compilers and even different versions of the same compiler, exporting C++ classes from DLLs is a tricky business. Luckily, with some care it is possible to do this safely, by employing abstract interfaces.

In this post I will show a code sample of a DLL and an application using it.The DLL exports a class by means of a factory function that creates new objects that adhere to a known abstract interface. The main application loads this DLL explicitly (with LoadLibrary) and uses the objects created by it. The code shown here is Windows-specific, but the same method should work for Linux and other platforms. Also, the same export technique will work for implicit DLL loading as well.

First, we define an abstract interface (by means of a class with pure virtual methods, and no data),in a file namedgeneric_interface.h:

上面最重要的说明的就是:by employing abstract interfaces.导出接口是安全的,导出抽象类是不安全的,因此我们对外只能export的"abstract interfaces.",包含纯虚方法的类叫抽象类,只包含纯虚方法的类叫"abstract interfaces."

classIKlass {
public:virtualvoid destroy() = 0;virtualint do_stuff(int param) = 0;virtualvoid do_something_else(double f) = 0;
};

Note that this interface has an explicit destroy method, for reasons I will explain later. Now, the DLL code, contained in a single C++ file:

#include "generic_interface.h"
#include <iostream>
#include <windows.h>
usingnamespace std;
classMyKlass : public IKlass {
public:MyKlass(): m_data(0){cerr << "MyKlass constructor\n";}~MyKlass(){cerr << "MyKlass destructor\n";}void destroy(){deletethis;}int do_stuff(int param){m_data += param;return m_data;}void do_something_else(double f){int intpart = static_cast<int>(f);m_data += intpart;}
private:int m_data;
};extern"C"__declspec(dllexport) IKlass* __cdecl create_klass()
{returnnew MyKlass;
}

There are two interesting entities here:

  1. MyKlass - a simplistic implementation of the IKlass interface.
  2. A factory function for creating new instances of MyKlass.

And here is a simple application (also contained in a single C++ file) that uses this library by loading the DLL explicitly, creating a new object and doing some work with it:

#include "generic_interface.h"
#include <iostream>
#include <windows.h>
usingnamespace std;// A factory of IKlass-implementing objects looks thus
typedef IKlass* (__cdecl *iklass_factory)();int main()
{// Load the DLLHINSTANCE dll_handle = ::LoadLibrary(TEXT("mylib.dll"));if (!dll_handle) {cerr << "Unable to load DLL!\n";return1;}// Get the function from the DLLiklass_factory factory_func = reinterpret_cast<iklass_factory>(::GetProcAddress(dll_handle, "create_klass"));if (!factory_func) {cerr << "Unable to load create_klass from DLL!\n";::FreeLibrary(dll_handle);return1;}// Ask the factory for a new object implementing the IKlass// interfaceIKlass* instance = factory_func();// Play with the objectint t = instance->do_stuff(5);cout << "t = " << t << endl;instance->do_something_else(100.3);int t2 = instance->do_stuff(0);cout << "t2 = " << t2 << endl;// Destroy it explicitlyinstance->destroy();::FreeLibrary(dll_handle);return0;
}

Alright, I raced through the code, but there are a lot of interesting details hiding in it. Let's go through them one by one.

Clean separation

There are other methods of exporting C++ classes from DLLs (here's one good discussion of the subject). The one presented here is the cleanest - the least amount of information is shared between the DLL and the application using it - just the generic interface header defining IKlass and an implicit agreement about the signature of the factory function.

The actual MyKlass can now use whatever it wants to implement its functionality, without exposing any additional details to the application.

Additionally, this code can easily serve as a basis for an even more generic plugin architecture. DLL files can be auto-discoverable from a known location, and a known function can be exposed from each that defines the exported factories.

Memory management

Memory management between DLLs can be a real pain, especially if each DLL links the MSVC C runtime statically (which tends to be common on Windows). Memory allocated in one DLL must not be released in another in such cases.

The solution presented here neatly overcomes this issue by leaving all memory management to the DLL. This is done by providing an explicit destroy function in the interface, that must be called when the object is no longer needed. Naturally, the application can wrap these objects by a smart pointer of some kind to implement RAII.

Note that destroy is implemented with delete this. This may raise an eyebrow or two, but it's actually valid C++ thatoccasionally makes sense if used judiciously.

It's time for a pop quiz: why doesn't IKlass need a virtual destructor?

Name mangling and calling convention

You've surely noticed that the signature of create_klass is rather intricate:

extern"C"__declspec(dllexport) IKlass* __cdecl create_klass()

Let's see what each part means, in order:

  • extern "C" - tells the C++ compiler that the linker should use the C calling convention and name mangling for this function. The name itself is exported from the DLL unmangled (create_klass)
  • __declspec(dllexport) - tells the linker to export the create_klass symbol from the DLL. Alternatively, the namecreate_klass can be placed in a .def file given to the linker.
  • __cdecl - repeats that the C calling convention is to be used. It's not strictly necessary here, but I include it for completeness (in the typedef for iklass_factory in the application code as well).

There is a variation on this theme, which I'll mention because it's a common problem people run into.

One can declare the function with the __stdcall calling convention instead of __cdecl. What this will do is causeGetProcAddress to not find the function in the DLL. A peek inside the DLL (with dumpbin /exports or another tool) reveals why - __stdcall causes the name to be mangled to something like _create_klass@0. To overcome this, either place the plain name create_klass in the exports section of the linker .def file, or use the full, mangled name in GetProcAddress. The latter might be required if you don't actually control the source code for the DL

这篇关于Exporting C++ classes from a DLL的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/701624

相关文章

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

C++作用域和标识符查找规则详解

《C++作用域和标识符查找规则详解》在C++中,作用域(Scope)和标识符查找(IdentifierLookup)是理解代码行为的重要概念,本文将详细介绍这些规则,并通过实例来说明它们的工作原理,需... 目录作用域标识符查找规则1. 普通查找(Ordinary Lookup)2. 限定查找(Qualif

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

C/C++中OpenCV 矩阵运算的实现

《C/C++中OpenCV矩阵运算的实现》本文主要介绍了C/C++中OpenCV矩阵运算的实现,包括基本算术运算(标量与矩阵)、矩阵乘法、转置、逆矩阵、行列式、迹、范数等操作,感兴趣的可以了解一下... 目录矩阵的创建与初始化创建矩阵访问矩阵元素基本的算术运算 ➕➖✖️➗矩阵与标量运算矩阵与矩阵运算 (逐元

C/C++的OpenCV 进行图像梯度提取的几种实现

《C/C++的OpenCV进行图像梯度提取的几种实现》本文主要介绍了C/C++的OpenCV进行图像梯度提取的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录预www.chinasem.cn备知识1. 图像加载与预处理2. Sobel 算子计算 X 和 Y

C/C++和OpenCV实现调用摄像头

《C/C++和OpenCV实现调用摄像头》本文主要介绍了C/C++和OpenCV实现调用摄像头,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录准备工作1. 打开摄像头2. 读取视频帧3. 显示视频帧4. 释放资源5. 获取和设置摄像头属性

c/c++的opencv图像金字塔缩放实现

《c/c++的opencv图像金字塔缩放实现》本文主要介绍了c/c++的opencv图像金字塔缩放实现,通过对原始图像进行连续的下采样或上采样操作,生成一系列不同分辨率的图像,具有一定的参考价值,感兴... 目录图像金字塔简介图像下采样 (cv::pyrDown)图像上采样 (cv::pyrUp)C++ O

c/c++的opencv实现图片膨胀

《c/c++的opencv实现图片膨胀》图像膨胀是形态学操作,通过结构元素扩张亮区填充孔洞、连接断开部分、加粗物体,OpenCV的cv::dilate函数实现该操作,本文就来介绍一下opencv图片... 目录什么是图像膨胀?结构元素 (KerChina编程nel)OpenCV 中的 cv::dilate() 函