扩展C ++模板的C#代理类

时间:2016-09-16 14:29:07

标签: c# c++ swig

TLDR:如何在C#中为SWIG访问模板类型“T”?

假设我在C ++中使用Validate函数有以下模板化类:

template<typename T>
struct MyTemplateClass
{
   bool Validate(T* _in)
   {
       //...
   }
   // ... other stuff... MyTemplateClass is also a container for T*
};

假设我为各种对象实例化了类:

%template(MyTemplateClassFoo) MyTemplateClass<Foo>
%template(MyTemplateClassBar) MyTemplateClass<Bar>
// etc.

在C#中,我希望Validate函数也验证内存所有权。那就是_in.swigCMemOwntrue还是false,所以我希望C#包装器看起来像这样(对于MyTemplateClassFoo

public class MyTemplateClassFoo{
    public bool Validate(Foo _in) {
       bool ret = _in.swigCMemOwn &&
          ModuleCLRPINVOKE.MyTemplateClassFoo_Validate(swigcPtr, Foo.getCPtr(_in));
       // SWIGEXCODE stuff
       return ret;
    }
// ...
}

这里的问题是,如果我想编写自己的Validate函数,我不知道_in将是什么类型。在Python中,我可以使用feature("shadow")pythonprependpythonappend

完成此操作

现在我已经到了:

  • 使用Validate
  • %csmethodmodifiers MyTemplateClass::Validate "private";设为私有
  • 通过Validate
  • InternalValidate重命名为%rename(InternalValidate, fullname=1) "MyTemplateClass::Validate";
  • 使用%typemap(cscode)添加将调用InternalValidate的新功能:

代码:

%typemap(cscode) MyTemplateClass %{
   public bool Validate(/*Type?*/ _in)
   {
       return _in.swigCMemOwn && InternalValidate(_in);
   }
%}

但我不知道我应该为/*Type?*/指定什么。我尝试了TT*typemap(cstype, T),但似乎没有像我可以使用的$csargtype这样的特殊swig变量。

我已经尝试过调查SWIG如何包装std::vector,看起来他们可能正在定义一个宏,然后以某种方式调用它来实现向量的每个特化?我想我可以忍受,但我不喜欢它。

1 个答案:

答案 0 :(得分:1)

要完成这些示例,我创建了以下头文件:

template<typename T>
struct MyTemplateClass
{
   bool Validate(T* _in)
   {
       return false;
   }
   // ... other stuff... MyTemplateClass is also a container for T*
};

struct Foo {};

好消息是,实际上可以生成您要求的代码比您尝试过的代码简单得多。我们可以使用仅匹配Validate的csout类型地图,我们很高兴:

%module test

%{
#include "test.hh"
%}

%typemap(csout, excode=SWIGEXCODE) bool Validate {
    // referring to _in by name is a bit of a hack here, but it works...
    bool ret = _in.swigCMemOwn && $imcall;$excode
    return ret;
  }

%include "test.hh"

%template(MyTemplateClassInt) MyTemplateClass<int>;
%template(MyTemplateClassFoo) MyTemplateClass<Foo>;

为了完整起见,让我们看看提出的原始问题。首先让我们通过使MyTemplateClass实际上不是一个模板来简化事情(即注释掉test.hh的第1行并在某处为T添加一个typedef)。

在那个实例中,您尝试做的工作非常有用,使用$typemap(cstype, T)在SWIG编译时查找用于给定类型的C#类型:

%module test

%{
#include "test.hh"
%}

%typemap(cscode) MyTemplateClass %{
  public bool Validate($typemap(cstype, T) in) {
    return in.swigCMemOwn && InternalValidate(in);
  }
%}

%rename(InternalValidate) Validate;

%include "test.hh"

然而,当我们再次将其恢复为模板时,生成的代码不正确,生成的Validate为:

public bool Validate(SWIGTYPE_p_T in)

这种情况正在发生,因为SWIG(至少3.0,来自Ubuntu 14.04)在该上下文中并不知道有关T的任何内容 - 模板替换没有正确发生。我不太确定这是一个错误或预期的行为,但无论哪种方式对我们来说都是一个问题。

有趣的是,如果您愿意在SWIG看到的模板定义中编写cscode类型映射,那么替换确实有效:

%module test

%{
#include "test.hh"
%}

%rename(InternalValidate) Validate;

template<typename T>
struct MyTemplateClass
{
   bool Validate(T* _in)
   {
       return false;
   }
   // ... other stuff... MyTemplateClass is also a container for T*

%typemap(cscode) MyTemplateClass %{
  public bool Validate($typemap(cstype, T) in) {
    return in.swigCMemOwn && InternalValidate(in);
  }
%}

};

struct Foo {};   

%template(MyTemplateClassInt) MyTemplateClass<int>;
%template(MyTemplateClassFoo) MyTemplateClass<Foo>;

在上面的界面中,T的类型确实被正确替换为输出。因此,如果您愿意接受.i文件与您在库中使用的实际头文件之间的重复,那么这就足够了。您还可以编辑头文件本身并将SWIG和C ++混合到其中,以下修改后的test.hh实现了相同的结果:

template<typename T>
struct MyTemplateClass
{
   bool Validate(T* _in)
   {
       return false;
   }
   // ... other stuff... MyTemplateClass is also a container for T*
#ifdef SWIG
%typemap(cscode) MyTemplateClass %{
  public bool Validate($typemap(cstype, T) in) {
    return in.swigCMemOwn && InternalValidate(in);
  }
%}
#endif
};

struct Foo {};

这是因为SWIG定义了预处理器宏SWIG,但是在正常的C ++编译过程中不会定义它,所以一切都很好。就个人而言,我不喜欢这样 - 我宁愿保持C ++和SWIG位在逻辑上与一个干净的边界分开。

但是,如果您不愿意像这样复制并且不能/不会简单地编辑头文件,那么一切都不会丢失。我们可以(ab)使用%extend让我们做同样的事情:

%module test

%{
#include "test.hh"
%}

%rename(InternalValidate) Validate;

%include "test.hh"

%extend MyTemplateClass {
%typemap(cscode) MyTemplateClass %{
  public bool Validate($typemap(cstype, T) in) {
    return in.swigCMemOwn && InternalValidate(in);
  }
%}
}

%template(MyTemplateClassInt) MyTemplateClass<int>;
%template(MyTemplateClassFoo) MyTemplateClass<Foo>;

再次有效。

最后一个解决方法是,如果你在模板中有一个只使用T的typedef,例如:

template<typename T>
struct MyTemplateClass {
  typedef T type;
  //...

然后以下工作,将typedef引用为$1_basetype::type

%module test

%{
#include "test.hh"
%}

%rename(InternalValidate) Validate;

%typemap(cscode) MyTemplateClass %{
  public bool Validate($typemap(cstype, $1_basetype::type) in) {
    return in.swigCMemOwn && InternalValidate(in);
  }
%}

%include "test.hh"

%template(MyTemplateClassInt) MyTemplateClass<int>;
%template(MyTemplateClassFoo) MyTemplateClass<Foo>;

所以即使看起来应该工作的简单方式似乎还没有很多选项可以实现我们需要的结果。