Search code examples
c#cswig

SWIG: Wrapping C API in OO way


I have a C (not C++) library that consistently uses the first parameter of functions as context object (let's call the type t_context), and I'd like to use SWIG to generate C# wrappers keep this style of call (i.e. instead of the functions being more or less isolated, wrap them as methods in some class and access the t_context via a reference from the this object within the methods).

Example (C signature):

void my_lib_function(t_context *ctx, int some_param);

Desired C# API:

class Context
{
    // SWIG generated struct reference
    private SWIG_t_context_ptr ctx;

    public void my_lib_function(int some_param)
    {
        // call SWIG generated my_lib_function with ctx
    }
}

I'd also be happy if someone points out to me a SWIG generated wrapper for an existing C (again: not C++) library that uses this API style; I could not find anything.

Alternatively, are there wrapper generators for the C to C# use case other than SWIG that offer more control over the API (perhaps by exposing the templates used for code generation)?


Solution

  • 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:

    1. C# users shouldn't even realise there's anything non-OO happening here.
    2. 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:

    1. The function that implements the extended context::func call is called context_func
    2. 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.