【问题标题】:SWIG: Wrapping C API in OO waySWIG:以 OO 方式包装 C API
【发布时间】:2016-09-27 08:04:11
【问题描述】:

我有一个 C(不是 C++)库,它始终使用函数的第一个参数作为上下文对象(我们称之为 t_context 类型),我想使用 SWIG 生成 C# 包装器保持这种调用风格(即,不要将函数或多或少隔离,而是将它们包装为某个类中的方法,并通过方法中 this 对象的引用访问 t_context

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

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

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

【问题讨论】:

  • 如果你想从多个 C 函数创建一个类,你需要告诉接口生成器哪些函数属于给定的类。我想最简单的方法是使用匿名结构 typedef struct _Context Context; 并使用 %extenddirective 添加调用包装 C 函数的成员函数。您必须为 .i 文件中的每个函数添加 3 行,类似于将 C 包装在 C++ 类中的方式。这样你就可以避免改变原来的头文件并且不需要重新编译你的 C 库
  • 函数是否有任何命名约定可以让您推断它们属于哪个“类”?如果可以的话,你可以自动化更多你想要的东西。
  • 是的,我可以控制函数的命名,因此很容易将所有应该以这种方式公开的函数命名为 my_lib_api_*。

标签: c# c swig


【解决方案1】:

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

  1. C# 用户甚至不应该意识到这里发生了任何非面向对象的事情。
  2. 您的 SWIG 模块的维护者不应该必须回应所有内容并尽可能手动编写大量代理函数。

为了开始,我编写了以下头文件 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 进入此函数

上面的内容与我们开始在 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

【讨论】:

    猜你喜欢
    • 2015-11-26
    • 2011-02-10
    • 2021-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    相关资源
    最近更新 更多