none
Platform Invoke: Marshalling char ** Data Type RRS feed

  • Question

  • I'm using platform invoke to call the following C function

    WBXML_DECLARE(WBXMLError) wbxml_conv_xml2wbxml_withlen(WB_UTINY  *xml,
                                                           WB_ULONG   xml_len,
                                                           WB_UTINY **wbxml,
                                                           WB_ULONG  *wbxml_len,
                                                           WBXMLGenWBXMLParams *params);

    WB_UTINY is defined as

    #define WB_UTINY unsigned char;

    The parameter wbxml and wbxml_len are output parameters. I know I'm supposed to use a StringBuilder as the output type for a char * output parameter, but I am at a loss trying to find out how to deal with the extra level of indirection of the unsigned char **

    Can anybody provide with some insight into this issue.

    Thanks,
    Greg Underwood.
    Thursday, April 17, 2008 6:51 PM

Answers

  • If these really are strings, you'd pass an array of initialized StringBuilders.  It doesn't smell like a string, 'C' doesn't have a "byte" type so 'C" programmer declare them as "unsigned byte".  WB_UTINY sounds like a byte, not a character.

    You have a far bigger problem if wbxml is truly an [out] parameter.  That means that wbxml_con_xml2wbxml_withlen() is allocating the memory for the array and strings.  A 'C' program would invariably use malloc() to allocate the memory.  The client, your .NET program, would have to call free() to release the mermoy.  It can't.  .NET requires memory to be allocated with CoTaskMemAlloc() so it can release it with CoTaskMemFree().

    You'll have to tell us more about the function's implementation to make an accurate call.
    Thursday, April 17, 2008 11:33 PM
    Moderator

