AndyG AndyG - 2 months ago 5
C++ Question

Extend C# Proxy Class for a C++ template

TLDR: How do I access the template type "T" in C# for SWIG?

Let's say I have the following templated class in C++ with a

Validate
function:

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


Let's say I instantiate the class for a variety of objects:

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


In C#, I want the
Validate
function to also validate memory ownership. That is whether
_in.swigCMemOwn
is
true
or
false
, so I'd like the C# wrapper to look something like this (for
MyTemplateClassFoo
)

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


The problem here is that if I want to write my own
Validate
function, I don't know what type
_in
is going to be. In Python I could accomplish this with
feature("shadow")
or
pythonprepend
and
pythonappend


For now I've gotten as far as:


  • Make
    Validate
    private using
    %csmethodmodifiers MyTemplateClass::Validate "private";

  • Rename
    Validate
    to
    InternalValidate
    via
    %rename(InternalValidate, fullname=1) "MyTemplateClass::Validate";

  • Use
    %typemap(cscode)
    to add a new function that will call
    InternalValidate
    :



Code:

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


But I don't know what I should specify for
/*Type?*/
. I've tried
T
,
T*
,
typemap(cstype, T)
but it doesn't appear that there is some special swig variable like
$csargtype
I can use.

I've tried looking into how SWIG wraps
std::vector
, and it appears that perhaps they are defining a macro, and then somehow calling it for every specialization of the vector? I guess I could live with that, but I wouldn't like it.

Answer

To work through these examples I created the following header file:

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

struct Foo {};

The good news is that it's actually possible to generate the code you're asking for much more simply than what you've tried. We can just use a csout typemap that matches only for Validate and we're good to go:

%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>;

For completeness sake though let's look at the original question as posed. Firstly let's simplify things by making MyTemplateClass not actually be a template (i.e. comment out line 1 of test.hh and add a typedef for T somewhere instead).

In that instance what you tried to do does pretty much work, using $typemap(cstype, T) to lookup the C# type used for a given type at SWIG compile time:

%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"

However when we revert this back to being a template again the code generated isn't correct, the Validate that gets generated is:

public bool Validate(SWIGTYPE_p_T in)

This is happening because SWIG (at least with 3.0, from Ubuntu 14.04) isn't aware of anything about T in that context - the template substitution isn't happening properly. I'm not quite sure if that's a bug or expected behaviour, but either way it's a problem for us.

What's interesting though is that if you're willing to write the cscode typemap inside the definition of the template that SWIG sees the substitution does work:

%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>;

In the above interface the type for T does get substituted into the output correctly. So if you're willing to accept the duplication between the .i file and the real header files you use in the library then that's sufficient. You could also edit the header file itself and mix SWIG and C++ into that, the following modified test.hh achieves the same result:

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 {};

That works because SWIG defines the preprocessor macro SWIG, but it won't be defined during normal C++ compilation so all is good. Personally I don't like that - I'd rather keep the C++ and SWIG bits logically separated with a clean boundary.

If however you're not willing to duplicate like that and can't/won't simply edit the header file all is not lost. We can (ab)use %extend to let us do the same thing:

%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>;

Which again works.

One final workaround is that if you've got a typedef inside the template that just uses T, e.g.:

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

Then the following works, references the typedef as $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>;

So even though the simple way that looks like it ought to work doesn't seem to there are still plenty of options open that achieve the result we need.

Comments