Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

StringBuilder object becomes null after C interop call

Discussion in 'Scripting' started by stu_fig, Jun 18, 2018.

  1. stu_fig

    stu_fig

    Joined:
    Jun 18, 2018
    Posts:
    2
    Hi,

    I have a peculiar issue whereby a managed StringBuilder object becomes null after being passed through a C-interop library call. Interestingly, this issue only surfaces when I run this particular bit of logic in a Unity Class Library - other C# apps don't show the issue.

    I have a C function that looks like this:
    void getName(int* nameLength, char* name);

    Similar to many Win32 APIs that return buffered strings, if you pass null for the name parameter, the function can be used to query the name length i.e. you can call the function once, allocate a nameLength sized buffer for name, and then call it again to fill the buffer as intended.

    The C# marshalling for this looks like so:
    Code (CSharp):
    1. [DllImport("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    2. public static extern void getName(ref int nameLength, StringBuilder name);
    From C# I use it like this:
    Code (CSharp):
    1.  
    2. string getNameWrapper()
    3. {
    4.     int size = 0;
    5.     getName(ref size, null);
    6.     StringBuffer name = new StringBuffer(size);
    7.     getName(ref size, name);
    8.     return name.ToString();
    9. }
    This works OK in another C# project but in Unity, after the second call to getName(...), the StringBuilder object is null. Debugging the native side I know that the C part is working correctly. If I use GCHandle.Alloc to get a handle to the StringBuilder object after creation, once the call has returned the handle's Target property is as expected i.e. it points to a StringBuilder object with the correct value in it.
    This only seems to be an issue if the StringBuilder is a local object - if I create it at as a field of the class then it works as expected. It's as if the runtime decided the local reference to the object is no longer needed and nulls it before it's actually ready to be retired.
    Has anyone else seen anything like this? As I say, this behaviour only appears when running with Unity (2017.1.1f1 specifically).

    Many thanks,
    Stu
     
    Last edited: Jun 18, 2018
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I don't think you want to get into marshaling anything beyond a simple Int32 (for integers) or an IntPtr (for strings) across the managed/unmanaged boundary. Your getName function can't do anything meaningful with the contents of a StringBuffer unless you had included unmanaged precisely-equivalent code to work with such a thing.

    The usual pattern is to decode the string to an array of bytes (using whatever encoding your string is in), then pin it and hand it into your native code like so:

    Code (csharp):
    1.         byte[] thestringbytes =  ... get your string into bytes here...
    2.         GCHandle handle = GCHandle.Alloc(thestringbytes, GCHandleType.Pinned);
    3.         try
    4.         {
    5.             // call your native function here:
    6.             myFunc( handle.AddrOfPinnedObject());
    7.         }
    8.         finally
    9.         {
    10.             if (handle.IsAllocated)
    11.             {
    12.                 handle.Free ();
    13.             }
    14.         }
    You can then retrieve the modified-in-place bytes and use Marshal.Copy() or Marshal.PtrToStringAnsi() to get it back into a byte array or a C# string. For getting back the modified length, just return it as a result of the function and you can prototype that directly as returning an int.
     
  3. stu_fig

    stu_fig

    Joined:
    Jun 18, 2018
    Posts:
    2
    Thanks for the reply Kurt-Dekker!

    StringBuilder seems reasonably well covered in the docs as the approach for returning out strings via char*, but I think the manual alloc approach you suggest could well do the job. I'll give that a go ta.
     
    Kurt-Dekker likes this.