All replies

  • If these really are strings, you'd pass an array of initialized StringBuilders.  It doesn't smell like a string, 'C' doesn't have a "byte" type so 'C" programmer declare them as "unsigned byte".  WB_UTINY sounds like a byte, not a character.

    You have a far bigger problem if wbxml is truly an [out] parameter.  That means that wbxml_con_xml2wbxml_withlen() is allocating the memory for the array and strings.  A 'C' program would invariably use malloc() to allocate the memory.  The client, your .NET program, would have to call free() to release the mermoy.  It can't.  .NET requires memory to be allocated with CoTaskMemAlloc() so it can release it with CoTaskMemFree().

    You'll have to tell us more about the function's implementation to make an accurate call.
    Thursday, April 17, 2008 11:33 PM
    Moderator
  • Thank you for the reply.

    The library I'm attempting to use is an open source library called libwbxml which is available at http://libwbxml.aymerick.com/ I'm using the latest stable build 0.92

    "It doesn't smell like a string, 'C' doesn't have a "byte" type so 'C" programmer declare them as "unsigned byte".  WB_UTINY sounds like a byte, not a character."

    I'm sure it is a C string that it is outputting, just with an extra level of indirection, it's a pointer to a unsigned char pointer. The type of unsigned char is used to support char values from 0-255 as opposed to 0-127, which is all that can be represented with a signed char (or byte for that matter).

    As I mentioned in my last post the define statement for WB_UTINY is:

    Code Snippet

    #define WB_UTINY unsigned char


    "a far bigger problem if wbxml is truly an [out] parameter.  That means that wbxml_con_xml2wbxml_withlen() is allocating the memory for the array and strings.  A 'C' program would invariably use malloc() to allocate the memory.  The client, your .NET program, would have to call free() to release the mermoy.  It can't.  .NET requires memory to be allocated with CoTaskMemAlloc() so it can release it with CoTaskMemFree().You'll have to tell us more about the function's implementation to make an accurate call."

    This is the definition of the wbxml_conv_xml2wbxml_withlen function in the header file (including comments):

    Code Snippet

    /**
     * @brief Convert XML to WBXML
     * @param xml       [in] XML Document to convert
     * @param xml_len   [in] Length of XML Document
     * @param wbxml     [out] Resulting WBXML Document
     * @param wbxml_len [out] Length of resulting WBXML Document
     * @param params    [in] Parameters (if NULL, default values are used)
     * @return WBXML_OK if convertion succeeded, an Error Code otherwise
     */
    WBXML_DECLARE(WBXMLError) wbxml_conv_xml2wbxml_withlen(WB_UTINY  *xml,
                                                           WB_ULONG   xml_len,
                                                           WB_UTINY **wbxml,
                                                           WB_ULONG  *wbxml_len,
                                                           WBXMLGenWBXMLParams *params);


    I traced the call through several functions to find where the memory allocation is taking place and this is what I found:

    Code Snippet


    /**
     * @brief Build WBXML Result
     * @param encoder [in] The WBXML Encoder
     * @param wbxml [out] Resulting WBXML document
     * @param wbxml_len [out] The resulting WBXML document length
     * @return WBXML_OK if built is OK, an error code otherwise
     * @note WBXML Header = version publicid charset length
     */
    static WBXMLError wbxml_build_result(WBXMLEncoder *encoder, WB_UTINY **wbxml, WB_ULONG *wbxml_len)



    /*...... Skipping irrelevant lines of code .... */


    /* Create Result Buffer */
        *wbxml = (WB_UTINY *) wbxml_malloc(*wbxml_len * sizeof(WB_UTINY));
        if (*wbxml == NULL) {
            if (encoder->flow_mode == FALSE)
                wbxml_buffer_destroy(header);
            
            *wbxml_len = 0;
            return WBXML_ERROR_NOT_ENOUGH_MEMORY;
        }

        /* Copy WBXML Header */
        memcpy(*wbxml, wbxml_buffer_get_cstr(header), wbxml_buffer_len(header));

        /* Copy WBXML Buffer */
        memcpy(*wbxml + wbxml_buffer_len(header), wbxml_buffer_get_cstr(encoder->output), wbxml_buffer_len(encoder->output));

        if (encoder->flow_mode == FALSE)
            wbxml_buffer_destroy(header);

        return WBXML_OK;


    And the actual memory allocation is taking place in the wbmxl_malloc function which is defined as:

    Code Snippet

    WBXML_DECLARE(void *) wbxml_malloc(size_t size)
    {
    #ifdef WBXML_USE_LEAKTRACKER
        return lt_malloc(size);
    #else
        return malloc(size);
    #endif
    }


    I hope this gives you enough information to help me.

    Thank You,
    Greg Underwood


    Monday, April 21, 2008 9:47 PM
  • Yup, malloc().  If the DLL doesn't provide an export that lets you release the memory with free(), you can't use P/Invoke.  You'll need a wrapper.  Google for "C++/CLI wrapper".
    Monday, April 21, 2008 10:48 PM
    Moderator
  • Thank you for the information and your time.

    Greg Underwood
    Monday, April 21, 2008 10:52 PM
  • You might the following useful if you choose to go the C++/CLI way:

    http://blogs.microsoft.co.il/blogs/sasha/archive/2008/02/16/net-to-c-bridge.aspx

     

    Tuesday, April 22, 2008 10:55 AM
  • Hi,
    Just in case the issue is still unresolved.

    You don't handle properly the pointer returned from umanaged code.

    To make the post short I've omitted the declaration of the enums (you may copy it from wbxml.h file or substitute with constants).

    [DllImport("libwbxml2.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
    public static extern WBXMLError wbxml_conv_xml2wbxml_withlen(string xml, int xml_size, out IntPtr wbxml, out uint wbxml_size, ref WBXMLConvXML2WBXMLParams parms);

    IntPtr wbxml;
    uint wbxml_size;

    WBXMLConvXML2WBXMLParams parms = new WBXMLConvXML2WBXMLParams();
    parms.wbxml_version = WBXMLVersion.WBXML_VERSION_13;
    parms.keep_ignorable_ws = 0;
    parms.use_strtbl = 1;
    parms.produce_anonymous = 1;

    WBXMLError retval = wbxml_conv_xml2wbxml_withlen(xml, xml.Length, out wbxml, out wbxml_size, ref parms);

    if (retval == WBXMLError.WBXML_OK)
    {
    byte[] arrWbxml = new byte[wbxml_size];
    Marshal
    .Copy(wbxml, arrWbxml, 0, (int)wbxml_size);
    return arrWbxml;
    }
    else {
    throw new Exception(String.Format("Failed to parse xml: {0}", retval));
    }

    Hope this helps.


    Senior Software Architect
    Sunday, October 19, 2008 7:11 PM
  • That worked for me! Thank you very very much!
    Saturday, August 29, 2009 6:50 PM