【问题标题】:Why does printf work with managed Strings?为什么 printf 可以与托管字符串一起使用?
【发布时间】:2023-03-15 06:00:01
【问题描述】:

我们目前正在挖掘一些非常古老的 C++/CLI 代码(旧语法 .NET Beta),看到这样的东西有点惊讶:

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

程序正确输出

Test-String

我们想知道,为什么可以将托管字符串源传递给printf - 更重要的是:为什么会起作用?我不希望它成为编译器的一些便利功能,因为以下内容不起作用:

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

这会产生一个(以某种方式预期的)编译错误,指出 System::String^ 无法转换为 const char*。所以我唯一真正的解释是,将托管引用传递给 va_list 会超越所有编译器检查,并欺骗本机代码使用指向托管堆的指针。由于System::String 的表示类似于内存中的char-Array,因此printf 可能会起作用。或者编译器转换为pin_ptr 并将其传递给printf

我不希望它自动将String^ 编组为char*,因为这会导致严重的内存泄漏,而没有对实际内存地址的任何引用。

我们知道这不是一个好的解决方案,后来的 Visual Studio 版本引入的各种编组方法提供了一种更好的方法,但了解这里实际发生的事情会非常有趣。

谢谢!

【问题讨论】:

    标签: .net visual-studio-2010 c++-cli marshalling printf


    【解决方案1】:

    我相信是因为编译器把它变成了这个 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)
    

    最后是对printf 的 pinvoke 调用,因此通过为您编组运行时有点偷偷摸摸。您仍处于托管运行时中,并且运行时将在需要时将编组作为服务提供。


    一些注意事项:

    似乎clr!GenericPInvokeCalliHelper 正在 x86 .NET 4 Workstation CLR 上执行此操作。

    以下不起作用

    那是因为那是直接的 C++。它没有机会通过编组,因为它不是必需的。

    【讨论】:

    • 所以也许问题是,为什么编译器会将它变成那个 IL?从什么时候开始printf 成为托管函数?为什么是 P/Invoking 它?为什么它不像其他本地 C++ 函数那样调用它?
    • @CodyGray 我怀疑它是 PInvoking 它,因为编译器首先看到需要通过编组器的东西。稍后我会验证并写一些更全面的东西。
    • 感谢您到目前为止的回答!对我来说,关于托管字符串的 strcpy 和 sprintf 之间唯一真正的区别是 sprintf 采用变量 arg-list 而 strcpy 没有。也许这就是 IL 代码的原因。
    • 再次感谢您的解释!我会坚持这个作为答案。我很确定它是因为可变参数而变成了 IL,而不是因为编译器注意到需要编组 - 否则 strcpy 也可以工作。
    猜你喜欢
    • 1970-01-01
    • 2011-07-24
    • 2014-11-02
    • 1970-01-01
    • 2021-11-17
    • 2017-05-19
    • 1970-01-01
    • 2011-10-29
    • 2015-07-03
    相关资源
    最近更新 更多