2

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)?

pmf
  • 7,619
  • 4
  • 47
  • 77
  • If you want to create a class from a number of C functions, you need somehow to tell the interface generator, which functions belong to a given class. I guess the easiest way is to use the anonymous struct, `typedef struct _Context Context;` and use the `%extend`directive to add member functions calling the wrapped C functions. You will have to add 3 lines for each function in your `.i` file, similarly to how your would wrap the C in a C++ class. In this way you avoid changing the original headers and do not need to recompile your C library – Jens Munk Sep 27 '16 at 16:52
  • Is there any naming convention to the functions that lets you infer which "class" they belong to? You can automate more of what you want I think if that's an option. – Flexo Sep 28 '16 at 20:12
  • Yes, I have control over the naming of the functions, so it would be easy to name all functions that should be exposed in this way as my_lib_api_*. – pmf Sep 29 '16 at 07:18

1 Answers1

2

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.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Flexo
  • 87,323
  • 22
  • 191
  • 272