【问题标题】:Loading external class c++加载外部类 c++
【发布时间】:2021-05-21 15:49:30
【问题描述】:

我正在尝试加载在 .dll 文件中定义的类。然而,在 dll 中定义类有两种略有不同的方法。我不确定哪种方法更合法,我也不知道为什么第二种方法也有效。这是一个简单的例子:

方法一:main.cpp:

#include <iostream>
#include <windows.h>
#include <memory>
#include "bar.h"

using namespace std;

typedef bar* (* MYDLLPROC)();

int main()
{
    unique_ptr<bar> theresult;
    auto thelib = LoadLibrary(TEXT("foo.dll"));

    if (thelib != NULL) {
        MYDLLPROC theprocs = (MYDLLPROC)GetProcAddress(thelib, "Myfoo");
        cout << "load successfully" << endl;
        theresult.reset(theprocs());
        theresult->printmsg();
    } else {
        cout << "cannot load the dll" << endl;
    }

    return 1;
}

barbar.h 中被定义为纯虚类:

class bar {
public:
    virtual ~bar() {};
    virtual void printmsg() = 0;
};

foo.dll 源文件中:

#include <iostream>
#include <windows.h>
#include "bar.h"

using namespace std;

class foo: public bar {
public:
    foo() { cout << "foo is instantiated" << endl; }
    ~foo() {}
    void printmsg() final { cout << "msg from foo print" << endl; }
};

extern "C" __declspec(dllexport) foo* __cdecl Myfoo()
{
    return new foo();
}

在第一种方法中,纯虚类bar 用作接口,它的成员函数在加载dll 时被foo 中的成员函数覆盖。

但是,我发现foo 不一定是从bar 派生的,只要foo 有一个Vtable,一切仍然有效:

在第二种方法中,除了foo的定义之外,一切都一样:

#include <iostream>
#include <windows.h>

using namespace std;

class foo {
public:
    foo() { cout << "foo is instantiated" << endl; }
    virtual ~foo() {}
    virtual void printmsg() final { cout << "msg from foo print" << endl; }
};

extern "C" __declspec(dllexport) foo* __cdecl Myfoo()
{
    return new foo();
}

谁能告诉我为什么第二种方法有效?我有点困惑,因为foobar 不相关,但bar 中的成员函数仍然可以被覆盖。

【问题讨论】:

  • 第二个是在你的测试程序中导致的未定义行为。不幸的是,有时未定义的行为似乎有效。
  • @drescherjm 哦,抱歉,这是一个错字,我已更正为 Myfoo。感谢您指出这一点!
  • @drescherjm 这是否意味着虽然第二种方法有效但有时会出错?
  • 这是否意味着虽然第二种方法有效但有时会出错? 是的,我相信是的。在第二个 dll 中,foo 类不是 bar,但通过强制转换 typedef bar* (* MYDLLPROC)(); 你是在对编译器撒谎,告诉它“相信我返回的指针是指向条形的指针”
  • @drescherjm 有道理!非常感谢!

标签: c++ c dll vtable loadlibrary


【解决方案1】:

因此,您将返回 foo* 的函数转换为返回 bar* 的函数,然后调用它。

最终结果是你有一个指向foo 的指针,它是一个指向不相关类型bar 的指针。以任何方式使用它都会导致undefined behavior

它似乎在这种特定情况下有效,因为 printmsg 虚函数在两个 vtable 中的位置相同,因此在 foo 的实例上调用 bar::printmsg 只需调用“vtable 中的第 N 个条目” .如果在printmsg 之前将另一个虚拟成员添加到foo,那么它可能会被调用(或者程序可能会崩溃)。

【讨论】:

  • 真的!我添加了另一个函数,这两个函数的顺序在foobar 中是不同的。然后调用新添加的函数。非常感谢!
【解决方案2】:

第一个示例非常脆弱,因为它隐含地依赖于 foobar 是指针互换。

第二个示例被破坏,因为函数 Myfoo 返回一个指向类 foo 的指针,该指针与 bar 无关,并且取消引用它会导致未定义的行为。

函数签名必须与 dll 中实现的函数匹配。你现在拥有的基本上是从foo *bar * 的狂野reinterpret_cast

【讨论】:

  • 谢谢!现在我明白第二种方法定义不明确。实际上,第一种方法会导致对象切片,但是我必须在 dll 中定义实际的类。对于主函数,实际类是未知的,所以我需要使用纯虚拟类为其创建一个接口。所以我想只要在主函数中我只调用纯虚类中声明的成员函数就安全了?
  • @Weigh 在第一个示例中没有发生对象切片。如果你要返回一个指向抽象类的指针,你需要确保在 dll 中实现的函数返回一个指向这个类的指针,而不是一个指向派生类的指针。将函数加载为返回 bar * 而在 dll 中声明为返回 foo * 不是一个好主意。
  • 那么我应该在函数Myfoo() 中将foo 转换为bar 吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多