Thursday, 22 March 2012

Macro binding

So, late into the night I was experimenting with an alternative approach to a binding generator: using a macro processor.

Idea being: one relatively simple and expressive definition of an interface using macros, then by changing the macro definitions one can generate different outputs.

So for example, for field accessors I ended up with something like this:
build_gs_int(AVCodecContext, bit_rate, BitRate)

With the right definitions of the macros I then generate C, Java, or wrapper classes by passing this all through the C pre-processor. It's not the most advanced macro processor but it is sufficient for this task.

Then I thought I hit a snag - wont this be totally unwieldy for defining the function calls themselves?

So I looked into an alternative approach, using Java with annotations to define the calling interface instead, and then use reflection to generate the C bindings from that. The problem with this is: it just takes a lot of code. Types to define the type conventions, virtual methods for the generators, so on and so forth. Actually I spent several times the amount of time I spent on the field generator before I had something working (not terribly familiar with the reflection api required).

The definitions are relatively succinct, but only for function calls: for field accessors one ends up writing each get/set function as well. Still not the end of the world and it might be a reasonable approach for some cases.

@JGS(cname = "frame_size")
static native int getFrameSize(AVCodecContext p);
@JGS(cname = "frame_size")
static native void setFrameSize(AVCodecContext p, int v);
@JNI(cname = "avcodec_open")
static native int open(AVCodecContext p, AVCodec c);
The annotations are relatively obvious.

Then I had another thought this morning: dynamic cpp macro invocation. I forgot you can create a macro call based on macro arguments. All I would need to then define is a new macro for each number of possible arguments, and other macros could handle the various conventions used to marshal that particular argument. And even then it would mostly be simple cut and paste code once a single-argument macro was defined.

So after half an hour of hacking and 50 lines of code (both java and perl were about 400 to do this, with perl being much more dense), I came up with some macros that generate the JNI C code once something like this is defined:
call2(AVCodecContext, int, int, avcodec_open, open,
object, AVCodecContext, ctx, object, AVCodec, c)

The arguments are: java/c type, return 'convention', return type, c function call name, java method name, argument 1 convention, type, name, argument 2 convention, type, name. If I removed the argument names - which could be defined in the macro - it would be even simpler.

I 'just' need to define a set of macros for each convention type - then I realised I'd just wrote some dynamic object-oriented c-pre-processor ... hah.

For a binding like FFmpeg where there are few conventions and it benefits from grouping and 'consistifying' names, i.e. a lot of manual work - this may be a practical approach. And even when there are consistent conventions, it may be a practical intermediate step: the first generator then has less to worry about and all it has to do is either infer or be told the specific conventions of a given argument.

Of course, using a more advanced macro processor may help here, although i'm not as familiar with them as cpp (and more advanced means harder to learn). So that got me thinking of a programming exercise: try to solve this problem with different mechanisms and just see which turns out the easiest. XSLT anyone? Hmm, maybe not.

I'd started to realise the way I'd done it in jjmpeg's generator wasn't very good: i'm trying to infer the passing convention and coding it in the generator using special cases: rather than working it out at the start and parametrising the generator using code fragment templates. The stuff I was playing with with the openal binding was more along these lines but still has some baggage.

No comments: