the Fiddler's picture

Investigate alternatives for the object overloads

Project:The Open Toolkit library
Version:0.9.6
Component:Code
Category:task
Priority:normal
Assigned:the Fiddler
Status:closed
Description

Object overloads wrap native functions that take void* parameters. OpenTK pins the parameter specified by the user and passes the address down to the unmanaged library.

OpenGL and OpenAL interact with value types or arrays of value types (i.e. int, float, Vector3 and similar types, as well as combinations and arrays of those). However, object overloads completely suppress type-checking and allow the user to pass any managed type to unmanaged code. This is unintuitive and potentially dangerous (crash-prone).

It is not possible to specify beforehand every possible structure the user may want to use. As a compromise, it is possible to use generic parameters to allow only T[] and ref T parameters, where T : struct. While this is not a 100% fail-safe solution (the struct constraint allows references to reference types), it should significantly reduce the dangers introduce by the type-less object overloads, with no loss of functionality. As an added advantage, these overloads will be symmetric to existing typed array overloads (i.e. T[] to int[] and ref T to ref int).

Because the pinvoke layer cannot deal with generic parameters, we need to pin and pass a pointer instead. With this implementation, a hack may be employed to avoid using the heavy-weight GCHandle class: a C# union can cast T[] to a byte[], allowing us to obtain a direct pointer to the underlying data.

This trick works on both .Net and Mono, seemingly without side-effects. A proof of concept is attached (the solution is intended for MonoDevelop 2 beta 1 or higher. It will probably require some modifications to work with Visual Studio or SharpDevelop). Note that you might need to copy manually the native library to the same directory as the managed exe.

AttachmentSize
ArrayInteropHack v1.zip9.12 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
objarni's picture

#1

This sounds great. Is there anything I can do to help out?

the Fiddler's picture

#2

I'd appreciate if you could run the attached project and see if it crashes. :)

I'd especially appreciate feedback on Windows, but I think you'll have to compile "native.dll" by hand (I wish I remembered the exact commandline.)

objarni's picture

#3

It would make it a lot easier for me if you could compile a .exe + the native dll in order for me to test run it.. I don't have VC++ installed for one thing :)

the Fiddler's picture

#4

I don't have Windows installed for another. :) (rebuilt my system, no access to VMs yet)

If you have mingw, you can compile a dll with something like "gcc native.c -fPIC -shared -o native.dll".

objarni's picture

#5

Ah ;) Forgot both of uses Ubuntu by default nowadays heh.

I have VirtualBox with a Windows XP installed -- could test at that. I guess VC++ express ed. is free nowadays, just as VC# express is?

Edit: or use gcc as you said, that is even simpler!

objarni's picture

#6

OK I compiled with the command line you mentioned (albeit changed native.c to main.c), copied the resulting native.dll into the Debug- and Release-folders.

Attached: Screenshot of thrown exception from a F5-run inside VS.
Attached: The complete solution, built using Visual C# Express 2008, including both the native.dll and binaries for any other testers convenience. Targets Windows only I guess, because of the native.dll dependence..?

Dump from a command-line run:

 Directory of C:\Documents and Settings\Administrator\Desktop\bin\Debug
 
2009-03-14  16:13    <DIR>          .
2009-03-14  16:13    <DIR>          ..
2009-03-14  16:13             6 656 ArrayInteropHack.exe
2009-03-14  16:13            17 920 ArrayInteropHack.pdb
2009-03-14  16:19            14 328 ArrayInteropHack.vshost.exe
2009-03-14  16:10             7 974 native.dll
               4 File(s)         46 878 bytes
               2 Dir(s)   6 106 972 160 bytes free
 
C:\Documents and Settings\Administrator\Desktop\bin\Debug>ArrayInteropHack.exe
 
