Using UnmanagedType.LPStr is almost always bad idea

Many .NET developers sooner or later will have to do interop with unmanaged world. Let’s say for example that you need to use some Dll written in C++. Passing integers or floats relatively easy, but things are much more complicated with strings. Many C++ Dll still uses ANSI strings, so let’s talk about them. Normally when you read Microsoft documentation you will be slightly confused. Some pages states that you have to use UnmanagedType.LPStr like it stated here: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedtype?view=netframework-4.8#System_Runtime_InteropServices_UnmanagedType_LPStr. And that looks like correct for ANSI strings. Moreover, you can try it and it works just fine.

Some pages stated that you have to use IntPtr: https://docs.microsoft.com/en-us/previous-versions/ms172514(v=vs.100)?redirectedfrom=MSDN and then manually prepare strings to pass to C++ and manually marshal them back to C#. You can try it and it also works just fine.

And as turns out you should never use UnmanagedType.LPStr because by default .NET runtime will attempt to release that string by calling CoTaskMemFree. So, unless you specifically wrote C++ Dll to communicate with .NET it will never work correctly. It works most of the time because memory that passed to CoTaskMemFree is clearly invalid and CoTaskMemFree will just return error that will be ignored. But eventually it will corrupt something.

In my case, I spent 1.5 days debugging error that happens only on Windows 7, and only after repeating a lot of steps every single time. At the end program corrupts heap and cache of critical sections and after that there were long chain of nested crashes that lead to Windows killing application. And the worst part that it looks correct and works just fine on small application. Only after I put infinite loop in my small case, I was able to identify that it is problem in .NET and not in C++ library or somewhere else.

Big thanks to JaredPar  https://stackoverflow.com/users/23283/jaredpar who gave me hint to why it does not work here: https://stackoverflow.com/questions/1223690/pinvoke-error-when-marshalling-struct-with-a-string-in-it. And I confirmed it with WinDbg. I did put breakpoint to CoTaskMemFree just before calling C++ function and I saw this after my C++ function returns:

combase!CoTaskMemFree [onecore\com\combase\class\memapi.cxx @ 444]
mscorwks!FieldMarshaler_StringAnsi::DestroyNativeImpl+0x19
mscorwks! ?? ::FNODOBFM::`string'+0x7a6be
mscorwks!LayoutDestroyNative+0x5a
mscorwks!FmtClassDestroyNative+0x28
mscorwks!StubHelpers::ValueClassMarshaler__ClearNative+0xdc

It confirms that .NET runtime attempts to release that memory, but C++ does not allocate, and it leads to heap corruption and other “goodness”

I hope it helps someone.