Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map a C union with const char* to C# struct?

In a callback function from a native library, I need to access an array of espeak_EVENT. The problem is the UNION statement in the original C code:

typedef struct {
    espeak_EVENT_TYPE type;
    unsigned int unique_identifier; // message identifier (or 0 for key or character)
    int text_position;    // the number of characters from the start of the text
    int length;           // word length, in characters (for espeakEVENT_WORD)
    int audio_position;   // the time in mS within the generated speech output data
    int sample;           // sample id (internal use)
    void* user_data;      // pointer supplied by the calling program
    union {
        int number;        // used for WORD and SENTENCE events. For PHONEME events this is the phoneme mnemonic.
        const char *name;  // used for MARK and PLAY events.  UTF8 string
    } id;
} espeak_EVENT;

I have

[StructLayout(LayoutKind.Explicit)]
        public struct espeak_EVENT
        {
            [System.Runtime.InteropServices.FieldOffset(0)]
            public espeak_EVENT_TYPE type;

            [System.Runtime.InteropServices.FieldOffset(4)]
            public uint unique_identifier;  // message identifier (or 0 for key or character)

            [System.Runtime.InteropServices.FieldOffset(8)]
            public int text_position;    // the number of characters from the start of the text

            [System.Runtime.InteropServices.FieldOffset(12)]
            public int length;           // word length, in characters (for espeakEVENT_WORD)

            [System.Runtime.InteropServices.FieldOffset(16)]
            public int audio_position;   // the time in mS within the generated speech output data

            [System.Runtime.InteropServices.FieldOffset(20)]
            public int sample;           // sample id (internal use)

            [System.Runtime.InteropServices.FieldOffset(24)]
            public IntPtr user_data;      // pointer supplied by the calling program

            [System.Runtime.InteropServices.FieldOffset(32)]
            public int number;

            [System.Runtime.InteropServices.FieldOffset(32)]
            [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)]
            public string name; 
        }

And then

public static Int32 SynthCallback(IntPtr wav, Int32 numsamples, IntPtr eventsParameter)
        {
            if (wav == IntPtr.Zero) 
                return 0;

            int j=0;
            while(true)
            {
                System.IntPtr ptr = new IntPtr( 
                                                (
                                                    eventsParameter.ToInt64() 
                                                    + (j *
                                                        System.Runtime.InteropServices.Marshal.SizeOf(typeof(cEspeak.espeak_EVENT))
                                                      ) 
                                                )
                                              );
                if(ptr == IntPtr.Zero)
                    Console.WriteLine("NULL");

                cEspeak.espeak_EVENT events = (cEspeak.espeak_EVENT) System.Runtime.InteropServices.Marshal.PtrToStructure(ptr, typeof(cEspeak.espeak_EVENT));

                if(events.type == cEspeak.espeak_EVENT_TYPE.espeakEVENT_SAMPLERATE)
                {
                    Console.WriteLine("Heureka");
                }
                break;


                //Console.WriteLine("\t\t header {0}: address={1}: offset={2}\n", j, info.dlpi_phdr, hdr.p_offset);
                ++j;
            }


            if(numsamples > 0)
            {
                byte[] wavbytes = new Byte[numsamples * 2];
                System.Runtime.InteropServices.Marshal.Copy(wav, wavbytes, 0, numsamples*2);
                bw.Write(wavbytes, 0, numsamples*2);
            }
            return 0;
        }

But it always fails on

cEspeak.espeak_EVENT events = (cEspeak.espeak_EVENT) System.Runtime.InteropServices.Marshal.PtrToStructure(ptr, typeof(cEspeak.espeak_EVENT));

However, when I remove

[System.Runtime.InteropServices.FieldOffset(32)][System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)]
            public string name; 

From espeak_event, then it works.

How can I make this work without removing the string in the union ? I need to access it in the callback function.

Edit: And btw, what happens to the field offsets if I let it run on x64, and the size of " public IntPtr user_data;" changes from 32 to 64 bit ?

Hm, thinking about it, is fieldoffset 32 correct ? Seems I mixed-up the pointer size when thinking about x64. That might very well be another bug.

Hm, union with int and char*, my guess is they never compiled it for x64. Because sizof(int) is 32 bit on a x64 Linux system.

like image 408
Stefan Steiger Avatar asked Jul 04 '11 20:07

Stefan Steiger


1 Answers

Declare name as IntPtr rather than string and then use Marshal.PtrToStringAnsi to get it into a string variable.

I'm ignoring the fact that the string contents are UTF-8. If your text is pure ASCII that's fine. If not then you need to copy to a byte[] array and then translate from UTF-8 with Encoding.UTF8.GetString.

like image 192
David Heffernan Avatar answered Oct 01 '22 22:10

David Heffernan