SWIG:以OO方式包装C API

时间:2016-09-27 08:04:11

标签: c# c swig

我有一个C(不是C ++)库,它始终使用函数的第一个参数作为上下文对象(让我们调用类型t_context),我想使用SWIG来生成C#包装器保持这种调用方式(即代替函数或多或少孤立,将它们包装为某些类中的方法,并通过方法中t_context对象的引用访问this

示例(C签名):

void my_lib_function(t_context *ctx, int some_param);

所需的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
    }
}

如果有人向我指出使用此API样式的现有C(再次:非C ++)库的SWIG生成包装器,我也很高兴;我找不到任何东西。

或者,是否存在除SWIG以外的C to C#用例的包装器生成器,它可以提供对API的更多控制(可能通过公开用于代码生成的模板)?

1 个答案:

答案 0 :(得分:1)

为了解决这个问题,我创建了以下迷你标题文件来演示我们(可能)关心真正做到这一点的所有部分。我这样做的目标是:

  1. C#用户甚至不应该意识到这里发生了任何非OO。
  2. 如果可能,您的SWIG模块的维护者不应该回应所有内容并手动编写大量代理功能。
  3. 为了解决问题,我编写了以下头文件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
    

    带有一些存根实现的相应test.c:

    #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;
    }
    

    我们需要解决一些不同的问题,以使其成为一个整洁,可用的OO C#界面。我会一次一个地完成它们并在最后提出我最喜欢的解决方案。 (这个问题可以用Python更简单的方式解决,但这里的解决方案将适用于Python,Java,C#和其他人)

    问题1:构造函数和析构函数。

    通常在OO样式的C API中,您会编写某种构造函数和析构函数,这些函数会封装您的任何设置(可能是不透明的)。为了以合理的方式将它们呈现给目标语言,我们可以使用%extend来编写看起来像C ++构造函数/析构函数的内容,但在SWIG处理之后仍然以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);
      }
    }
    

    问题2:成员函数

    我设置它的方式允许我们使用一个可爱的技巧。当我们说:

    %extend context {
      void func();
    }
    
    然后,SWIG会生成一个如下所示的存根:

    SWIGEXPORT void SWIGSTDCALL CSharp_Context_func(void * jarg1) {
      struct context *arg1 = (struct context *) 0 ;
    
      arg1 = (struct context *)jarg1; 
      context_func(arg1);
    }
    

    要从中得到的两件事是:

    1. 实现扩展context::func调用的函数称为context_func
    2. 有一个隐含的'this'等价参数进入这个函数,因为参数1总是
    3. 以上几乎与我们开始在C侧包装的内容相匹配。所以要包装它我们可以简单地做:

      %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);
      }
      

      这不太符合我的目标第2点以及我希望的,你必须手动写出函数声明(除非你使用%include的技巧并保留单独的头文件)。使用Python,您可以在导入时将所有部分组合在一起并使其更加简单,但是我无法看到一种巧妙的方法来将所有与模式匹配的函数枚举到SWIG生成.cs文件的正确位置。

      这足以让我使用以下代码测试(使用Mono):

      using System;
      
      public class Run
      {
          static public void Main()
          {
              Context ctx = new Context();
              ctx.func2("", 0.0);
          }
      }
      

      我可以解决other variants of C OO style design, using function pointers个问题以及过去曾提到的类似问题looking at Java