如果我理解正确,当使用具有非blitable值的结构时,非托管内存中的结构数据将被复制到托管内存中(基本上具有相同的结构两次)。
此外,如果我没错,IntPtr变量存储在托管内存中,但它们指向的数据位于非托管内存中。
现在假设我有一个委托方法,它由c ++函数调用并接收一个结构作为ref,并且处理程序将类似于:
private void _handler(ref MyStruct p){}
现在api说我不应该保留对结构的引用,因为内存可能被回收并用于进一步调用,但是因为非托管结构被复制到托管内存中,并且在我将它分配给局部变量之后它是再次复制(因为它是一个结构!)如果非托管内存被释放或重写,我根本不应该有任何问题。 但是,如果struct包含一个IntPtr变量,我可能会保存指针的副本而不是它指向的数据的副本,所以如果我尝试从我保存的struct中访问IntPtr数据,我可能会遇到一些内存错误,这是正确的?
最后一个问题是,对托管内存中的结构进行更改也会影响非托管内存结构,因为它是由ref传递的,它隐含地表示IN / OUT,所以在事件处理程序调用结束后,更改为托管内存也将在非托管内存中生成?
答案 0 :(得分:0)
您的理解基本上是正确的。除去blittabilyy的细节,根问题是传递给回调的内存的所有权。通常,回调调用者和回调本身之间的契约是内存由调用者拥有,并且仅在回调本身的持续时间内有效。 (其他合同也是可能的 - 例如,调用者可以将内存的所有权转移到回调,在这种情况下,回调负责在完成后释放内存。)
在您引用的文档中,API告诉您不要保留对内存的引用,这意味着您很可能处于调用者拥有该结构的标准情况。这意味着结构的内容和该结构指向的所有内容在调用回调期间都应该有效,但在回调完成后不能依赖它。
将结构复制到本地不会有问题,因为在回调完成之前不应释放本机内存,因此在执行回调期间保留尽可能多的副本应该没问题。有问题的是将结构的副本保存到某个位置,例如静态字段,它将在您的回调返回后保持活动状态。
由于结构是隐式进/出的,因此您对其进行的修改也将反映在调用的本机端。无论结构是否仅包含blittable数据,都是如此。如果结构包含非blittable数据,那么当代码完成时,CLR会将对结构的更改编组回原生副本。
请注意,仅仅因为您可以修改本机结构并不意味着API合约希望您这样做。 API签名通常包含指向C ++中结构的指针,以避免在进行调用时复制整个结构,而不是允许修改结构。