Why does printf work with managed Strings?

2020-03-19 06:22发布

问题:

we are currently digging through some really old C++/CLI-Code (Old Syntax .NET Beta) and were a bit surprised to see something like this:

System::String ^source("Test-String");
printf("%s", source);

The program correctly outputs

Test-String

We are wondering, why is it possible to pass the managed string source to printf - and more importantly: Why does it work? I don't expect it to be some convenience-feature by the compiler because the following doesn't work:

System::String ^source("Test-String");
char pDest[256];
strcpy(pDest, source);

This produces a (somehow expected) compiling error saying that System::String^ can't be converted to const char*. So my only real explanation is that passing a managed reference to a va_list surpasses all compiler-checks and tricks the native code into using a pointer into the managed heap. Since System::String is represented similar to a char-Array in memory, printf may work. Or the compiler converts to a pin_ptr and passes that to printf.

I don't expect it to automatically marshal the String^ to char*, because that would result in a bad memory leak without any reference to the actual memory address.

We know that this isn't a good solution and the various marshalling methods introduced by the later Visual Studio-Versions provide a way better approach but it would be very interesting to understand what is actually happening here.

Thanks!

回答1:

I believe it is because the compiler is turning it into this IL:

call vararg int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) printf(int8 modopt([mscorlib]System.Runtime.CompilerServices.IsSignUnspecifiedByte) modopt([mscorlib]System.Runtime.CompilerServices.IsConst)*, ..., string)

Which ends up as a pinvoke call to printf, so the runtime is being a little sneaky by marshalling it for you. You are still in a managed runtime, and the runtime will provide marhsalling as a service when it's needed.


Some notes:

It seems that clr!GenericPInvokeCalliHelper is doing this lifting on the x86 .NET 4 Workstation CLR.

the following doesn't work

That's because that is straight C++. It has no chance to go through marshalling because it isn't needed.