C++:从 dll 动态加载类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/431533/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:19:23  来源:igfitidea点击:

C++: Dynamically loading classes from dlls

c++windowsclassdll

提问by Fire Lancer

For my current project I want to be able to load some classes from a dll (which is not always the same, and may not even exist when my app is compiled). There may also be several alternative dll's for a given class (eg an implementation for Direct3D9 and one for OpenGL), but only one of the dlls will be loaded/used at any one time.

对于我当前的项目,我希望能够从 dll 加载一些类(这并不总是相同的,甚至在我的应用程序编译时可能不存在)。对于给定的类,也可能有多个替代 dll(例如 Direct3D9 的实现和 OpenGL 的一个实现),但在任何时候都只会加载/使用其中一个 dll。

I have a set of base classes that define the interface plus some basic methods/members (ie the ones for refrence counting) of the classes I want to load, which the dll projects then derive from when creating there classes.

我有一组定义接口的基类以及我想要加载的类的一些基本方法/成员(即用于引用计数的方法/成员),然后 dll 项目在创建这些类时从中派生。

//in namespace base
class Sprite : public RefCounted//void AddRef(), void Release() and unsigned refCnt
{
public:
    virtual base::Texture *GetTexture()=0;
    virtual unsigned GetWidth()=0;
    virtual unsigned GetHeight()=0;
    virtual float GetCentreX()=0;
    virtual float GetCentreY()=0;
    virtual void SetCentre(float x, float y)=0;

    virtual void Draw(float x, float y)=0;
    virtual void Draw(float x, float y, float angle)=0;
    virtual void Draw(float x, float y, float scaleX, flota scaleY)=0;
    virtual void Draw(float x, float y, float scaleX, flota scaleY, float angle)=0;
};

The thing is I'm not sure how to do it all so that the executable and other dlls can load and use these classes since ive only ever used dlls where there was only one dll and I could have the Visual Studio linker sort it all out using the .lib file I get when compileing dll's.

问题是我不知道如何做到这一切,以便可执行文件和其他 dll 可以加载和使用这些类,因为我只使用过只有一个 dll 的 dll 并且我可以让 Visual Studio 链接器将其全部解决使用我在编译 dll 时得到的 .lib 文件。

