0

我有一个使用以下方法的 C++ dll:

//C++ dll method (external)
GetServerInterface(ServerInterface* ppIF /*[OUT]*/)
{
//The method will set ppIF
}

//ServerInterface is defined as:
typedef void *  ServerInterface;

为了从 C# 项目访问 dll,我创建了一个 C++/CLI 项目并声明了一个托管类,如下所示:

public ref class ComWrapperManager
{
//
//
ServerInterface _serverInterface;
void Connect();
//
//
}

我使用 Connect() 方法调用 GetServerInterface,如下所示。第一个电话有效,第二个电话无效。有人可以解释为什么吗?我需要将该指针作为成员变量保留在托管类中。有没有更好的方法来做到这一点?

    void Connect()
    {
    ServerInterface localServerInterface;
    GetServerInterface(&localServerInterface); //THIS WORKS

    GetServerInterface(&_serverInterface); //THIS DOESNT

    //Error 1   error C2664: 'ServerInterface ' : 
    //cannot convert parameter 1 from //'cli::interior_ptr<Type>' 
    //to 'ServerInterface *'


    }
4

2 回答 2

7

您正在传递一个指向托管对象成员的指针。这种指针很特殊,称为内部指针。它们被垃圾收集器跟踪,它会在 GC 压缩堆时移动托管对象时修改指针值。

问题是,您将该指针传递给非托管代码。GC 无法修改本机代码正在使用的指针值的副本。现在,当另一个线程触发垃圾收集时,灾难就会发生,就在本机代码正在执行并取消引用指针时。该对象不再存在于原始地址。非常非常糟糕。而且极难诊断,因为它不太可能发生。

编译器可以看到你犯了这个错误。并抱怨 C2664。

解决方法是传递一个指针,该指针存储在一个不会被 GC 移动的内存位置。这样的位置很容易找到,一个局部变量就可以了。它存储在堆栈中,不会被移动。所以让它看起来像这样:

void Connect()
{
    ServerInterface temp;
    GetServerInterface(&temp);
    this->_serverInterface = temp;
    // etc..
}

您已经发现了自己,只是不要忘记分配班级成员。

于 2013-09-09T12:33:00.043 回答
1

这就是为什么你不能做第二个的原因:_serverInterface是一个 void 指针,它是托管类的一部分。想想垃圾收集器做了什么......它可以随意移动内存中的托管对象,因此 void 指针的地址可以随时更改。因此,使用该地址是无效的。

有两种解决方案:

  1. 如您所述,您可以将堆栈变量的地址传递给非托管方法。与托管对象不同,当垃圾收集器执行其操作时堆栈不会移动,因此地址不会改变。然后,您可以获取存储在堆栈变量中的数据并将其复制到类字段,这很好,因为您没有处理它的地址。
  2. 正如其他回答者所说,您可以将托管对象锁定在内存中。一旦它不能移动,您可以毫无问题地获取 void 指针字段的地址。(他在你寻找 C++/CLI 语法的地方展示 C# 语法。我不在编译器旁检查,但我相信 C++/CLI 语法不一样。)

在这两种解决方案中,我更喜欢#1,即您已经实现的那个: 解决方案#2 在垃圾收集器想要重新排列的空间中间引入一块不可移动的内存。如果可以选择,我不希望限制垃圾收集器。

于 2013-09-09T11:32:25.743 回答