C++/CLI marshal_context 本机字符串已损坏

C++/CLI marshal_context native strings corrupted

以下是 C++/CLI 代码被编译成 DLL,并被 C# 应用程序调用:

void Foo(String^ strManaged) {
    marshal_context^ context = gcnew marshal_context();
    FooUnmanaged(context->marshal_as<const char*>(strManaged));
}

FooUnmanaged() 读取 const char*,运行一些大约需要一秒钟的处理,然后再次读取 const char*,例如:

void FooUnmanaged(const char* str) {
    // 1
    Log(str);

    // Process things unrelated to 'str'
    // ...

    // 2
    Log(str);
}

有时,str 的内容在 FooUnmanaged() 中的第一次和第二次读取之间发生变化,就好像该内存已被重新用于其他目的。无论在 FooUnmanaged() 中完成的处理如何,都会发生这种情况,只要它花费大量时间(我猜,足够长的时间以至于 GC 有机会触发)。

如果Foo这样写就不会发生这种情况

void Foo(String^ strManaged) {
    marshal_context^ context = gcnew marshal_context();
    FooUnmanaged(context->marshal_as<const char*>(strManaged));
    delete context; // addded
}

或者那样

void Foo(String^ strManaged) {
    marshal_context context; // created on the stack
    FooUnmanaged(context.marshal_as<const char*>(strManaged));
}

原码有错吗?为什么它不能在 context 的生命周期内正确保留 const char* 的内存?或者context的生命周期可以比我想象的更短(Foo()的范围)吗?

@HansPassant 的回答:

Yes, that's a lifetime issue. .NET uses an aggressive collector, it has no idea that the native code relies on the context. The first snippet requires GC::KeepAlive(context); at the end. The last snippet is how it was meant to be used, stack semantics emulates RAII, the auto-generated Dispose() call keeps it alive in similar fashion. And avoids the temporary memory leak. If FooUnmanaged() stores the passed pointer then you can't use marshal_context.

this article证实了这一点:

The lifetime of [local variables] can depend on the way the program was built. In debug builds, a local variable lasts for as long as the method is on the stack. In release builds, the JIT is able to look at the program structure to work out the last point within the execution that a variable can be used by the method and will discard it when it is no longer required.