为 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.swigCMemOwn
是 true
还是 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")
或 pythonprepend
和 pythonappend
来完成此操作
目前我已经达到:
- 使用
%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
实际上不是模板来简化事情(即注释掉 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>;
因此,尽管看起来应该可行的简单方法似乎并不奏效,但仍有许多选择可以实现我们需要的结果。
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.swigCMemOwn
是 true
还是 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")
或 pythonprepend
和 pythonappend
目前我已经达到:
- 使用
%csmethodmodifiers MyTemplateClass::Validate "private";
将 - 通过
%rename(InternalValidate, fullname=1) "MyTemplateClass::Validate";
将 - 使用
%typemap(cscode)
添加将调用InternalValidate
: 的新函数
Validate
设为私有
Validate
重命名为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
实际上不是模板来简化事情(即注释掉 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>;
因此,尽管看起来应该可行的简单方法似乎并不奏效,但仍有许多选择可以实现我们需要的结果。