为啥我不能在发布版本中将 char* 字符串从 C++ 返回到 C#?

Posted

技术标签:

【中文标题】为啥我不能在发布版本中将 char* 字符串从 C++ 返回到 C#?【英文标题】:Why can't I return a char* string from C++ to C# in a Release build?为什么我不能在发布版本中将 char* 字符串从 C++ 返回到 C#? 【发布时间】:2011-06-09 22:33:08 【问题描述】:

我正在尝试从 C# 调用以下琐碎的 C 函数:

SIMPLEDLL_API const char* ReturnString()

    return "Returning a static string!";

使用以下 P/Invoke 声明(带或不带 return 属性,没有区别):

[DllImport("SimpleDll")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string ReturnString();

如果 DLL 是 Debug 版本但在 Release 版本中崩溃 (AccessViolationException),则它可以工作。

我正在调用十几个其他简单函数,这是唯一一个失败的函数(这是其他函数:)

[DllImport("SimpleDll")] public static extern int NextInt();
[DllImport("SimpleDll")] public static extern void SetNextInt(int x);
[DllImport("SimpleDll")] public static extern int AddInts(int a, int b);
[DllImport("SimpleDll")] public static extern int AddFourInts(int a, int b, int c, int d);
[DllImport("SimpleDll")] public static extern double AddDoubles(double x, double y);
[DllImport("SimpleDll")] public static extern IntPtr AddDoublesIndirect(ref double x, ref double y);
[DllImport("SimpleDll")] [return: MarshalAs(UnmanagedType.U1)]
public static extern char CharStringArgument([MarshalAs(UnmanagedType.LPStr)]string s);
[DllImport("SimpleDll")] [return: MarshalAs(UnmanagedType.U2)]
public static extern char WCharStringArgument([MarshalAs(UnmanagedType.LPWStr)]string s);
[DllImport("SimpleDll")] [return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string ReturnWString();
[DllImport("SimpleDll")] [return: MarshalAs(UnmanagedType.BStr)]
public static extern string ReturnBSTR();
[DllImport("SimpleDll")] public static extern System.Drawing.Point MakePoint(int x, int y);
[DllImport("SimpleDll")] public static extern IntPtr MakePointIndirect(int x, int y);
[DllImport("SimpleDll")] public static extern int GetPointY(System.Drawing.Point p);
[DllImport("SimpleDll")] public static extern int GetPointYIndirect(ref System.Drawing.Point pp);
[DllImport("SimpleDll")] public static extern int SumIntegers(ref int firstElem, int size);

【问题讨论】:

您希望 .net 代码如何释放 C++ 运行时分配的内存? 看到这个***.com/questions/5308584/… 在这种情况下,C++ 运行时没有分配内存。 这能回答你的问题吗? PInvoke for C function that returns char * 【参考方案1】:

或者尝试使用

[DllImport("SimpleDll")]
public static extern IntPtr ReturnString();

在您的调用代码中,使用 Marshal 类

string ret = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(PInvoke.ReturnString());

【讨论】:

问题已解决,谢谢!我猜这指向 .NET Framework 中的一些奇怪错误。 @Qwertie,这不是 .NET 框架中的错误。默认情况下,string 编组将创建@​​987654325@ 的托管副本并尝试取消分配char* 使用的C 内存。使用IntPtr 不会自动创建任何对象的托管副本,因此不会自动尝试取消分配内存。我相信您会同意,尝试从字符串文字中取消分配内存不是一个好主意 :) 出于兴趣,您可能希望查看此帖子以获取更多信息:***.com/a/370519/1244630 @Pooven,你的评论应该是一个答案:)...调用CoTaskMemFree 对我来说似乎是非常奇怪的默认行为,因为我在整个职业生涯中都在 Windows 上编程并且从未调用它。 CoTaskMemFree 被记录为 COM API,但如果我想以 COM 方式传递字符串,我会使用 SysAllocString 和 SysFreeString 并作为 BSTR 编组。 “PInvoke.ReturnString()”是否正确?我搜索并搜索了一个 PInvoke 类,但我还没有找到一个。我想也许它应该只是“ReturnString()”。当我这样尝试时,我得到了一个空字符串。 PInvoke 是您定义的一个类,用于保存对本机 dll 的调用。 ReturnString 是您要调用的 dll 中的一个方法!类名只是一个约定。【参考方案2】:

也可以使用自定义 Marshaler 来做到这一点:

class ConstCharPtrMarshaler : ICustomMarshaler

    public object MarshalNativeToManaged(IntPtr pNativeData)
    
        return Marshal.PtrToStringAnsi(pNativeData);
    

    public IntPtr MarshalManagedToNative(object ManagedObj)
    
        return IntPtr.Zero;
    

    public void CleanUpNativeData(IntPtr pNativeData)
    
    

    public void CleanUpManagedData(object ManagedObj)
    
    

    public int GetNativeDataSize()
    
        return IntPtr.Size;
    

    static readonly ConstCharPtrMarshaler instance = new ConstCharPtrMarshaler();

    public static ICustomMarshaler GetInstance(string cookie)
    
        return instance;
    

并像这样使用它:

[DllImport("SimpleDll")]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ConstCharPtrMarshaler))]
public static extern string ReturnString();

【讨论】:

这是要走的路:) 应该标记为答案,因为这是最正确的方式。 如果性能至关重要,那么您应该避免使用ICustomMarshalers。即使使用像这样的基本操作,自定义封送拆收器的开销也会使您的代码运行速度比直接调用相同的方法慢大约五倍。【参考方案3】:

Release 模式下的 C++ 编译器将常量放入受保护的数据页中;试图将其交给 C# 会导致问题。在 Debug 模式下,编译器不会将常量优化到数据页中,因此不存在保护问题。

【讨论】:

数据页被“保护”了怎么办?当然它没有被保护不被读取,并且 .NET Marshaler 不应该写入字符串,对吧?【参考方案4】:

在我的 P/Invoke 经验中,您通常有 2 个参数: 1. 预先分配的缓冲区,以及 2,缓冲区的长度。 Return值为返回数据的长度(不超过原始长度)。

通常,DEBUG 版本不会移动内存。

顺便说一句,您可以传入一个预先分配的 StringBuilder,然后设置 sb.Lenght = C 函数的返回值,这样您的字符串末尾就不会有一堆 \0 空值了。

【讨论】:

【参考方案5】:

我遇到了类似的问题,指定“CharSet”似乎可以解决它。

[DllImport("SimpleDll", CharSet = CharSet.Ansi)]

不知道为什么在调试和发布时会有所不同。

【讨论】:

我尝试了您的建议,但没有奏效。 MarshalAs 属性很可能具有相同的效果。

以上是关于为啥我不能在发布版本中将 char* 字符串从 C++ 返回到 C#?的主要内容,如果未能解决你的问题,请参考以下文章

不能在 C 中将 sscanf() 用于 char 数组

为啥我不能使用 `section .data:` 和 `section .text:` 在 C 中创建一个长度超过 61 个字符的 char 数组? [复制]

为啥从字符串常量转换为 'char*' 在 C 中有效但在 C++ 中无效

为啥将字符串作为文件名而不是 char* 传递时出现错误?

在c中将长动态字符串拆分为字符串数组

在C中将char从大端转换为小端