I'm having a hard time with the swig-generated code.
Everything that returns a pointer is wrapped into classes.
For example
In Newton.h, NewtonBody is just a forward declarated class. The C-interface has no idea what it contains and it doesn't need to.
The Swig generated code newton_wrap.cxx generates this...
- Code: Select all
SWIGEXPORT void * SWIGSTDCALL CSharp_NewtonCreateDynamicBody(void * jarg1, void * jarg2, float * jarg3) {
void * jresult ;
NewtonWorld *arg1 = (NewtonWorld *) (NewtonWorld *)0 ;
NewtonCollision *arg2 = (NewtonCollision *) (NewtonCollision *)0 ;
float *arg3 = (float *) (float *)0 ;
NewtonBody *result = 0 ;
arg1 = (NewtonWorld *)jarg1;
arg2 = (NewtonCollision *)jarg2;
arg3 = jarg3;
result = (NewtonBody *)NewtonCreateDynamicBody((NewtonWorld const *)arg1,(NewtonCollision const *)arg2,(float const *)arg3);
jresult = (void *)result;
return jresult;
}
I don't know why it clear the parameters, it overwrites them anyway
Now in newton_wrap.cs it creates this...
- Code: Select all
public class SWIGTYPE_p_NewtonBody {
private global::System.Runtime.InteropServices.HandleRef swigCPtr;
internal SWIGTYPE_p_NewtonBody(global::System.IntPtr cPtr, bool futureUse) {
swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
}
protected SWIGTYPE_p_NewtonBody() {
swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
}
internal static global::System.Runtime.InteropServices.HandleRef getCPtr(SWIGTYPE_p_NewtonBody obj) {
return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
}
}
It just creates a class containing the ptr to NewtonBody.
This is wasteful and not necessary. It's just an expensive wrapping that cause garbage collection.
Finally it creates this dll import
- Code: Select all
[global::System.Runtime.InteropServices.DllImport("NewtonWrapper", EntryPoint="CSharp_NewtonCreateDynamicBody")]
public static extern SWIGTYPE_p_NewtonBody NewtonCreateDynamicBody(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2, float * jarg3);
All we really need is to return a simple IntPtr which basically is C# version of a void *
Also, Swig generates classes for dMatrix, dQuaternion etc. We can't really use those in C#. We can but it would be inefficient calling the dll whenever we want to create a Matrix and as a Unity user I wouldn't want to user Vectors and Matrices other than those Unity uses.
Small classes like Vector, Quaternion, Matrix should really be a struct in .Net instead of a class.
These are used a lot where performance is needed and if they are classes they would end up in the garbage collector instead of being deallocated immediately when going out of scope(as value types does in .Net)
When C# passes a matrix to Newton like it does in NewtonBodyCreateDynamicBody, Newton wants a float *.
C# can't just pass a pointer to it's Matrix but instead you have to use something called marshalling.
This is how you must do it...(Matrix4x4 here is Unitys 4x4 matrix which is a struct)
- Code: Select all
Matrix4x4 mat = Matrix4x4.identity;
IntPtr ptrmat = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Matrix4x4)));
Marshal.StructureToPtr(mat, ptrmat, false);
NewtonWrapper.NewtonCreateDynamicBody(m_world, m_collider, ptrmat);
Marshal.FreeHGlobal(ptrmat);
You see, first we must allocate unmanaged memory the size of the struct.
Get a ptr to it.
Then copy the contents(memcopy) to the unmanaged buffer.
Then we pass that to Newton
After Newton returns the newly created body we have to free the buffer.
This is slow but that is the only allowed way to do it.
Since I know Newton's and Unity's matrix has identical members I can instead declare the dllimport like this...
- Code: Select all
[code][global::System.Runtime.InteropServices.DllImport("NewtonWrapper", EntryPoint="CSharp_NewtonCreateDynamicBody")]
public static extern SWIGTYPE_p_NewtonBody NewtonCreateDynamicBody(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2, ref Matrix4x4 matrix);[/code]
This will still cause the memory copy to happen but Pinvoke does it for me instead so I just need to write
- Code: Select all
Matrix4x4 mat = Matrix4x4.identity;
NewtonWrapper.NewtonCreateDynamicBody(m_world, m_collider, ref mat);
Which is alot easier.
Now I said this is the only allowed way to pass classes and structure via Pinvoke.
That isn't really true.
There is a compile option in .NET.
"Allow unsafe code"
Enabling this allows you to use pointers in C#(won't work in VB.NET).
Now you can actually call declare the dllimport like this...
- Code: Select all
[code][global::System.Runtime.InteropServices.DllImport("NewtonWrapper", EntryPoint="CSharp_NewtonCreateDynamicBody")]
public static extern SWIGTYPE_p_NewtonBody NewtonCreateDynamicBody(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2, float *matrix);[/code]
And call it like this from C#
- Code: Select all
Matrix4x4 mat = Matrix4x4.identity;
NewtonWrapper.NewtonCreateDynamicBody(m_world, m_collider, &(Matrix4x4)mat);
(Not sure about the casting above, but something like that...
This would ignore marshalling and work just like c and c++, with no extra memory copying which would make things faster.
This requires the functions to be declarated with the
unsafe keyword and this will be tricky with swig.
I peeked a little at the Bullet C# wrapper and they went with a custom wrapping tool as well and use the unsafe method.
Julio, would you be very upset if I tried coding my own wrapping tool for this?
It would be specialized just for Newton and this Plugin project, but it would make things so much easier for me.
I would try to make it so that we could rerun it anytime Newton.h changes with as little manual intervention as possible.