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++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

C++构造函数中explicit详解

《C++构造函数中explicit详解》explicit关键字用于修饰单参数构造函数或可以看作单参数的构造函数,阻止编译器进行隐式类型转换或拷贝初始化,本文就来介绍explicit的使用,感兴趣的可以... 目录1. 什么是explicit2. 隐式转换的问题3.explicit的使用示例基本用法多参数构造

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

C++打印 vector的几种方法小结

《C++打印vector的几种方法小结》本文介绍了C++中遍历vector的几种方法,包括使用迭代器、auto关键字、typedef、计数器以及C++11引入的范围基础循环,具有一定的参考价值,感兴... 目录1. 使用迭代器2. 使用 auto (C++11) / typedef / type alias

C++ scoped_ptr 和 unique_ptr对比分析

《C++scoped_ptr和unique_ptr对比分析》本文介绍了C++中的`scoped_ptr`和`unique_ptr`,详细比较了它们的特性、使用场景以及现代C++推荐的使用`uni... 目录1. scoped_ptr基本特性主要特点2. unique_ptr基本用法3. 主要区别对比4. u

C++11中的包装器实战案例

《C++11中的包装器实战案例》本文给大家介绍C++11中的包装器实战案例,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录引言1.std::function1.1.什么是std::function1.2.核心用法1.2.1.包装普通函数1.2.

C++多线程开发环境配置方法

《C++多线程开发环境配置方法》文章详细介绍了如何在Windows上安装MinGW-w64和VSCode,并配置环境变量和编译任务,使用VSCode创建一个C++多线程测试项目,并通过配置tasks.... 目录下载安装 MinGW-w64下载安装VS code创建测试项目配置编译任务创建 tasks.js

C++ 多态性实战之何时使用 virtual 和 override的问题解析

《C++多态性实战之何时使用virtual和override的问题解析》在面向对象编程中,多态是一个核心概念,很多开发者在遇到override编译错误时,不清楚是否需要将基类函数声明为virt... 目录C++ 多态性实战:何时使用 virtual 和 override?引言问题场景判断是否需要多态的三个关

C++简单日志系统实现代码示例

《C++简单日志系统实现代码示例》日志系统是成熟软件中的一个重要组成部分,其记录软件的使用和运行行为,方便事后进行故障分析、数据统计等,:本文主要介绍C++简单日志系统实现的相关资料,文中通过代码... 目录前言Util.hppLevel.hppLogMsg.hppFormat.hppSink.hppBuf