Creating a Purebasic wrapper for Chipmunk.

Post date: Oct 22, 2012 7:42:35 AM

Step one: compile Chipmunk in mingw as a .dll

This was pretty easy. Following the history on this page I eventually landed on this process:

Unpack archive. Navigate to src tree.

gcc -I../include/chipmunk -O3 -ffast-math -std=gnu99 -c *.c

Success so far; no errors, no warnings.

(using -fPIC as per the forum post above spits out a warning that -fPIC is irrelevant because all code is position independent)

gcc --shared -o chipmunk-6.1.1.dll -Wl,--out-implib=chipmunk-6.1.1.dll.a -Wl,--output-def=chipmunk-6.1.1.dll.def cp*.o chipmunk.o

This produces a .dll that I confirmed is valid using dependency walker.

Step two: test .dll in purebasic

This one was more awkward. I didn’t know at first that mingw was producing x86 code, and I was trying to use x64. I eventually worked that out, and started using the x86 version of purebasic.

At first I used infratec’s tool to make a bare-bones almost-wrapper.. then I added some types..

; Warning! Achtung!

; This will be a Float on 32-bit MacOS installations!

; This will need to be addressed at some point.

Macro cpFloat

double

EndMacro

; See above. Floats on 32-bit MacOS.

Structure cpVect

x.d

y.d

EndStructure

and defined a single prototype to start with...

OSPrototype.d Proto_cpvtoangle(x.d, y.d)

And made a very simple test case.

tempvec1.cpVect

tempvec2.cpVect

tempvec3.cpVect

tempvec1\x = 1.0

tempvec1\y = 0.0

tempvec2\x = 0.0

tempvec2\y = 1.0

tempvec3\x = 0.7

tempvec3\y = 0.7

tempdouble.d

tempdouble = cpvtoangle(tempvec1\x, tempvec1\y)

Debug tempdouble

tempdouble = cpvtoangle(tempvec2\x, tempvec2\y)

Debug tempdouble

tempdouble = cpvtoangle(tempvec3\x, tempvec3\y)

Debug tempdouble

Results:

0.0

1.5707963267948966

0.78539816339744828

So far, so good.

Around this point, I started realizing a few things. One, an awful lot of chipmunk involves passing structures by value, not just as arguments, but as the return from functions.

At first, I pursued using assembly to play with hidden parameters, but then a simpler solution occurred: Make a shim in C that makes GCC do all that work for me.

Since I don’t HAVE to use this as a .dll, and in fact in the long term I don’t want to, the ASM approach is highly suboptimal.

Step three: create a test shim in C to make GCC do all the work for me

This immediately presents a problem: I don’t know C. Okay, let’s back up.

Step three: bribe a C programmer to tell me exactly how to make GCC do the work for me.

For this process, first you need to befriend an expert in C. If you don’t have one, well, good luck.

Through a careful series of [redacted] I managed to create the following test case.

Using one of the inline functions from cpVect.h:

static inline cpFloat cpvdist(const cpVect v1, const cpVect v2)

{

return cpvlength(cpvsub(v1, v2));

}

I created the following shim:

#include “chipmunk.h”

void PB_cpvdist(cpFloat *result, cpVect *v1, cpVect *v2)

{

*result = cpvdist(*v1, *v2);

}

I put this in a shim.c file in the src folder, and went back to the compilation process in step one above.

Result: a .dll file with one extra function in it than before.

I created a new test .pb file:

Prototype.i proto_PB_cpvdist(*result.cpFloat, *v1.cpVect, *v2.cpVect)

Global PB_cpvdist.proto_PB_cpvdist

chipmunk = OpenLibrary(#PB_Any, "chipmunk-x86.dll")

PB_cpvdist = GetFunction(chipmunk, "PB_cpvdist")

vector1.cpVect

vector2.cpVect

dist.cpFloat

vector1\x = 1.0

vector1\y = 0.0

vector2\x = 0.0

vector2\y = 1.0

PB_cpvdist(@dist, @vector1, @vector2)

Debug dist

(Not pictured: the previously indicated macro and structure declaration from step Two)

Result:

1.4142135623730951

Well, that sure looks like it’s working. Just to be sure, I also tested 1.0,0.0 against 0.0,0.0, and got 1.0. Equal vectors result in a dist of 0.0. That’s enough to believe that it’s probably okay.

Step Four: Make a purebasic library.

This seems simple on the surface. The forum post I found here implied that the .a output from mingw would work fine for this purpose.

I thought: Oh hey my .dll creation process ALSO created a .a file! I’m saved!

No.

Not even a little.

Even after correctly making a chipmunk-x86.desc file...

; Langage used to code the library: ASM or C

C

; Number of windows DLL than the library need

0

; Library type (Can be OBJ or LIB).

LIB

; Number of PureBasic library needed by the library. Here we need the Gadget library

0

; Help directory name. Useful when doing an extension of a library and want to put

; the help file in the same directory than the base library. This is not a facultative

; result.

chipmunk

; Library functions:

cpvdist, Long, Long, Long (*Result, *Vector1, *Vector2) - Result is Float/Double. Vector1 and Vector2 are cpVects

None

… it turns out that, no, that doesn’t work. Because the code was compiled as a shared library, the stub in the .a immediately tries to load in the .dll file, and things go sad from there.

At first I thought I would need to follow some complicated process involving using Microsoft’s Visual Studios tools to create a proper .lib file..

But I thought back to milan1612’s post in the PB forums, and did more digging.

This was the result of my search. Specifically, the following instruction for creating a .a file using mingw:

ar rcs libadd.a add.o

I thought: There is no way it could be that simple. There has to be more to this story.

Spoiler: Nope. That’s exactly what I needed.

ar rcs chipmunk-x86.a *.o

After feeding the .desc file and .a file (helpfully renamed as .lib) into the LibraryMaker.exe, I had myself a library. I dropped it into the userlibraries folder, and edited my above test code to use cpvdist instead of PB_cpvdist.

Result:

1.4142135623730951

Success! This process does, in fact work.

Step five: Create a full set of shims for the entirety of chipmunk, along with .desc files for x86 and x64 targets.

No, wait, that’s way too much work.

Step five: Create an automated tool to generate a full set of shims for the entirety of chipmunk, along with appropriate .desc files.

That sounds better.

This is still quite a bit of work, since it requires some infrastructure construction, but it has improved maintainability. I can maintain a single list of all functions, and have a tool autogenerate the shim and .desc files for any target architecture I desire. This should greatly simplify tracking the upstream library.

More on this later.