本文主要是介绍Exporting C++ classes from a DLL,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Exporting C++ classes from a DLL
// 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:
MyKlass
- a simplistic implementation of theIKlass
interface.- 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 thecreate_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 thetypedef
foriklass_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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!