locked
Help - trying to marshall c++ char* to a vb.net string BYREF RRS feed

  • Question

  • hi,


    the scenario - i have a native c++ dll that creates and initializes a string, which is a char array.
    1. char pRet[1024];
    2. memset(pRet, 0, 1024);
    3. strcpy(pRet, "change me!");

    the c++ dll then fires a callback function, and the callback function must change the value of the string. however, the callback function is in a VB.NET app.

    the callback function is defined in the c++ dll as :
    1. typedef VOID (WINAPI *CALLBACK_ROUTINE)(char* pRet);

    so the delegate type that i have created in the VB.NET app is:

    1. Public Delegate Sub CALLBACK_ROUTINE(ByVal pRet As IntPtr)

    then the actual VB.NET callback function itself is like this:
    1. Private Sub pCallback(ByRef pRet As IntPtr)
    2. pRet = Marshal.StringToCoTaskMemAnsi("response from vb.net app !")
    3.  
    4. ' do not call CoTaskMemFree because the C++ dll created the char array and will therefore be responsible for freeing the memory !
    5. End Sub

    however when the C++ dll has finished calling this VB.NET callback, pRet still says 'change me !'.

    so how do i define the delegate type (assuming what i have done is wrong) and how do i modify the value of the string in the VB.NET callback function, such that the c++ dll 'sees' this change ? in other words how do i write the code so that the C++ dll and the VB.NET app share and access the same string buffer ?

    i did post a similar question last week but i have deleted it because it was badly explained. any help very much appreciated.
    Monday, August 9, 2010 9:57 AM

