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 开发者_Python百科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
.
Now this might sound overly work intensive, but whenever I'm a situation where I must access through a binding some C structure utilizing C specific features (like unions or void*
pointers), I normally write a set of get/set functions from/to the target language native (marshalling) types in C and provide those through the binding and keep the structure itself opaque.
Can you first marshal a struct with only espeak_EVENT_TYPE type
? Then you could choose one of two structs depending on what you expect to find in the contents of the union: one having only int number
and one having only const char *name
.
精彩评论