c++ dll windows插件架构方法

Fru*_*oul 5 c++ dll plugins

正如标题所暗示的那样,我的第一种插件架构方法遇到了麻烦,我希望得到一些建议,但让我具体说一下……

假设您有一个带有一些定义方法的纯抽象类 BASE... 假设您还有一个主要的可执行文件,以及一个在运行时与主要可执行文件动态链接的 dll 文件...BASE 位于包含在主文件中的头文件中exe 和 dll... BASE 子类在 dll 中定义,并实现了它们的抽象方法... Main exe 分配了一个指向 BASE 的指针数组。该数组被传递给 dll。dll 在堆上创建对象,并返回填充有指向这些对象实例的指针的数组。

问题是,当我尝试在主 exe 上的那些实例之一上调用 BASE 的成员方法时(应该由派生类在 dll 中重载),我收到访问冲突错误。我没有通过我为主要可执行文件和 dll 定义的 c 接口传递类或任何特定于 c++ 的东西......只是指针......

这实际上只是出于学术目的,但我仍然在考虑可能不同的人会为主程序创建插件(dll)的想法,因此兼容性是一个问题。所以我认为将 dll 的接口保留在 c 中是个好主意,您不同意吗?

我认为 dll 和主可执行文件共享相同的堆空间,所以指向对象实例的指针应该可以工作,不是吗?

另一方面,指向对象的指针只会给主程序一个内存中存储实例变量的位置,但没有关于其方法代码的信息,主程序对此一无所知,因为它是在 dll 中定义的. 所以如果没有可执行代码的地址,它就不能调用抽象类的方法实现是合乎逻辑的。到目前为止,我的推测是否正确?

我知道我可以使用保存在每个对象中的函数指针,但这不会破坏 C++ 好的内置多态机制吗?

另一种方法是,因为 BASE 的抽象方法是已知的,所以为每个 BASE 类的方法实现一个 c 风格的函数(在 dll 的 c 接口中),并带有一个额外的参数,即对象的 ptr。通过这种方式,可以在特定对象的 dll 内处理实现的方法。我非常不喜欢这个主意。使dll的接口更大,使用了大量的c函数,几乎使派生类的处理成为c风格,或者至少需要包装在其他c++类中。你对此有什么想法?

我主要问社区的是:我尝试这样做的方式是正确的方法吗?还是我的想法完全错误?我错过了什么重要的东西吗?你还有什么建议?

非常感谢您的时间和支持。

更新: 由于我无法弄清楚我做错了什么,我将求助于上传我的代码......

主程序 - .cpp 文件

#include "feats.h"

using namespace std;
typedef void* (__cdecl *_connect)(void);

void addFeats(vector<feats::feat*> &vec, void* pluginDllRet) {
    size_t i = 0;
    feats::feat **retVal = (feats::feat**) pluginDllRet;
    while(retVal[i] != NULL) vec.push_back(retVal[i++]);
}

int main(void) {
    HINSTANCE dllFile = LoadLibrary("dllName.dll");
    if(dllFile == NULL) { /*error Handling*/ }

    _connect con = (_connect) GetProcAddress(dllFile, "_connect");
    if(con == NULL) { /*error Handling*/ }

    vector<feats::feat*> featsHolder;
    addFeats(featsHolder, con());

    for(size_t i = 0; i < featsHolder.size(); i++)
        cout << featsHolder[i]->getName() << "\n";
    FreeLibrary(dllFile );
}
Run Code Online (Sandbox Code Playgroud)

以下是 dll 文件的 .cpp 文件...

#include "feats.h"
using namespace feats;
using namespace std;

extern "C" {
    __declspec(dllexport) void* __cdecl _connect(void) {
        return executableCode();    //<- this is visible in main program
    }
}

namespace feats {
    class acrobatic : public feats::feat {
        public:
        std::string getName(void) {
            return "Acrobatic";
        }
    };

    class powerfull : public feats::feat {
        public:
        std::string getName(void) {
            return "Powerfull";
        }
    };
}

void* executableCode(void) {
    feats::feat **fts = new feats::feat*[3];
    fts[0] = new acrobatic;
    fts[1] = new powerfull;
    fts[2] = NULL;
    return (void*) fts;
}
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的是主程序和 dll 中的头文件...

#ifndef FEATS_H
#define FEATS_H

#include <string>

namespace feats {
    class feat {
        public:
        virtual std::string getName(void) = 0;
    };
}
#endif
Run Code Online (Sandbox Code Playgroud)

因此,在提出这个问题时,我希望我的问题更清楚......我试图让它尽可能紧凑......所以,再次感谢你的帮助......

更新 2 - 解决方案 上面的代码似乎是正确的并且可以正常工作......我的问题是,在 Visual Studio 上工作是我在发布模式下编译了 dll,而主程序在调试模式下。翻转解决了问题......这么简单的错误,引起了这样的沉思......无论如何,我将上面的代码作为未来搜索的示例......

jus*_*sij 5

只需在头文件中定义抽象接口:

class AbstractBase
{
public:
  virtual int SomeFunction() = 0;

  virtual int SomeOtherFunction() = 0;
};
Run Code Online (Sandbox Code Playgroud)

在该头文件中还定义了两个 C 函数来创建和销毁一个类:

AbstractBase *CreateClass();    

void DestroyClass(AbstractBase *data);    
Run Code Online (Sandbox Code Playgroud)

在你的 dll 中实现具体的类:

class MyClass: AbstractBase
{
public:
    SomeFunction() {};
    virtual ~SomeFunction() {};

    virtual int SomeFunction() { return 1; }

    virtual int SomeOtherFunction() { return 1; }
};
Run Code Online (Sandbox Code Playgroud)

在你的 dll 中还实现了 create 和 destroy 函数:

AbstractBase *CreateClass()
{
    return new SomeFunction();
}

void DestroyClass(AbstractBase *data);    
{
   delete data;
}
Run Code Online (Sandbox Code Playgroud)

现在在主要的可执行文件加载中,在运行时创建一个 destroy dll 入口点。

调用 create 入口点以获取对象并使用抽象接口与该对象对话。

调用 destroy 入口点销毁对象。

  • 该模型绝对有效。我是 Zeus IDE 的作者,我使用完全相同的模式来实现通用代码折叠和通用脚本。例如,IDE 可以用大约 7 或 8 种语言(即 Python、Lua、VB 脚本等)编写脚本,但就 IDE 而言,只有一种脚本语言,因为它使用一种语言与所有不同的脚本引擎对话并且只有界面。代码折叠也是如此。 (2认同)