Exporting C++ classes from a DLL

2 篇文章 0 订阅
2 篇文章 0 订阅

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 1
	test_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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ABAP可以通过RFC函数调用外部DLL。RFC函数可以通过远程函数调用(RFC)协议在SAP系统和外部系统之间进行通信。以下是使用RFC函数调用DLL的一般步骤: 1. 在SAP系统中创建RFC函数模块,该函数模块将调用DLL中的函数。 2. 将DLL文件复制到SAP系统中,并在SAP系统中注册该DLL。 3. 在SAP系统中创建一个外部定义(EXTDEF),该定义指向DLL中的函数。 4. 在RFC函数模块中调用外部定义。 下面是一个简单的例子: 1. 在SAP系统中创建RFC函数模块 ```ABAP FUNCTION ZRFC_CALL_DLL. *"---------------------------------------------------------------------- *"*"Lokale Schnittstelle: *" IMPORTING *" VALUE(DLL_PATH) TYPE STRING *" VALUE(FUNCTION_NAME) TYPE STRING *" EXPORTING *" VALUE(RETURN_CODE) TYPE I *"---------------------------------------------------------------------- DATA: l_return_code TYPE i. CALL 'ZRFC_CALL_DLL' ID 'DLL_PATH' FIELD DLL_PATH ID 'FUNCTION_NAME' FIELD FUNCTION_NAME DESTINATION 'NONE' EXCEPTIONS OTHERS = 1. RETURN_CODE = l_return_code. ENDFUNCTION. ``` 2. 将DLL文件复制到SAP系统中,并在SAP系统中注册该DLL。 使用tcode SM59在SAP系统中创建RFC目标,使用tcode SMGW在SAP系统中配置RFC目标。 3. 在SAP系统中创建一个外部定义(EXTDEF),该定义指向DLL中的函数。 ```ABAP FUNCTION Z_MY_EXTERNAL_DEF. *"---------------------------------------------------------------------- *"*"Lokale Schnittstelle: *" IMPORTING *" VALUE(P1) TYPE I *" EXPORTING *" VALUE(P2) TYPE I *"---------------------------------------------------------------------- EXTERNAL DLL 'MY_DLL' NAME 'MY_FUNCTION' LANGUAGE C PARAMETER STYLE C USING p1 TYPE i CHANGING p2 TYPE i. ENDFUNCTION. ``` 4. 在RFC函数模块中调用外部定义。 ```ABAP FUNCTION ZRFC_CALL_DLL. *"---------------------------------------------------------------------- *"*"Lokale Schnittstelle: *" IMPORTING *" VALUE(DLL_PATH) TYPE STRING *" VALUE(FUNCTION_NAME) TYPE STRING *" EXPORTING *" VALUE(RETURN_CODE) TYPE I *"---------------------------------------------------------------------- DATA: l_return_code TYPE i. CALL FUNCTION 'Z_MY_EXTERNAL_DEF' DESTINATION 'NONE' EXPORTING p1 = 1 IMPORTING p2 = l_return_code. RETURN_CODE = l_return_code. ENDFUNCTION. ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值