2016-09-27 58 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我已經在過去處理。