Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Receiving a char* from c++ into c#, and passing it back again

I'm having memory leak issues with a third party c++ dll. For certain calls, the dll allocates memory for the string, passes it out as a char* and then expects to receive that pointer back so that it can de-allocate the memory.

Here are some comments from the header file, a couple of examples of where the char* get returned, and the signature of the "Release" method.

(The dll is called SW_API, it's from a trade clearing house - if anyone has perhaps wrapped this already I'd love to talk to them!).

   /* 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);

Attempting to read up from various sources, I have tried the following:

    // 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);
    }

It seems I just can't get the API to release the strings.

Now, it's possible that this is just a bug in the API, but I doubt it. More likely is I'm doing something funamentally wrong.

All I know is that my working set just keeps on growing.

The company in question provide a Java wrapper but won't stretch to a .Net wrapper.

Any help most gratefully received.

Brett.

like image 978
Brett Avatar asked Nov 05 '22 01:11

Brett


1 Answers

My best guess is that the IntPtr is not equivalent to the char* of your string. So when you call SW_ReleaseString, you're not providing the same pointer.

What you can do, is throw together a little C++CLI intermediary. In C++CLI, and you will have access to the char* directly, as well as being able to use Marshal::PtrToString and managed string pointers, with String^.

Here's what I think that would look like:

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);
}

and then in 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();
like image 164
Tim Avatar answered Nov 12 '22 16:11

Tim