为 C++ 模板扩展 C# 代理 Class

Extend C# Proxy Class for a C++ template

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

假设我在 C++ 中有以下模板 class 和 Validate 函数:

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

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

%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

来完成此操作

目前我已经达到:

代码:

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

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

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

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

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 的类型定义)。

在那种情况下,您尝试执行的操作非常有效,使用 $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 位分开。

但是,如果您不愿意那样复制并且不能't/won简单地编辑头文件,那么所有内容都不会丢失。我们可以(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 引用为 _basetype::type:

%module test

%{
#include "test.hh"
%}

%rename(InternalValidate) Validate;

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

%include "test.hh"

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

因此,尽管看起来应该可行的简单方法似乎并不奏效,但仍有许多选择可以实现我们需要的结果。