Answers

  • The String object in .NET is supposed to be immutable -- always.

    For an interop scenario, you should declare the parameter:

    ByVal sb As StringBuilder

    When you call the function, be sure that a StringBuilder of sufficient capacity for both the input (if applicable) and output values is allocated.  Load the StringBuilder with the input string (if applicable) prior to calling your external function.

    Dim sb As StringBuilder
    Dim s As String
    sb = New StringBuilder(1023) 'This is big enough hold 1023 characters PLUS a null terminator
    sb.Append("Initial text for StringBuilder here.")
    
    '
    'Put code to call the external function.
    '
    
    s = sb.ToString()
    'Use result in s here.
    
    

    See the example Fixed-Length String Buffers here: http://msdn.microsoft.com/en-us/library/s9ts558h.aspx

    • Marked as answer by unclepauly2 Tuesday, August 10, 2010 8:54 AM
    Tuesday, August 10, 2010 12:06 AM
  • hi folks - well i managed to bundle something together based on your comments ;)

    the c++ dll actually uses a third party lib file, and it is this lib file that actually calls the callback.  there are a few other parameters to the callback, but this is the only one i was having problems with. yes you are right, the c++ dll creates the char array and passes it to the callback to be filled in - its the third party lib that does this.  the callback used to be in the c++ dll itslef so there were no problems, its only now we have put the callback in the vb.net that things have become tricky.

    i tried using StringBuilder as the vb.net delegate param, and this was working, but the MaxCapacity was always 0, so i could not write even one byte of data to it. so i imagine the third party lib is doing a memset(NULL) on the char array, even though i write "change me!" to it...hence the StringBuilder is seeing a zero length string. i dont have source code for the third party lib.

    so my solution was to create (put back!) the callback function in the c++ dll itself, and let this callback fire the vb.net callback. having the callback in the c++ dll allows me to create another char array of the same size and fill the memory with garbage (i just used # symbol). then i fire the vb.net callback and this time the StringBuilder gets a string full of #'s, but at least its MaxCapacity is 1024. then i just need to replace the #'s with NULLs, then write the outgoing string:

    Private Sub pCallbackFunc(ByVal sbOutgoing As StringBuilder)
        sbOutgoing.Replace("#", Chr(0))
        sbOutgoing.Remove(0, sbOutgoing.Capacity)
        sbOutgoing.Insert(0, "here is a response !")
     End Sub
    
    seems to work just right although its a bit of a fudge ! thanks for your help and input.
    • Marked as answer by unclepauly2 Tuesday, August 10, 2010 11:16 AM
    Tuesday, August 10, 2010 8:54 AM

All replies

  • The String object in .NET is supposed to be immutable -- always.

    For an interop scenario, you should declare the parameter:

    ByVal sb As StringBuilder

    When you call the function, be sure that a StringBuilder of sufficient capacity for both the input (if applicable) and output values is allocated.  Load the StringBuilder with the input string (if applicable) prior to calling your external function.

    Dim sb As StringBuilder
    Dim s As String
    sb = New StringBuilder(1023) 'This is big enough hold 1023 characters PLUS a null terminator
    sb.Append("Initial text for StringBuilder here.")
    
    '
    'Put code to call the external function.
    '
    
    s = sb.ToString()
    'Use result in s here.
    
    

    See the example Fixed-Length String Buffers here: http://msdn.microsoft.com/en-us/library/s9ts558h.aspx

    • Marked as answer by unclepauly2 Tuesday, August 10, 2010 8:54 AM
    Tuesday, August 10, 2010 12:06 AM
  • The problem is that he's creating the buffer in C++ and sending to a VB function to fill it in...probably not the simplest way to to things.  I don't have time at the moment to throw together a dll and VB project to play with but my guess would be that this would work:

     

    typedef LPTSTR (WINAPI *CALLBACK_ROUTINE)(int maxLength);

    and his delegate more like this:

    Public Delegate Function CALLBACK_ROUTINE(maxLength as Int ) as String


    Then in the VB function create a StringBuilder set to maxLength and fill it to as much as maxLength-1 and simply return it.  My guess is that the default marshalling behavior will take care of the rest.  Once the callback is complete, back in his C++ dll, he can just copy the string and use it.

    Again, not tested but I'm in a hurry :)

    ShaneB

    Tuesday, August 10, 2010 2:05 AM
  • > The problem is that he's creating the buffer in C++ and sending to a VB function to fill it in...

    Looking at this again, I think the problem might be in the approach on the C side.  I don't think you can validly modify pRet to point elsewhere because it is an array.  Perhaps you need something along these lines:

    char pRet[1024];
    memset(pRet, 0, 1024);
    strcpy(pRet, "change me!");
    char *pstr = pRet;
    routine(&pstr);
    // Examine pstr, then CoTaskMemFree when done with it.

    The following:

    typedef VOID (WINAPI *CALLBACK_ROUTINE)(char* pRet);

    should be:

    typedef VOID (WINAPI *CALLBACK_ROUTINE)(char** pRet);

    There is probably still a way to do this with a StringBuilder to actually get the data into the fixed-size array (pRet) instead of changing the pointer (pstr).  Originally I wasn't paying attention to the part where you actually wanted to deal with the the pointers and CoTaskMemFree.

     

    Tuesday, August 10, 2010 2:23 AM
  • hi folks - well i managed to bundle something together based on your comments ;)

    the c++ dll actually uses a third party lib file, and it is this lib file that actually calls the callback.  there are a few other parameters to the callback, but this is the only one i was having problems with. yes you are right, the c++ dll creates the char array and passes it to the callback to be filled in - its the third party lib that does this.  the callback used to be in the c++ dll itslef so there were no problems, its only now we have put the callback in the vb.net that things have become tricky.

    i tried using StringBuilder as the vb.net delegate param, and this was working, but the MaxCapacity was always 0, so i could not write even one byte of data to it. so i imagine the third party lib is doing a memset(NULL) on the char array, even though i write "change me!" to it...hence the StringBuilder is seeing a zero length string. i dont have source code for the third party lib.

    so my solution was to create (put back!) the callback function in the c++ dll itself, and let this callback fire the vb.net callback. having the callback in the c++ dll allows me to create another char array of the same size and fill the memory with garbage (i just used # symbol). then i fire the vb.net callback and this time the StringBuilder gets a string full of #'s, but at least its MaxCapacity is 1024. then i just need to replace the #'s with NULLs, then write the outgoing string:

    Private Sub pCallbackFunc(ByVal sbOutgoing As StringBuilder)
        sbOutgoing.Replace("#", Chr(0))
        sbOutgoing.Remove(0, sbOutgoing.Capacity)
        sbOutgoing.Insert(0, "here is a response !")
     End Sub
    
    seems to work just right although its a bit of a fudge ! thanks for your help and input.
    • Marked as answer by unclepauly2 Tuesday, August 10, 2010 11:16 AM
    Tuesday, August 10, 2010 8:54 AM