Exporting C++ classes from a DLL

来源:互联网 发布:施工动画什么软件 编辑:程序博客网 时间:2024/06/05 03:54

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 DLL    HINSTANCE dll_handle = ::LoadLibrary(TEXT("mylib.dll"));    if (!dll_handle) {        cerr << "Unable to load DLL!\n";        return1;    }    // Get the function from the DLL    iklass_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// interface    IKlass* 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 explicitly    instance->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

0 0