Marshalling a char** in C#

前端 未结 2 444
别跟我提以往
别跟我提以往 2021-02-08 12:34

I am interfacing with code that takes a char** (that is, a pointer to a string):

int DoSomething(Whatever* handle, char** error);

相关标签:
2条回答
  • 2021-02-08 12:52

    For reference, here is code that compiles (but, not tested yet, working on that next tested, works 100%) that does what I need. If anyone can do better, that's what I'm after :D

    public static unsafe int DoSomething(IntPtr handle, out string error) {
        byte* buff;
    
        int ret = DoSomething(handle, &buff);
    
        if(buff != null) {
            int i = 0;
    
            //count the number of bytes in the error message
            while (buff[++i] != 0) ;
    
            //allocate a managed array to store the data
            byte[] tmp = new byte[i];
    
            //(Marshal only works with IntPtrs)
            IntPtr errPtr = new IntPtr(buff);
    
            //copy the unmanaged array over
            Marshal.Copy(buff, tmp, 0, i);
    
            //get the string from the managed array
            error = UTF8Encoding.UTF8.GetString(buff);
    
            //free the unmanaged array
            //omitted, since it's not important
    
            //take a shot of whiskey
        } else {
            error = "";
        }
    
        return ret;
    }
    

    Edit: fixed the logic in the while loop, it had an off by one error.

    0 讨论(0)
  • 2021-02-08 13:04

    You should just be able to use a ref string and have the runtime default marshaller take care of this conversion for you. You can hint the char width on the parameter with [MarshalAs(UnmanagedType.LPStr)] to make sure that you are using 8-bit characters.

    Since you have a special deallocation method to call, you'll need to keep the pointer, like you've already shown in your question's example.

    Here's how I'd write it:

    [DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)] 
    private static unsafe extern int DoSomething(
        MySafeHandle handle, void** error); // byte** should work, too, I'm just lazy
    

    Then you can get a string:

    var errorMsg = Marshal.PtrToStringAnsi(new IntPtr(*error));
    

    And cleanup:

    [DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)] 
    private static extern int FreeMyMemory(IntPtr h);
    
    // ...
    
    FreeMyMemory(new IntPtr(error));
    

    And now we have the marshalled error, so just return it.

    return errorMsg;
    

    Also note the MySafeHandle type, which would inherit from System.Runtime.InteropServices.SafeHandle. While not strictly needed (you can use IntPtr), it gives you a better handle management when interoping with native code. Read about it here: http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle.aspx.

    0 讨论(0)
提交回复
热议问题