Unhandled Exception: System.TypeLoadException: Could not load type 'ArrayInterop
Hack.ArrayConversionHack`1' from assembly 'ArrayInteropHack, Version=1.0.3360.29
211, Culture=neutral, PublicKeyToken=null' because generic types cannot have exp
licit layout.
   at ArrayInteropHack.MainClass.Print[T](ArraySegment`1 items)
   at ArrayInteropHack.MainClass.Print[T](T[] items) in C:\Documents and Setting
s\Administrator\Desktop\ArrayInteropHack\Main.cs:line 26
   at ArrayInteropHack.MainClass.Main(String[] args) in C:\Documents and Setting
s\Administrator\Desktop\ArrayInteropHack\Main.cs:line 55
 
C:\Documents and Settings\Administrator\Desktop\bin\Debug>
AttachmentSize
ArrayInteropHack.zip44.45 KB
ArrayHack.png132.26 KB
martinsm's picture

#7

For ArrayInteropHack you could try to solve error above with following code:

    [StructLayout(LayoutKind.Explicit)]
    struct ArrayConversionHack
    {
        [FieldOffset(0)]readonly Array Array;
        [FieldOffset(0)]public readonly byte[] Bytes;
 
        ArrayConversionHack(Array array)
            : this()
        {
            Array = array;
        }
 
        public static ArrayConversionHack Create<T>(T[] array) where T : struct
        {
            return new ArrayConversionHack(array);
        }
    }
 
        public static void Print<T>(ArraySegment<T> items) where T : struct
        {
            unsafe
            {
                ArrayConversionHack hack = ArrayConversionHack.Create<T>(items.Array);
                int stride = Marshal.SizeOf(typeof(T));
 
                fixed (byte* ptr = hack.Bytes)
                {
                    UnsafeNativeMethods.PrintArray(items.Count,
                        (void*)(ptr + items.Offset * stride));
                }
            }
        }

Don't know what to do with ItemConversionHack.

the Fiddler's picture

#8

Maybe System.ValueType would fit better here. If you have access to a Windows machine, can you please check that?

Edit: This hack is at the boundaries of what's acceptable (or maybe beyond), but it's about 10x faster than the old code which makes it worth it, I think. Without generics we lose the optimization of the static Stride member, but even with Marshal.SizeOf we should come out in front.

Edit 2: Lack of covariance bytes us again - you can't cast int[] to ValueType[]. Using Array should work for arrays then, it remains to find a hack for ItemConversionHack.

Edit 3: System.ValueType causes boxing (as far as I can tell), which means it can't be used in ItemConversionHack. Trying a different approach now.

martinsm's picture

#9

Yeah, I tired with ValueType - it doesn't help. ValueType is class (not struct type) so it yields incorrect result for ItemConversionHack - for me output was always 0, not 2 like it should be.

You can put Stride in different struct, if it really helps performance:

struct ArrayElementStride<T> where T : struct
{
    public static readonly int Stride = Marshal.SizeOf(typeof(T));
}
 
public static void Print<T>(ArraySegment<T> items) where T : struct
{
    unsafe
    {
        ArrayConversionHack hack = ArrayConversionHack.Create<T>(items.Array);
        fixed (byte* ptr = hack.Bytes)
        {
                UnsafeNativeMethods.PrintArray(items.Count,
                        (void*)(ptr + items.Offset * ArrayElementStride<T>.Stride));
        }
    }
}
the Fiddler's picture

#10

Ah, neat trick, hadn't thought of that!

It's not only about performance - it also allows you to check whether the type is compatible with OpenGL:

struct ArrayElementStride<T> where T : struct
{
    public static readonly int Stride = Marshal.SizeOf(typeof(T));
 
    static ArrayElementStride()
    {
        // Recursively inspect all fields of this type to ensure they are blittable, i.e:
        // byte, sbyte, short, ushort, int, uint, long, ulong, IntPtr, UIntPtr, float, double or pointers.
        // If they are not, throw a NotSupportedException (otherwise the type *will* corrupt memory.)
    }
}