none
How to marshal a StringNameBlock? RRS feed

  • Question

  • I'm trying to import a COM function that takes a StringNameBlock (SNB).  That's defined, in C++, as OLESTR**.  So what's the safest way to marshal that?  The function in question only needs [in] parameters.  So my code would own the memory.

    If I marshal as string[] as LPArray, would that convert the string instances?  If not, what should I do?  P/Invoke's page just leave it as a IntPtr.  I have no idea how to convert that.


    Will Pittenger

    Saturday, September 13, 2014 2:06 AM

Answers

  • I've never used custom marshalers so take this with a grain of salt. The following code appears to work:

    class SNBMarshaler : ICustomMarshaler {
        private static readonly SNBMarshaler instance = new SNBMarshaler();
    
        public static ICustomMarshaler GetInstance(string cookie) {
            return instance;
        }
    
        public void CleanUpManagedData(object ManagedObj) {
            throw new NotImplementedException();
        }
    
        public void CleanUpNativeData(IntPtr pNativeData) {
            Marshal.FreeCoTaskMem(pNativeData);
        }
    
        public int GetNativeDataSize() {
            return -1;
        }
    
        public IntPtr MarshalManagedToNative(object ManagedObj) {
            return MarshalToSNB((string[])ManagedObj);
        }
    
        public object MarshalNativeToManaged(IntPtr pNativeData) {
            throw new NotImplementedException();
        }
    }
    
    [DllImport...]
    static extern void Foo([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(SNBMarshaler))] string[] strings);
    
    What bugs me about this is that the ICustomMarshaler documentation only mentions interface marshaling and never says anything about custom marshaling for other types. And some methods have almost no documentation, GetNativeDataSize for example.

    Saturday, September 13, 2014 7:11 AM
    Moderator

All replies

  • LPArray produces an array of strings but that array is not NULL terminated so it's probably not suitable to be used as SNB, the callee would have no way to know the number of strings in the array.

    You'll have to marshall the data yourself, I'm not aware of any built in marshaler that deals with SNBs. This should do:

    static unsafe IntPtr MarshalToSNB(string[] strings) {
        int arrayLength = (strings.Length + 1) * sizeof(char *);
        int blockLength = arrayLength;
    
        foreach (var s in strings)
            blockLength += (s.Length + 1) * sizeof(char);
    
        IntPtr ptr = Marshal.AllocCoTaskMem(blockLength);
        char** array = (char**)ptr;
        char* chars = (char*)((byte*)ptr + arrayLength);
    
        foreach (var s in strings) {
            *array++ = chars;
    
            foreach (var c in s)
                *chars++ = c;
    
            *chars++ = '\0';
        }
    
        *array = null;
    
        return ptr;
    }
    
    Be sure to release the allocate memory with Marshal.FreeCoTaskMem after the COM function returns (assuming that it doesn't store the pointer for later use).

    Saturday, September 13, 2014 5:28 AM
    Moderator
  • Would it help to create a custom marshaller?  I know there's a UnmanagedType member for that, but don't know where to start creating one.

    My problem is that that I want to bury marshal step so that callers of the interface don't need to deal with it.  I'm working on an API that provides exported functions and I might not be the only one calling it.  So without an enclosing class, I need some sort of marshaller.


    Will Pittenger

    Saturday, September 13, 2014 6:44 AM
  • I've never used custom marshalers so take this with a grain of salt. The following code appears to work:

    class SNBMarshaler : ICustomMarshaler {
        private static readonly SNBMarshaler instance = new SNBMarshaler();
    
        public static ICustomMarshaler GetInstance(string cookie) {
            return instance;
        }
    
        public void CleanUpManagedData(object ManagedObj) {
            throw new NotImplementedException();
        }
    
        public void CleanUpNativeData(IntPtr pNativeData) {
            Marshal.FreeCoTaskMem(pNativeData);
        }
    
        public int GetNativeDataSize() {
            return -1;
        }
    
        public IntPtr MarshalManagedToNative(object ManagedObj) {
            return MarshalToSNB((string[])ManagedObj);
        }
    
        public object MarshalNativeToManaged(IntPtr pNativeData) {
            throw new NotImplementedException();
        }
    }
    
    [DllImport...]
    static extern void Foo([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(SNBMarshaler))] string[] strings);
    
    What bugs me about this is that the ICustomMarshaler documentation only mentions interface marshaling and never says anything about custom marshaling for other types. And some methods have almost no documentation, GetNativeDataSize for example.

    Saturday, September 13, 2014 7:11 AM
    Moderator
  • I'll probably take a look at it later.  Thanks.

    Will Pittenger

    Saturday, September 13, 2014 9:22 AM