【问题标题】:Receiving a char* from c++ into c#, and passing it back again从 c++ 接收一个 char* 到 c#,并再次将其传回
【发布时间】:2011-07-15 08:49:25
【问题描述】:

我遇到了第三方 c++ dll 的内存泄漏问题。对于某些调用,dll 为字符串分配内存,将其作为 char* 传递出去,然后期望接收返回的指针,以便它可以取消分配内存。

这里有一些头文件中的 cmets、几个 char* 返回的示例,以及“Release”方法的签名。

(该 dll 被称为 SW_API,它来自贸易结算所 - 如果有人可能已经包装了这个,我很想和他们谈谈!)。

   /* Strings returned by the API are similarly normal nul-terminated C strings.
   * The user should not attempt to change any of the bytes or read past the
   * terminating nul of any returned string. All returned strings must be
   * released using SW_ReleaseString() once the user is finished with the
   * result. Failure to do this will result in memory leaks.
   */

    /**
     * @typedef const char* SW_XML
     * @brief A string containing an XML documents text.
     * @note As with all output strings, returned XML must be freed
     * by the user. See @ref resource.
     * @sa ErrorCodes
     */
    typedef const char* SW_XML;

    const char* STDAPICALLTYPE SW_GetLastErrorSpecifics();

    SW_ErrCode STDAPICALLTYPE SW_DealGetSWML(SW_LoginID           lh,
                                     const char*          swmlVersion,
                                     SW_DealVersionHandle dealVersionHandle,
                                     SW_XML*              resultXML_out);


    void STDAPICALLTYPE SW_ReleaseString(const char* buffer);

尝试从各种来源阅读,我尝试了以下方法:

    // Extern declarations
    [DllImport(sw_api_dll, EntryPoint = "_SW_GetLastErrorSpecifics@0", CharSet = CharSet.Ansi)]
    public static extern IntPtr SW_GetLastErrorSpecifics();

    [DllImport(sw_api_dll, EntryPoint = "_SW_DealGetSWML@16", CharSet = CharSet.Ansi)]
    public static extern int SW_DealGetSWML(int lh, string swmlVersion, string dealVersionHandle, [Out] out IntPtr outputSWML);

    [DllImport(sw_api_dll, EntryPoint = "_SW_ReleaseString@4", CharSet=CharSet.Ansi)]
    public static extern void SW_ReleaseString(IntPtr buffer);


    // Using the externs.
    private static string GetIntPtrStringAndRelease(IntPtr ptr)
    {
        string result = Marshal.PtrToStringAnsi(ptr);
        API.SW_ReleaseString(ptr);
        return result;
    }

    public static int SW_DealGetSWML(int lh, string swmlVersion, string dealVersionHandle, ref string outputSWML)
    {
        IntPtr outputSWML_out = new IntPtr();
        int result = API.SW_DealGetSWML(lh, swmlVersion, dealVersionHandle, out outputSWML_out);

        outputSWML = GetIntPtrStringAndRelease(outputSWML_out);

        return result;
    }

    public static string SW_GetLastErrorSpecifics()
    {
        IntPtr ptr = API.SW_GetLastErrorSpecifics();
        return GetIntPtrStringAndRelease(ptr);
    }

似乎我无法让 API 释放字符串。

现在,这可能只是 API 中的一个错误,但我对此表示怀疑。 更有可能是我做错了什么。

我只知道我的工作集在不断增长。

有问题的公司提供 Java 包装器,但不会扩展到 .Net 包装器。

非常感谢您的任何帮助。

布雷特。

【问题讨论】:

  • 对我来说这看起来像是 API 中的一个错误。如果您返回的数据(即从GetIntPtrStringAndRelease 返回的值很好),那么这应该可以工作。
  • GetIntPtrStringAndRelease 我觉得不错。
  • 我拿回来的价值不错。我几乎希望不是!

标签: c# c++ char marshalling


【解决方案1】:

我是 C# 人,而不是 C++ 人,但在我使用的非托管 C++ DLL 中,我使用 char* 参数将它们编组为 StringBuilders。我确实在某处读到,对于 const char*,最好的选择是 System.String,但对于 char *,最好选择 StringBuilder。但是,如果您需要保留指针以便可以将其发回以释放内存,那么 StringBuilder 可能会更好地工作,因为 System.String 是不可变的?

【讨论】:

  • 感谢@Joel 的回复。我试过这个(非常简单),这个过程就爆炸了。不过,我实施它的方式可能有很多问题。当我得到一点喘息的空间时,我会再次尝试。
【解决方案2】:

我最好的猜测是 IntPtr 不等于字符串的 char*。所以当你调用 SW_ReleaseString 时,你没有提供相同的指针。

您可以做的就是拼凑一个小的 C++CLI 中介。在 C++CLI 中,您将可以直接访问 char*,以及能够使用 Marshal::PtrToString 和托管字符串指针,以及 String^

这就是我认为的样子:

C++/CLI:

String^ GetStringAndRelease(char* ptr)
{
    string result = Marshal::PtrToStringAnsi(ptr);
    SW_ReleaseString(ptr);
    return result;
}

int SW_DealGetSWML(int lh, const char* swmlVersion, const char* dealVersionHandle, String% outputSWML)
{
    char* outputSWML_out;
    int result = SW_DealGetSWML(lh, swmlVersion, dealVersionHandle, outputSWML_out);

    outputSWML = GetStringAndRelease(outputSWML_out);

    return result;
}

String^ SW_GetLastErrorSpecifics()
{
    char* ptr = SW_GetLastErrorSpecifics();
    return GetStringAndRelease(ptr);
}

然后在 C# 中:

[DllImport(your_wrapper_dll, EntryPoint = "_SW_DealGetSWML@16", CharSet = CharSet.Ansi)]
public static extern int SW_DealGetSWML(int lh, string swmlVersion, string dealVersionHandle, [Out] out string outputSWML);


[DllImport(your_wrapper_dll, EntryPoint = "_SW_GetLastErrorSpecifics@0", CharSet = CharSet.Ansi)]
public static extern string SW_GetLastErrorSpecifics();

【讨论】:

  • 如果您正在制作 C++/CLI DLL,则不需要 DllImport。它是一个成熟的 .NET 程序集,您可以像添加对外部 C# 程序集的引用一样添加对它的引用,不需要 DllImports。 (C# ref string 会变成 C++/CLI String^%,顺便说一句。)
  • 太棒了,谢谢@David!我对 C++/CLI 的唯一接触是 C# 和 .NET 之间的桥梁,而且这只是最近的接触。
  • 当我做类似的事情时(很久以前,大约是 .Net 1.1),我不得不编写违背标准 C dll 的代码。我发现编写 C++/COM+ 组件作为包装器比尝试在 C# 中使用 DllImport 更容易。一个 C++/CLI dll 可能是要走的路。
  • 感谢您的回复,先生们。我肯定会在某个时候尝试一下——虽然我的弓上没有 c++ 字符串,但这可能是一个“有趣”的练习 :-)
猜你喜欢
  • 2013-05-03
  • 2018-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-01
  • 1970-01-01
相关资源
最近更新 更多