Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » [c#]need some help with p/invoke and unions

This thread is locked; no one can reply to it. rss feed Print
[c#]need some help with p/invoke and unions
Neil Walker
Member #210
April 2000
avatar

Hello,
I'm having a play with binding the allegro5 dll to .net and am getting an odd error with my marshalling of the event unions.

"Could not load type ...because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field"

I've read elsewhere it's to do with value/reference types but I'm not really sure how to fix it. I verified what I was doing using the P/Invoke interop Assistant and it was quite happy.

Basically, what is throwing me is it seems to be giving me the error based on the size of the struct items and not object/value type mixing as I've read elsewhere:

I've cut things down hugely to show the error:

   [StructLayout(LayoutKind.Explicit)]
    public struct GAME_EVENT
    {
       [FieldOffsetAttribute(0)]
       public int type;
       [FieldOffsetAttribute(0)]
       public ANY_EVENT display;
       [FieldOffsetAttribute(0)]
       public JOYSTICK_EVENT joystick;
    };

If I remove JOYSTICK_EVENT then the code works. JOYSTICK_EVENT has more data values than ANY_EVENT and if I remove, say, an int from JOYSTICK_EVENT it also works.

ANY_EVENT is described as:

    [StructLayout(LayoutKind.Sequential)]
    public struct ANY_EVENT
    {
        public   type;
        public EVENT_SOURCE source;
        public double timestamp;
    };

and JOYSTICK_EVENT is described exactly the same but has an extra int (in reality it isn't but I've managed to get it to fail like this):

    [StructLayout(LayoutKind.Sequential)]
    public struct ANY_EVENT
    {
        public   type;
        public EVENT_SOURCE source;
        public double timestamp;
        int value;      // the new value
    };

When I run my code I get the error as detailed. If I remove 'int value' it works fine.

If I remove EVENT_SOURCE then it seems to work. So I'm a little confused as to exactly what the fault is!

EVENT_SOURCE is:

    [StructLayout(LayoutKind.Sequential)]
    public struct EVENT_SOURCE
    {
        /// int[32]
        [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst=32, ArraySubType=UnmanagedType.I4)]
        public int[] @__pad;
    };

Any help would be much appreciated. Thanks.

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

Erin Maus
Member #7,537
July 2006
avatar

The second field should be an IntPtr, not an actual structure, like so:

#SelectExpand
1 [StructLayout(LayoutKind.Sequential, Pack = 0)] 2 public struct AllegroInternalEventHeader 3 { 4 public uint type; 5 public IntPtr source; 6 public double timestamp; 7 } 8 9 [StructLayout(LayoutKind.Sequential, Pack = 0)] 10 public struct AllegroInternalDisplayEvent 11 { 12 public AllegroInternalEventHeader header; 13 public int x, y; 14 public int width, height; 15 } 16 17 [StructLayout(LayoutKind.Sequential, Pack = 0)] 18 public struct AllegroInternalJoystickEvent 19 { 20 public AllegroInternalEventHeader header; 21 public int stick; 22 public int axis; 23 public float pos; 24 public int button; 25 } 26 27 [StructLayout(LayoutKind.Sequential, Pack = 0)] 28 public struct AllegroInternalKeyboardEvent 29 { 30 public AllegroInternalEventHeader header; 31 public IntPtr display; 32 public int keycode; 33 public uint unichar; 34 public uint modifiers; 35 } 36 37 [StructLayout(LayoutKind.Sequential, Pack = 0)] 38 public struct AllegroInternalMouseEvent 39 { 40 public AllegroInternalEventHeader header; 41 public IntPtr display; 42 public int x, y, z, w; 43 public int dx, dy, dz, dw; 44 public uint button; 45 } 46 47 [StructLayout(LayoutKind.Sequential, Pack = 0)] 48 public struct AllegroInternalTimerEvent 49 { 50 public AllegroInternalEventHeader header; 51 public long count; 52 public double error; 53 } 54 55 [StructLayout(LayoutKind.Explicit, Pack = 0)] 56 public struct AllegroInternalEvent 57 { 58 [FieldOffset(0)] 59 public uint type; 60 61 [FieldOffset(0)] 62 public AllegroInternalEventHeader any; 63 64 [FieldOffset(0)] 65 public AllegroInternalDisplayEvent display; 66 67 [FieldOffset(0)] 68 public AllegroInternalJoystickEvent joystick; 69 70 [FieldOffset(0)] 71 public AllegroInternalKeyboardEvent keyboard; 72 73 [FieldOffset(0)] 74 public AllegroInternalMouseEvent mouse; 75 76 [FieldOffset(0)] 77 public AllegroInternalTimerEvent timer; 78 }

That's how I wrap Allegro 5 WIP events in my game.

---
ItsyRealm, a quirky 2D/3D RPG where you fight, skill, and explore in a medieval world with horrors unimaginable.
they / she

Neil Walker
Member #210
April 2000
avatar

I'll try that, thanks. I use IntPtr in most places but I wanted to recreate the structs wherever it was possible. Regarding this error, what's the reason behind not being allowed to do this, and is there no way round it?

Also, without EVENT_SOURCE how did you do stuff like:
public static extern void al_init_user_event_source(ref ALLEGRO_EVENT_SOURCE src);

Did you just use IntPtr as well? I was just trying to add some readability to the code where possible. I was even tempted to edit the allegro code and make the union just one big struct :)

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

