反向动态链接功能调用

时间:2016-05-17 07:08:34

标签: c++ visual-c++ dll windows-applications

考虑应用程序的微内核软件架构。 我有一个内核和一个组件。

该组件是在运行时使用Windows中的LoadLibrary API由内核加载的DLL;当然,可以使用GetProcAddress调用导出的函数。

现在组件需要向内核发送消息。换句话说,现在是加载DLL的组件需要从内核调用函数。什么是正确的机制?

2 个答案:

答案 0 :(得分:3)

它应该有用,请看这里:https://stackoverflow.com/a/30475042/1274747

对于MSVC,您基本上会使用.exe中的__declspec(dllexport)。编译器/链接器为.exe生成导入库,然后可以与DLL链接,然后DLL将使用.exe中的符号。

另一种选择是通过“依赖性反转”来解决这个问题 - .exe不会导出符号,而是提供一个(纯虚拟)接口,它将在.exe中实现并传递(通过引用或指针)加载后到接口)进入DLL。然后,DLL可以调用.exe内部提供的接口上的方法。但实际上,当你谈到微内核时,它取决于你是否可以接受虚拟调用开销(尽管从.exe导出函数时,该方法也是通过函数指针调用的AFAIK,所以我不指望任何重大差异)。

修改

我刚创建了一个示例,它对我有用(只是一个快速的代码,没有太多的抛光,通常会使用标题等):

档案“mydll.cpp”:

// resolved against the executable
extern "C" __declspec(dllimport)
int __stdcall getSum(int a, int b);


extern "C" __declspec(dllexport)
int __stdcall callSum(int a, int b)
{
    return getSum(a, b);
}

文件“myexe.cpp”:

#include <iostream>
using namespace std;

#include <windows.h>

// export from the .exe
extern "C" __declspec(dllexport)
int __stdcall getSum(int a, int b)
{
    return a + b;
}


typedef int(__stdcall * callSumFn)(int a, int b);

int main()
{
    HMODULE hLibrary = LoadLibrary(TEXT("MyDll.dll"));
    if (!hLibrary)
    {
        cerr << "Failed to load library" << endl;
        return 1;
    }

    callSumFn callSum = (callSumFn)GetProcAddress(hLibrary, "_callSum@8");
    if (!callSum)
    {
        cerr << "Failed to get function address" << endl;
        FreeLibrary(hLibrary);
        return 1;
    }

    cout << "callSum(3, 4) = " << callSum(3, 4) << endl;

    FreeLibrary(hLibrary);
    return 0;
}

DLL与“MyExe.lib”链接,“MyExe.lib”是在构建EXE时创建的。 main()调用DLL中的callSum()函数,后者又调用EXE提供的getSum()

话虽这么说,我仍然更喜欢使用“依赖性反转”并将接口传递给DLL - 对我来说它看起来更干净也更灵活(例如通过接口继承进行版本控制等)。

编辑#2

对于依赖倒置技术,它可以是这样的:

文件ikernel.hpp(由内核可执行文件提供,而不是由DLL提供):

#ifndef IKERNEL_HPP
#define IKERNEL_HPP

class IKernel
{
protected:
    // or public virtual, but then there are differences between different compilers
    ~IKernel() {}
public:
    virtual int someKernelFunc() = 0;
    virtual int someOtherKernelFunc(int x) = 0;
};

#endif

档案“mydll.cpp”:

#include "ikernel.hpp"

// if passed the class by pointer, can be extern "C", i.e. loadable by LoadLibrary/GetProcAddress
extern "C" __declspec(dllexport)
int __stdcall callOperation(IKernel *kernel, int x)
{
    return kernel->someKernelFunc() + kernel->someOtherKernelFunc(x);
}

文件“myexe.cpp”:

#include "ikernel.hpp"

#include <iostream>
using namespace std;

#include <windows.h>

// the actual kernel definition
class KernelImpl: public IKernel
{
public:
    virtual ~KernelImpl() {}
    virtual int someKernelFunc()
    {
        return 10;
    }
    virtual int someOtherKernelFunc(int x)
    {
        return x + 20;
    }
};

typedef int(__stdcall * callOperationFn)(IKernel *kernel, int x);

int main()
{
    HMODULE hLibrary = LoadLibrary(TEXT("ReverseDll.dll"));
    if (!hLibrary)
    {
        cerr << "Failed to load library" << endl;
        return 1;
    }

    callOperationFn callOperation = (callOperationFn)GetProcAddress(hLibrary, "_callOperation@8");
    if (!callOperation)
    {
        cerr << "Failed to get function address" << endl;
        FreeLibrary(hLibrary);
        return 1;
    }

    KernelImpl kernel;

    cout << "callOperation(kernel, 5) = " << callOperation(&kernel, 5) << endl;

    FreeLibrary(hLibrary);
    return 0;
}

如上所述,这更灵活,恕我直言更容易维护;内核可以为不同的DLL调用提供不同的回调。如果有必要,DLL也可以提供内核的一些接口作为说明符的实现,它将首先从DLL中检索,内核将函数调用到它上面。

另一个便利是DLL不需要链接到任何“内核”库(不需要导出纯虚拟接口)。

这通常适用于编译器(即由不同的编译器编译的可执行文件,例如MSVC和GCC) - 前提是虚拟表实现是相同的。这不是强制性的,但它实际上是COM工作的先决条件(提供不同多态性实现的编译器无法使用Microsoft COM调用)。

但特别是在这种情况下,你绝对必须确保在EXE中没有释放DLL中分配的对象,反之亦然(它们可能使用不同的堆)。如果有必要,接口应提供纯虚拟destroy()方法,以确保在正确的内存上下文中“删除此”的多态调用。但即使直接调用函数,这可能也是一个问题(通常情况下不应该是free()内存malloc() - 另一方面)。此外,不得允许C ++异常通过API边界。

答案 1 :(得分:0)

考虑使您的设计反过来:即,'kernel'成为DLL并由组件应用程序加载。由于它是为组件提供服务的内核,而不是相反的方式,因此更有意义。