Nilo Paim
Nilo Paim

Reputation: 447

C# Marshaling - Get contents of C char** and convert to byte[]

I have a C DLL with a function that returns a "char**". I need to pass it to a MemoryStream under C#.

How to marshall this?

I've already tried

[DllImport("ctf.dll")]
    public static extern long tts_sintetizaTexto_mm(long canal, string text, char* BuffVoice);

When I try to use BuffVoice on MemoryStream it produces compilation error...

TIA, Nilo - Brazil

Upvotes: 2

Views: 1726

Answers (2)

Jonathan Dickinson
Jonathan Dickinson

Reputation: 9248

Remember char in C is actually an unsigned byte. This means that it's actually byte** - which itself can be one of two things: either a pointer to an array (byte[]*) or an array of arrays (byte[][]).

Pointer to an Array

This scenario is more likely.

[DllImport("ctf.dll")]
public static extern long tts_sintetizaTexto_mm(long canal, string text, ref byte[] BuffVoice);

If this is a Microsoft API there are overwhelming odds that this is how it works:

[DllImport("ctf.dll")]
private static extern long tts_sintetizaTexto_mm(long canal, string text, ref byte[] BuffVoice);

public byte[] SintetizaText(long canal, string text)
{
    // The first call requests the size of the memory that we need to allocate.
    byte[] buffer = null;
    var size = tts_sintetizaTexto_mm(canal, text, ref buffer);
    if (size <= 0) throw new Exception();

    // The second call actually does the work.
    buffer = new byte[(int)size];
    size = tts_sintetizaTexto_mm(canal, text, ref buffer);

    // size either holds a new size, or the result code.
    // size: Array.Resize(ref buffer, size);
    // result code: if (size != 0) throw new Exception();

    return buffer;
}

Array of Arrays

[DllImport("ctf.dll")]
public static extern long tts_sintetizaTexto_mm(long canal, string text, ref byte[][] BuffVoice);

Upvotes: 1

comdiv
comdiv

Reputation: 951

Best newby article about interop mapping: http://www.mono-project.com/docs/advanced/pinvoke/ (while it's about Mono it's about .NET same time while they are compatible). Most common point is to marshall "in" strings directly from System.String with MarshallAs attribute for exact mapping and for "out" strings use StringBuilder with MarshallAs and predefined size.

I myself was with some troubles with PInvoke that is close to you question: How to correctly send long-live buffer from C# to C++

But your sample not clear : tts_sintetizaTexto_mm(long canal, string text, char* BuffVoice);

what here is from C and what is from C# - string it's System.String or std::string what char* means? unsafe C# char (wide char!) pointer or just C char* (signed!)byte buffer ???? - if std::string - it's not compatible with PInvoke - implement own C overload with char* or wchar_t* , if it's char* - remember, that it's not byte[] while byte[] is uchar*... If it's C# char* - remember that C# char is 16bit, so valid C type would be wchar_t or ushort... Where we can send size of buffer to prevent overflow? So where are more questions that you just asked...

For my expirience while it's not from attribute, but more controllable: use Marshall.GlobalHAlloc and IntPtr as interop type (for any xxxx*)

[DllImport("ctf.dll")]
public static extern long tts_sintetizaTexto_mm(long canal, string text, IntPtr BuffVoice); //you should provide valid pointer to global memory IntPtr is compatible with any pointer type

 ...
 _ptr = Marshal.AllocHGlobal(REQUIRED_BUFFER_SIZE_IN_BYTES);
 try{
      /* initialize it if required, for ex write ZERO char to start buffer  to allow strlen and other work well */
     tts_sintetizaTexto_mm ( /*other params*/, _ptr); //it will be valid pointer in C context
     /* do required work with _ptr - you can work with Read method and so on */
 }finally{
      Marshall.FreeHGlobal(_ptr); // we was beyond GC - remembere to clear memory
 }

Here: How can I pass MemoryStream data to unmanaged C++ DLL using P/Invoke you can see close sample with GCHandle that is almost same

Upvotes: 3

Related Questions