SWIG:以面向对象的方式包装 C API
SWIG: Wrapping C API in OO way
我有一个 C(不是 C++)库,它一直使用函数的第一个参数作为上下文对象(我们称之为类型 t_context
),我想使用 SWIG 生成 C# 包装器这种调用方式(即不是将函数或多或少地隔离,而是将它们作为方法包装在某些 class 中,并通过方法中 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# 用户甚至不应该意识到这里发生了任何非面向对象的事情。
- 如果可能的话,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++ constructor/destructor,但在 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);
}
}
问题二:成员函数
我设置它的方式允许我们使用一个可爱的技巧。当我们说:
%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);
}
要从中拿走的两件事是:
- 实现扩展
context::func
调用的函数被调用context_func
- 有一个隐含的 '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 which are possible to solve and a similar question looking at Java 我过去曾提到过。
我有一个 C(不是 C++)库,它一直使用函数的第一个参数作为上下文对象(我们称之为类型 t_context
),我想使用 SWIG 生成 C# 包装器这种调用方式(即不是将函数或多或少地隔离,而是将它们作为方法包装在某些 class 中,并通过方法中 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# 用户甚至不应该意识到这里发生了任何非面向对象的事情。
- 如果可能的话,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++ constructor/destructor,但在 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);
}
}
问题二:成员函数
我设置它的方式允许我们使用一个可爱的技巧。当我们说:
%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);
}
要从中拿走的两件事是:
- 实现扩展
context::func
调用的函数被调用context_func
- 有一个隐含的 '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 which are possible to solve and a similar question looking at Java 我过去曾提到过。