In order to work through this problem I've created the following mini header-file to demonstrate all the pieces we (probably) care about to do this for real. My goals in doing this are:
- C# users shouldn't even realise there's anything non-OO happening here.
- The maintainer of your SWIG module shouldn't have to echo everything and write lots of proxy functions by hand if possible.
To kick things off I wrote the following header file, test.h:
#ifndef TEST_H
#define TEST_H
struct context;
typedef struct context context_t;
void init_context(context_t **new);
void fini_context(context_t *new);
void context_func1(context_t *ctx, int arg1);
void context_func2(context_t *ctx, const char *arg1, double arg2);
#endif
And a corresponding test.c with some stub implementations:
#include <stdlib.h>
#include "test.h"
struct context {};
typedef struct context context_t;
void init_context(context_t **new) {
*new = malloc(sizeof **new);
}
void fini_context(context_t *new) {
free(new);
}
void context_func1(context_t *ctx, int arg1) {
(void)ctx;
(void)arg1;
}
void context_func2(context_t *ctx, const char *arg1, double arg2) {
(void)ctx;
(void)arg1;
(void)arg2;
}
There are a few different problems we need to solve to make this into a neat, usable OO C# interface. I'll work through them one at a time and present my preferred solution at the end. (This problem can be solved in a simpler way for Python, but the solution here will be applicable to Python, Java, C# and probably others)
Problem 1: Constructor and destructor.
Typically in an OO style C API you'd have some kind of constructor and destructor functions written that encapsulate whatever setup of your (likely opaque). To present them to the target language in a sensible way we can use %extend
to write what looks rather like a C++ constructor/destructor, but is still comes out after the SWIG processing as C.
%module test
%{
#include "test.h"
%}
%rename(Context) context; // Make it more C# like
%nodefaultctor context; // Suppress behaviour that doesn't work for opaque types
%nodefaultdtor context;
struct context {}; // context is opaque, so we need to add this to make SWIG play
%extend context {
context() {
context_t *tmp;
init_context(&tmp);
// we return context_t * from our "constructor", which becomes $self
return tmp;
}
~context() {
// $self is the current object
fini_context($self);
}
}
Problem 2: member functions
The way I've set this up allows us to use a cute trick. When we say:
%extend context {
void func();
}
SWIG then generates a stub that looks like:
SWIGEXPORT void SWIGSTDCALL CSharp_Context_func(void * jarg1) {
struct context *arg1 = (struct context *) 0 ;
arg1 = (struct context *)jarg1;
context_func(arg1);
}
The two things to take away from that are:
- The function that implements the extended
context::func
call is called context_func
- There's an implicit 'this' equivalent argument going into this function as argument 1 always
The above pretty much matches what we set out to wrap on the C side to begin with. So to wrap it we can simply do:
%module test
%{
#include "test.h"
%}
%rename(Context) context;
%nodefaultctor context;
%nodefaultdtor context;
struct context {};
%extend context {
context() {
context_t *tmp;
init_context(&tmp);
return tmp;
}
~context() {
fini_context($self);
}
void func1(int arg1);
void func2(const char *arg1, double arg2);
}
This doesn't quite meet point #2 of my goals as well as I'd hoped, you have to write out the function declarations manually (unless you use a trick with %include
and keeping themin individual header files). With Python you could pull all the pieces together at import time and keep it much simpler but I can't see a neat way to enumerate all the functions that match a pattern into the right place at the point where SWIG generates the .cs files.
This was sufficient for me to test (using Mono) with the following code:
using System;
public class Run
{
static public void Main()
{
Context ctx = new Context();
ctx.func2("", 0.0);
}
}
There are other variants of C OO style design, using function pointers which are possible to solve and a similar question looking at Java I've addressed in the past.