Kibiz0r
Member #6,203
September 2005
avatar

Neil, a little bit of critique:

  • Try to name your stuff by .NET standards; not Allegro standards. (PascalCase for public/protected members, camelCase for private members.)

  • Use [DllImport(EntryPoint="foo")] rather than naming your methods awkwardly. Especially, use a constant for the name of the DLL, so that if it changes you only have to change it in one spot.

  • Do [MarshalAs], not [MarshalAsAttribute].

  • You don't have to specify [StructLayout(LayoutKind.Sequential)] for structs; they are sequential by default.

  • Avoid specifying field offsets. It is unnecessary for almost all cases and burdens the code with more things that could change or just be incorrect.

  • Your C#-side objects should be classes, and should behave like classes.

  • Especially for resources like Bitmaps, they should implement IDisposable and have an appropriate finalizer.

  • Use structs as memory blocks to communicate with the C-side.

  • Follow the .NET guidelines for structs wherever possible -- ie. structs should be immutable.

  • You can specify [StructLayout] for classes, and when marshalled, classes are passed as if they were by-ref structs.

  • Read about [In] and [Out] as well as default marshaling for the out keyword, if you haven't already.

  • Look out for callbacks, they can be complicated. You need the [UnmanagedFunctionPointer] attribute on your delegate type, and you generally need to specify the calling convention too. (Which you will only find out is incorrect if the callback has parameters.)

  • If two C# objects refer to the same C resource, make sure that they are kept in sync.

  • Use an IntPtr for anything that is opaque from the C# side.

Neil Walker
Member #210
April 2000
avatar

Thanks, there's so much to learn with p/invoke :)

Kibiz0r said:

Read about [In] and [Out]

Do have any good sources, this is something I've seen a fair bit in code but have been unable to find any reference on the internet whatsoever to explain it as I find it impossible to construct a search to give me anything useful.

Quote:

Try to name your stuff by .NET standards

I appreciate what you're saying and I do follow standards, but my goal in this instance was to make a direct equivalent where with minimal changes code could easily be written and ported. Otherwise you'd end up with effectively a new library. What I plan on doing (if all works and the speed is sufficient in the bindings) add a proper class library around it. The only real concession I've made to this api equivalence is where functions return pointer data (e.g. bitmap creation, etc) I use exceptions and I have some static constructors to automatically run the install methods for those requiring them (e.g. the add-ons, keyboard, etc)

Quote:

Do [MarshalAs], not [MarshalAsAttribute]

This is now redundant as I'm using IntPtr. However, I got MarshalAsAttribute directly from MSDN pinvoke tool, so I assumed Microsoft knew what they were talking about in this case. Why do you say this?

Quote:

You don't have to specify [StructLayout(LayoutKind.Sequential)] for structs

I prefer to explicitly state this for my readibility

Quote:

Avoid specifying field offsets

You need field offsets for unions, that's how create them (all with offset of 0)

Quote:

they should implement IDisposable

That adds overhead, as I mentioned I wanted this thin wrapper layer and the posh stuff comes in a proper managed framework

Quote:

You need the [UnmanagedFunctionPointer]

Ok, again I followed what pinvoke at MSDN gave me for function pointers and assumed they knew what they were doing, as this is my first p/invoke exploration

Quote:

immutable structs

Rightly or wrongly, I did this as it's a low level library and didn't want the overhead of getter/setters.

Quote:

You can specify [StructLayout] for classes, and when marshalled, classes are passed as if they were by-ref structs

Cheers. I guess this is one way of handling the struct pointer functions without using IntPtr or having the issues with the object/value runtime error.

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

Kibiz0r
Member #6,203
September 2005
avatar

Do have any good sources, this is something I've seen a fair bit in code but have been unable to find any reference on the internet whatsoever to explain it as I find it impossible to construct a search to give me anything useful.

There's a really good article on MSDN or maybe some MS-affiliate that I found a while back. I took a quick peek and didn't find it; if I do, I'll let you know.

Suffice it to say that there are behavioral and performance considerations to bear in mind when deciding how to decorate your parameters for P/Invoke methods. It especially comes into play with arrays, where you don't want to waste time copying 1,000 values back and forth between the boundaries. The attributes themselves do exactly what you expect -- [In] copies data into the method (presumably managed to unmanaged, but I think it switches with callbacks) and [Out] copies it back out of the method. (Mimicking passing by reference when language constructs preclude you from actually passing by reference, or for use with arrays.)

Quote:

I appreciate what you're saying and I do follow standards, but my goal in this instance was to make a direct equivalent where with minimal changes code could easily be written and ported.

If it's just for your use, I'll leave you to it. But if you intend for other people to use this, my gut feeling is that this is an auxiliary use case at best. Allegro is just one piece of a large puzzle that would have to be reassembled in C#, for little to no benefit.

I'm a big advocate of making your code feel like the language that it's written in. The place I'm writing for right now has one standard that they use for all languages, and it makes the code I'm writing look completely out of place, and in some cases ruins the API.

Quote:

This is now redundant as I'm using IntPtr. However, I got MarshalAsAttribute directly from MSDN pinvoke tool, so I assumed Microsoft knew what they were talking about in this case. Why do you say this?

It's more concise and expressive, it's what shows up in IntelliSense, and [MarshalAsAttribute] can match either class MarshalAsAttribute or class MarshalAsAttributeAttribute. (and yes, I have seen attribute that look like that.)

Quote:

You need field offsets for unions, that's how create them (all with offset of 0)

Ah, I didn't know that. I never tried to make a direct mirror of a union on my A5 binding; instead I inspected the type of the event when I pulled it out and instantiated the correct layout right off the bat.

Quote:

That adds overhead, as I mentioned I wanted this thin wrapper layer and the posh stuff comes in a proper managed framework

Then your proper managed framework is going to have overhead because you didn't cut right to the heart of the issue. :)

Quote:

Ok, again I followed what pinvoke at MSDN gave me for function pointers and assumed they knew what they were doing, as this is my first p/invoke exploration

I didn't see any callback code in what you posted, I'm just telling you for when you do need to interact with some.

Quote:

Rightly or wrongly, I did this as it's a low level library and didn't want the overhead of getter/setters.

I'm not talking about getters and setters, I'm talking about complete immutability. You can think of the structs as sort of messages that you send back and forth between C and C#.

Tobias Dammers
Member #2,604
August 2002
avatar

Quote:

Read about [In] and [Out]

I'm pretty sure google would turn up something useful for "InAttribute msdn" or similar.
In fact, it does.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Go to: