无法转换指针字段,但可以转换在托管方法中定义的相同指针 类
Cannot cast pointer field while can cast same pointer defined within method in managed classes
我有 WtfClass 的非托管对象。
class WtfClass { };
而且我还管理了 class,它使用指向该对象的指针。
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() 中定义的相同指针。如果使 MyClass 不受管理,它在任何情况下都有效。
最好看截图:
- https://ibin.co/2iOcN1ooaC7A.png [使用 ref-bad.png]
- https://ibin.co/2iOcYtP84H0e.png [使用 ref-good.png]
- ibin.co/2iOcjCCc2gQe.png [without ref.png](抱歉没有足够的声誉粘贴超过 2 个链接)
当然我可以有这样的解决方法,但我想了解为什么会这样:
void MyClass::MyMethod()
{
WtfClass* pWorkAround = pWtfStruct; //not required in this case
WtfMethod((void*)(&pWorkAround));
}
当我尝试重新创建它时,编译器生成了以下错误:
error C2440: 'type cast' : cannot convert from 'cli::interior_ptr<CWtfClass*>' to 'LPVOID *'
我认为这里发生的事情是某种允许托管 类 拥有非托管成员的魔法。 MSDN documentation for cli::interior_ptr 描述了正在发生的事情 - 基本上这用于允许托管对象更改其在托管堆中的内存地址,这会在本机指针发挥作用时导致问题。
首先将成员分配给变量的原因很可能是因为它具有到模板参数的隐式转换,但由于它是托管类型,编译器不允许您获取变量(因为垃圾收集器可以根据需要在内存中移动它)。
您问题中的解决方法可能是修复此编译器错误的最佳方法。
好的,总结一下,没有重复的字段和局部变量名称:
ref class MyClass
{
WtfClass* fieldWtfPtr;
void foo()
{
WtfClass* localvarWtfPtr;
WtfMethod((int*)(&fieldWtfPtr)); // Error
WtfMethod((int*)(&localvarWtfPtr)); // Works
}
};
附带问题:&fieldWtfPtr
是 WtfClass**
类型,一个双指针。您的意思是将其转换为 int**
,也是双指针吗?或者您是否想将 fieldWtfPtr
作为 WtfClass*
单指针并将其转换为 int*
单指针?
这就是您收到错误的原因:MyClass
是一个托管对象。垃圾编译器可以随时移动它,而不用告诉你。因此,它在内存中的位置可以随时更改。因此,当您尝试获取 class 字段的地址时,它是无效的,因为该字段的地址可以随时更改!
为什么其他因素让它起作用:
- 局部变量存储在栈中,栈不会被垃圾收集器移动,所以取局部变量的地址是有效的。
- 如果删除
ref
,则 MyClass
不再是托管对象,因此垃圾收集器不会移动它,所以现在它的字段地址不会改变情不自禁。
对于这种情况,最简单的解决方法是使用局部临时变量。
void foo()
{
WtfClass* localCopyWtfPtr = this->fieldWtfPtr;
WtfMethod((int*)(&localCopyWtfPtr)); // Works
// If WtfMethod changed the data, write it back.
this->fieldWtfPtr = localCopyWtfPtr;
}
David 回答了为什么 会发生这种情况,并针对您的情况提出了解决方法。
我将在这里 post 一个不同的解决方案:您可以 固定 您的托管对象以告诉 GC 不要移动它。最轻量级的方法是通过 pin_ptr
(GC 甚至不知道您固定了某些东西,除非它在集合中间偶然发现您的代码)。只要它留在范围内,托管对象就会 固定 并且不会移动。最好不要固定太久,但这会让你得到一个指向保证不会移动的托管内存块的指针 - 当你想避免复制东西时它会很有帮助。
操作方法如下:
pin_ptr<WtfClass*> pin(&pWtfStruct);
WtfMethod(pin);
pin
就像 WtfClass**
.
关于 David Yaw 的附带问题。
我在使用一些WINAPI函数时遇到了这个问题。
IAudioEndpointVolume* pWtfVolume = NULL;
pDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void**)&pWtfVolume);
pWtfVolume->SetMute(BST_CHECKED, pGuidMyContext);
而且只有当我通过 &pWtfVolume 时它才有效。具有讽刺意味的是,您可以在没有“&”的情况下传递参数,只是 pFieldVolume 和编译器会说 OKAY,但接口 IAudioEndpointVolume 将不起作用。
看看这个:
ref class MyClass
{
WtfClass* fieldWtfPtr;
void foo()
{
WtfClass* localvarWtfPtr;
WtfMethod((int*)(&fieldWtfPtr)); // Error
WtfMethod((int*)(&localvarWtfPtr)); // Works
WtfMethod((int*)(fieldWtfPtr)); // Compiles!!!
}
};
我有 WtfClass 的非托管对象。
class WtfClass { };
而且我还管理了 class,它使用指向该对象的指针。
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() 中定义的相同指针。如果使 MyClass 不受管理,它在任何情况下都有效。
最好看截图:
- https://ibin.co/2iOcN1ooaC7A.png [使用 ref-bad.png]
- https://ibin.co/2iOcYtP84H0e.png [使用 ref-good.png]
- ibin.co/2iOcjCCc2gQe.png [without ref.png](抱歉没有足够的声誉粘贴超过 2 个链接)
当然我可以有这样的解决方法,但我想了解为什么会这样:
void MyClass::MyMethod()
{
WtfClass* pWorkAround = pWtfStruct; //not required in this case
WtfMethod((void*)(&pWorkAround));
}
当我尝试重新创建它时,编译器生成了以下错误:
error C2440: 'type cast' : cannot convert from 'cli::interior_ptr<CWtfClass*>' to 'LPVOID *'
我认为这里发生的事情是某种允许托管 类 拥有非托管成员的魔法。 MSDN documentation for cli::interior_ptr 描述了正在发生的事情 - 基本上这用于允许托管对象更改其在托管堆中的内存地址,这会在本机指针发挥作用时导致问题。
首先将成员分配给变量的原因很可能是因为它具有到模板参数的隐式转换,但由于它是托管类型,编译器不允许您获取变量(因为垃圾收集器可以根据需要在内存中移动它)。
您问题中的解决方法可能是修复此编译器错误的最佳方法。
好的,总结一下,没有重复的字段和局部变量名称:
ref class MyClass
{
WtfClass* fieldWtfPtr;
void foo()
{
WtfClass* localvarWtfPtr;
WtfMethod((int*)(&fieldWtfPtr)); // Error
WtfMethod((int*)(&localvarWtfPtr)); // Works
}
};
附带问题:&fieldWtfPtr
是 WtfClass**
类型,一个双指针。您的意思是将其转换为 int**
,也是双指针吗?或者您是否想将 fieldWtfPtr
作为 WtfClass*
单指针并将其转换为 int*
单指针?
这就是您收到错误的原因:MyClass
是一个托管对象。垃圾编译器可以随时移动它,而不用告诉你。因此,它在内存中的位置可以随时更改。因此,当您尝试获取 class 字段的地址时,它是无效的,因为该字段的地址可以随时更改!
为什么其他因素让它起作用:
- 局部变量存储在栈中,栈不会被垃圾收集器移动,所以取局部变量的地址是有效的。
- 如果删除
ref
,则MyClass
不再是托管对象,因此垃圾收集器不会移动它,所以现在它的字段地址不会改变情不自禁。
对于这种情况,最简单的解决方法是使用局部临时变量。
void foo()
{
WtfClass* localCopyWtfPtr = this->fieldWtfPtr;
WtfMethod((int*)(&localCopyWtfPtr)); // Works
// If WtfMethod changed the data, write it back.
this->fieldWtfPtr = localCopyWtfPtr;
}
David 回答了为什么 会发生这种情况,并针对您的情况提出了解决方法。
我将在这里 post 一个不同的解决方案:您可以 固定 您的托管对象以告诉 GC 不要移动它。最轻量级的方法是通过 pin_ptr
(GC 甚至不知道您固定了某些东西,除非它在集合中间偶然发现您的代码)。只要它留在范围内,托管对象就会 固定 并且不会移动。最好不要固定太久,但这会让你得到一个指向保证不会移动的托管内存块的指针 - 当你想避免复制东西时它会很有帮助。
操作方法如下:
pin_ptr<WtfClass*> pin(&pWtfStruct);
WtfMethod(pin);
pin
就像 WtfClass**
.
关于 David Yaw 的附带问题。 我在使用一些WINAPI函数时遇到了这个问题。
IAudioEndpointVolume* pWtfVolume = NULL;
pDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void**)&pWtfVolume);
pWtfVolume->SetMute(BST_CHECKED, pGuidMyContext);
而且只有当我通过 &pWtfVolume 时它才有效。具有讽刺意味的是,您可以在没有“&”的情况下传递参数,只是 pFieldVolume 和编译器会说 OKAY,但接口 IAudioEndpointVolume 将不起作用。
看看这个:
ref class MyClass
{
WtfClass* fieldWtfPtr;
void foo()
{
WtfClass* localvarWtfPtr;
WtfMethod((int*)(&fieldWtfPtr)); // Error
WtfMethod((int*)(&localvarWtfPtr)); // Works
WtfMethod((int*)(fieldWtfPtr)); // Compiles!!!
}
};