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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With