为c++模板扩展c#代理类

Extend C# Proxy Class for a C++ template

本文关键字:代理 扩展 c++      更新时间:2023-10-16

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

来完成此操作。

现在我已经得到了:

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

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

但是我不知道我应该为/*Type?*/指定什么。我试过T, T*, 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实际上不是一个模板(即注释出测试的第一行)来简化事情。(并在某处为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(至少从Ubuntu 14.04开始的3.0)在上下文中没有意识到关于T的任何事情-模板替换没有正确发生。我不太确定这是一个错误还是预期的行为,但无论如何,这对我们来说都是一个问题。

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

%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++混合到其中,即下面修改过的测试。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>;

又成功了

最后一个解决方法是,如果你在模板中有一个typedef,它只使用T,例如:
template<typename T>
struct MyTemplateClass {
  typedef T type;
  //...

然后下面的工作,引用类型定义为$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>;

因此,即使简单的方法看起来应该工作似乎没有,仍然有很多选项开放,以达到我们需要的结果