I dont mind using factory methods for instancing the classes, many of them do already by design (Ie a sprite class is created by the main Graphics class, eg Graphics->CreateSpriteFromTexture(base::Texture*)

我不介意使用工厂方法来实例化类,其中许多已经通过设计(即精灵类由主 Graphics 类创建,例如 Graphics->CreateSpriteFromTexture(base::Texture*)

EDIT: When I needed to write some c++ dlls for use in python I used a library called pyCxx. The resulting dll basicly only exported one method, which created an instance of the "Module" class, which could then contain factory methods to create other classes etc.

编辑:当我需要编写一些在 python 中使用的 c++ dll 时,我使用了一个名为 pyCxx 的库。生成的 dll 基本上只导出一个方法,它创建了一个“模块”类的实例,然后可以包含工厂方法来创建其他类等。

The resulting dll could be imported in python just with "import [dllname]".

生成的 dll 可以通过“import [dllname]”导入到 python 中。

//dll compiled as cpputill.pyd
extern "C" void initcpputill()//only exported method
{
    static CppUtill* cpputill = new CppUtill;
}

class CppUtill : public Py::ExtensionModule<CppUtill>
{
public:
    CppUtill()
    : Py::ExtensionModule<CppUtill>("cpputill")
    {
        ExampleClass::init_type();

        add_varargs_method("ExampleClass",&CppUtill::ExampleClassFactory, "ExampleClass(), create instance of ExampleClass");
        add_varargs_method("HelloWorld",  &CppUtill::HelloWorld,  "HelloWorld(), print Hello World to console");

        initialize("C Plus Plus module");
    }
...
class ExampleClass
...
    static void init_type()
    {
        behaviors().name("ExampleClass");
        behaviors().doc ("example class");
        behaviors().supportGetattr();
        add_varargs_method("Random", &ExampleClass::Random, "Random(), get float in range 0<=x<1");
    }

How exactly does that work, and could I use it in a purely c++ enviroment to solve my problem here?

它究竟是如何工作的,我可以在纯 C++ 环境中使用它来解决我的问题吗?

采纳答案by Paulius

Easiest way to do this, IMHO, is to have a simple C function that returns a pointer to an interface described elsewhere. Then your app, can call all of the functions of that interface, without actually knowing what class it is using.

恕我直言,最简单的方法是使用一个简单的 C 函数,该函数返回一个指向其他地方描述的接口的指针。然后您的应用程序可以调用该接口的所有功能,而实际上不知道它正在使用什么类。

Edit: Here's a simple example.

编辑:这是一个简单的例子。

In your main app code, you create a header for the interface:

在您的主应用程序代码中,您为界面创建一个标头:

class IModule
{
public:
    virtual ~IModule(); // <= important!
    virtual void doStuff() = 0;
};

Main app is coded to use the interface above, without any details on the actual implementation of the interface.

主应用程序被编码为使用上面的接口,没有关于接口实际实现的任何细节。

class ActualModule: public IModule
{
/* implementation */
};

Now, the modules - the DLL's have the actual implementations of that interface, and those classes don't even have to be exported - __declspec (dllexport)isn't needed. The only requirement for the modules is to export a single function, that would create and return an implementation of the interface:

现在,不需要这些模块 - DLL 具有该接口的实际实现,并且这些类甚至不必导出 -__declspec (dllexport)是不需要的。模块的唯一要求是导出单个函数,该函数将创建并返回接口的实现:

__declspec (dllexport) IModule* CreateModule()
{
    // call the constructor of the actual implementation
    IModule * module = new ActualModule();
    // return the created function
    return module;
}

note: error checking left out - you'd usually want to check, if new returned the correct pointer and you should protect yourself from the exceptions that might be thrown in the constructor of the ActualModuleclass.

注意:错误检查被遗漏了 - 您通常想要检查 new 是否返回了正确的指针,并且您应该保护自己免受ActualModule类的构造函数中可能抛出的异常的影响。

Then, in your main app, all you need is to simply load the module (LoadLibraryfunction) and find the function CreateModule(GetProcAddrfunction). Then, you use the class through the interface.

然后,在您的主应用程序中,您只需加载模块(LoadLibrary函数)并找到函数CreateModuleGetProcAddr函数)。然后,您通过接口使用该类。

Edit 2: your RefCount (base class of the interface), can be implemented in (and exported from) the main app. Then all your module would need to link to the lib file of the main app (yes! EXE files can have LIB files just like DLL files!) And that should be enough.

编辑 2:您的 RefCount(接口的基类),可以在主应用程序中实现(并从中导出)。然后你的所有模块都需要链接到主应用程序的 lib 文件(是的!EXE 文件可以像 DLL 文件一样具有 LIB 文件!)这应该就足够了。

回答by Hans Passant

You are re-inventing COM. Your RefCounted class is IUnknown. Your abstract class is an interface. A COM server in a DLL has an entrypoint named DllGetClassObject(), it is a class factory. There is lots of documentation available from Microsoft on COM, poke around a bit to see how they did it.

您正在重新发明 COM。您的 RefCounted 类是 IUnknown。你的抽象类是一个接口。DLL 中的 COM 服务器有一个名为 DllGetClassObject() 的入口点,它是一个类工厂。微软在 COM 上有很多可用的文档,看看他们是如何做到的。

回答by Hans Passant

[Edit: whilst I was composing all this, Paulius Maru?ka edited his comment to say basically the same. So apologies for any duplication. Though I suppose you've now got one for spare :)]

[编辑:当我在创作这一切时,Paulius Maru?ka 编辑了他的评论以说基本相同。因此,对于任何重复,我们深表歉意。虽然我想你现在有一个备用:)]

Off the top of my head, and assuming Visual C++...

在我的头顶上,假设 Visual C++ ......

You need to use LoadLibrary to load a DLL in dynamically, then use GetProcAddress to retrieve from it the address of a function that will create the actual derived class that the DLL code implements. How you decide to do this precisely is up to you (the DLLs need finding, the way the expose their functionality needs specifying, etc.) so for now let's assume that plugins only provides new Sprite implementations.

您需要使用 LoadLibrary 动态加载 DLL,然后使用 GetProcAddress 从中检索函数的地址,该函数将创建 DLL 代码实现的实际派生类。您决定如何精确地执行此操作取决于您(DLL 需要查找、公开其功能需要指定的方式等),因此现在让我们假设插件仅提供新的 Sprite 实现。

To do this, decide on the signature of the function in the DLL that the main program will call to create one of these new sprites. This one looks suitable:

为此,请确定主程序将调用以创建这些新精灵之一的 DLL 中函数的签名。这个看起来很合适:

typedef Sprite *(*CreateSpriteFn)();

Then, from the main program, you'll have to load a DLL (again, how you find this DLL is up to you) and get the sprite creation function from it. I've decided that the sprite creation function will be called "CreateSprite":

然后,从主程序中,您必须加载一个 DLL(同样,您如何找到这个 DLL 取决于您)并从中获取精灵创建函数。我决定将精灵创建函数称为“CreateSprite”:

HMODULE hDLL=LoadLibrary(pDLLName);
CreateSpriteFn pfnCreateSprite=CreateSpriteFn(GetProcAddress(hDLL,"CreateSprite"));

Then to actually create one of these, just call the function:

然后要实际创建其中之一,只需调用函数:

Sprite *pSprite=(*pfnCreateSprite)();

Once you are done with the DLL and there are no objects left that were created by it, you then use FreeLibrary to get rid of it:

一旦你完成了 DLL 并且没有留下由它创建的对象,你就可以使用 FreeLibrary 来摆脱它:

FreeLibrary(hDLL);

To create a DLL that sports this interface, write the code for the derived class and so on then somewhere in the DLL code provide the CreateSprite function that the calling program needs, using the appropriate signature:

要创建支持此接口的 DLL,请编写派生类的代码等,然后在 DLL 代码中的某处使用适当的签名提供调用程序所需的 CreateSprite 函数:

__declspec(dllexport)
extern "C"
Sprite *CreateSprite()
{
    return new MyNewSprite;
}

The dllexport thing means that you can use GetProcAddress to pick this function up by name, and the extern "C" ensures that the name is unmangled and doesn't end up as "CreateSprite@4" or something like.

dllexport 意味着您可以使用 GetProcAddress 按名称选择此函数,并且 extern "C" 确保名称未损坏并且不会以“CreateSprite@4”或类似的形式结束。

Two other notes:

另外两个注意事项:

  1. GetProcAddress returns 0 if it couldn't find the function, so you can use this to scan through a list of DLLs (e.g., returned from FindFirstFile and friends) looking for DLLs that support the interface, and/or try to find multiple entry points and support multiple types per plugin.
  2. if the main program is going to delete the sprite using the delete operator, this requires that both DLL and main program are built using the same runtime library, so that the main program's delete and the DLL's new are both using the same heap. You can work around this to some extent by having the DLL provide a DeleteSprite function, that deletes the sprite using the DLL's runtime library rather than the main program's, but you still have to take care about the rest of your program, so you may just want to force DLL writers to use the same runtime library as your program. (I wouldn't say you'd be in good company by doing this, but it's not uncommon. 3D Studio MAX requires this, for example.)
  1. 如果找不到函数,GetProcAddress 返回 0,因此您可以使用它来扫描 DLL 列表(例如,从 FindFirstFile 和朋友返回)寻找支持该接口的 DLL,和/或尝试找到多个入口点并支持每个插件的多种类型。
  2. 如果主程序要使用 delete 操作符删除精灵,这就要求 DLL 和主程序都使用相同的运行时库构建,以便主程序的 delete 和 DLL 的 new 都使用相同的堆。您可以通过让 DLL 提供一个 DeleteSprite 函数来在一定程度上解决这个问题,该函数使用 DLL 的运行时库而不是主程序的运行时库来删除精灵,但您仍然需要注意程序的其余部分,因此您可能只需要想要强制 DLL 编写者使用与您的程序相同的运行时库。(我不会说你这样做会很好,但这种情况并不少见。例如,3D Studio MAX 要求这样做。)

回答by Ana Betts

Perhaps you want to look into DLL Delay-Loading (http://www.codeproject.com/KB/DLL/Delay_Loading_Dll.aspx) - this will give you what you want without having to work too hard for it

也许你想研究 DLL 延迟加载(http://www.codeproject.com/KB/DLL/Delay_Loading_Dll.aspx)——这会给你你想要的东西,而不必为此付出太多努力