2016-09-27 38 views
1

我有一个C(而不是C++)库,它始终使用函数的第一个参数作为上下文对象(我们称之为t_context),并且我想用SWIG生成C#包装纸保持呼叫的这个样式(即,而不是被或多或少分离的功能,将它们包装在某些类的方法和通过参考从this对象的方法中访问t_context)。SWIG:以OO方式包装C API

例(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 
    } 
} 

我也很乐意,如果有人指出我痛饮生成的包装在现有的C(再次:不是C++)使用此API风格的库;我找不到任何东西。

可替代地,是在API提供更多的控制为C 1至C#用例比其他SWIG包装有发电机(可能由暴露用于代码生成的模板)?

+0

如果您想从许多C函数创建一个类,您需要以某种方式告诉接口生成器,哪些函数属于给定的类。我猜最简单的方法是使用匿名结构“typedef struct _Context Context;'并使用'%extend'directive来添加调用封装C函数的成员函数。你将不得不在你的'.i'文件中为每个函数添加3行,就像你将C包装到C++类中一样。通过这种方式,您可以避免更改原始标题并且不需要重新编译您的C库 –

+0

是否有任何命名约定可以让您推断它们属于哪个“类”?如果这是一个选项,你可以自动化更多你想要的东西。 – Flexo

+0

是的,我可以控制函数的命名,所以很容易以my_lib_api_ *的形式命名应以这种方式公开的所有函数。 – pmf

回答

1

为了通过这个问题,我创建了以下小型头文件来证明一切,我们(可能)关心,为真正做到这件工作。我这样做的目标是:

  1. C#用户甚至不应该意识到这里有什么非OO发生。
  2. 你痛饮模块的维护者应该不会有回音的一切,如果可以通过手工编写大量的代理功能。

要踢东西了,我写了下面的头文件,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,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”等价参数作为argum进入该函数ent 1总是

上面的内容非常符合我们在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文件的位置相匹配的所有功能的正确位置。

这足以让我用下面的代码测试(使用单声道):

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我已经在过去处理。