无法转换指针字段,同时可以在托管类

时间:2016-05-25 18:16:38

标签: c++-cli

我有WtfClass的非托管对象。

class WtfClass    {    };

我也有托管类,它使用指向这个对象的指针。

ref class MyClass  //works fine if you remove "ref"
{
public:
    void MyMethod();
    void WtfMethod(void * pVoid);
    WtfClass *pWtfStruct;
};

void MyClass::MyMethod()
{
    /*WtfClass* pWtfStruct;  //if you uncomment this it will compile even with ref*/
    WtfMethod((int*)(&pWtfStruct));  //(!!!invalid type conversion here)
}

void MyClass::WtfMethod(void *pVoid)
{}

我无法从字段中强制转换WtfClass *指针,但可以轻松地转换MyMethod()中定义的相同指针。如果make MyClass不受管理,它无论如何都适用。

查看屏幕截图更好:

  1. https://ibin.co/2iOcN1ooaC7A.png [使用ref-bad.png]
  2. https://ibin.co/2iOcYtP84H0e.png [使用ref-good.png]
  3. ibin.co/2iOcjCCc2gQe.png [without ref.png] (抱歉没有足够的声誉来粘贴2个以上的链接)
  4. 当然我可以有这样的解决方法,但我想了解发生这种情况的原因:

    void MyClass::MyMethod()
    {
        WtfClass* pWorkAround = pWtfStruct;  //not required in this case
        WtfMethod((void*)(&pWorkAround));
    }
    

4 个答案:

答案 0 :(得分:1)

当我尝试重新创建时,编译器生成了以下错误:

  

错误C2440:'type cast':无法转换为'cli :: interior_ptr< CWtfClass *>' 'LPVOID *'

我认为这里发生的事情是允许托管类拥有非托管成员的一些魔力。 MSDN documentation for cli::interior_ptr描述了正在发生的事情 - 基本上这用于允许托管对象更改托管堆中的内存地址,这会在本机指针进入时发生问题。

将成员首先分配给变量的原因很可能是因为它具有对模板参数的隐式转换,但由于它是托管类型,因此编译器不允许您获取变量的地址(因为垃圾收集器可以根据需要在内存中移动它。)

您的问题中的解决方法可能是解决此编译器错误的最佳方法。

答案 1 :(得分:1)

好的,总而言之,没有重复的字段&局部变量名:

ref class MyClass
{
    WtfClass* fieldWtfPtr;

    void foo()
    {
        WtfClass* localvarWtfPtr;

        WtfMethod((int*)(&fieldWtfPtr)); // Error
        WtfMethod((int*)(&localvarWtfPtr)); // Works
    }
};

附带问题:&fieldWtfPtr类型为WtfClass**,是双指针。你的意思是把它投射到int**,也是双指针吗?或者您是否想将fieldWtfPtr作为WtfClass*单指针并将其转换为int*单指针?

这就是您收到错误的原因:MyClass是一个托管对象。垃圾编译器可以在任何时候移动它,而不会告诉你。因此,它在内存中的位置可以随时改变。因此,当您尝试获取类字段的地址时,它无效,因为该字段的地址可以随时更改!

为什么其他事情能够发挥作用:

  • 局部变量存储在堆栈中,堆栈不会被垃圾收集器移动,因此获取局部变量的地址是有效的。
  • 如果删除ref,则MyClass不再是托管对象,因此垃圾收集器不会移动它,所以现在它的字段地址不会改变 - 愿意不愿意。

对于这种情况,最简单的解决方法是使用本地临时变量。

void foo()
{
    WtfClass* localCopyWtfPtr = this->fieldWtfPtr;

    WtfMethod((int*)(&localCopyWtfPtr)); // Works

    // If WtfMethod changed the data, write it back.
    this->fieldWtfPtr = localCopyWtfPtr;
}

答案 2 :(得分:1)

大卫回答为什么这种情况发生了,并为您的案例提出了解决方法。

我将在此处发布一个不同的解决方案:您可以固定您的托管对象,告诉GC不要移动它。最轻量级的方法是通过pin_ptr(GC甚至不会知道你固定的东西,除非它在一个集合的中间偶然发现你的代码)。只要它保持在范围内,托管对象将固定并且不会移动。最好是你要避免固定太长时间,但是这可以让你得到一个指向一大堆托管内存的指针,保证不会移动 - 当你想避免复制时,它会很有用。

以下是如何操作:

pin_ptr<WtfClass*> pin(&pWtfStruct);
WtfMethod(pin);

pin就像WtfClass**一样。

答案 3 :(得分:0)

关于 David Yaw 的附带问题。 我在使用一些WINAPI函数时遇到了这个问题。

IAudioEndpointVolume* pWtfVolume = NULL;    
pDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void**)&pWtfVolume);
pWtfVolume->SetMute(BST_CHECKED, pGuidMyContext);

只有当我通过&amp; pWtfVolume时,它才有效。具有讽刺意味的是,您可以在没有“&amp;”的情况下传递参数,只需pFieldVolume和编译器就会说OKAY,但接口IAudioEndpointVolume将无效。

看看这个:

ref class MyClass
{
    WtfClass* fieldWtfPtr;

    void foo()
    {
        WtfClass* localvarWtfPtr;

        WtfMethod((int*)(&fieldWtfPtr)); // Error
        WtfMethod((int*)(&localvarWtfPtr)); // Works
        WtfMethod((int*)(fieldWtfPtr)); // Compiles!!!
    }
};