【问题标题】:Reverse Dynamic Linking function call反向动态链接函数调用
【发布时间】:2016-05-17 07:08:34
【问题描述】:

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

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

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

【问题讨论】:

  • 当你说“内核”是什么意思? “内核”一词在计算机编程中有多种用途,目前尚不清楚其中哪一种含义与此处相关。您还应该read about how to ask good questions,并学习如何创建Minimal, Complete, and Verifiable Example
  • @JoachimPileborg 它会改变答案吗?考虑微内核架构。内核是应用程序的核心。
  • 所以您正在编写自己的操作系统内核?不是例如一个CUDA内核?请更新您的标签以反映这一点,或者至少在问题正文中说明这一点。
  • @JoachimPileborg 您认为在编写自己的操作系统时可以调用 Windows API 吗?这个问题很清楚。
  • @JoachimPileborg 更不用说在 CUDA 内核中无法调用此类函数,上下文和问题完美地表明了内核的含义。坦率地说,你试图离开 cmets 是为了提高声誉。

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


【解决方案1】:

它应该可以工作,见这里: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”,这是在构建 EXE 时创建的。 main()调用DLL中的callSum()函数,DLL又调用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 不需要链接到任何“内核”库(不需要导出纯虚拟接口)。

这通常甚至可以跨编译器工作(即由与 DLL 不同的编译器编译的可执行文件,例如 MSVC 和 GCC) - 只要虚拟表实现相同。这不是强制性的,但它实际上是 COM 工作的先决条件(提供不同多态性实现的编译器不能使用 Microsoft COM 调用)。

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

【讨论】:

  • 非常感谢。我正在探索答案。
  • 一个问题,这种技术(使用 .lib 文件)是否需要在每次更改内核时重新编译组件?顺便问一下,有依赖倒置技术的例子吗?
  • 我想我得到了问题第一部分的答案。使用过 .lib 文件的程序无需重新编译。
  • 谢谢,感谢您的帮助。
【解决方案2】:

考虑以另一种方式设计您的设计:也就是说,“内核”是一个 DLL,并由您的组件应用程序加载。因为是内核在为组件提供服务,而不是反过来,所以这更有意义。

【讨论】:

  • 内核应该为组件提供通过消息传递相互通信的基础设施。我做不到。
猜你喜欢
  • 2011-09-25
  • 1970-01-01
  • 1970-01-01
  • 2017-06-23
  • 1970-01-01
  • 1970-01-01
  • 2020-12-02
  • 1970-01-01
  • 2018-01-24
相关资源
最近更新 更多