Monday, April 11, 2011

Complicated SWIG polymorphic return

A simpler solution

////////////////////////////////////////////////////////////////////////////////
// TODO: make this macro compatible with .NET Compact Framework, in which you
// can't return a structure over PInvoke.
//
// There is a problem in case you want to wrap a function that returns a pointer
// to a base class in SWIG, if that base class could refer to some derived
// class. For example suppose these classes are defined:
// class Actor { virtual ~Actor(); ... };
// class HumanActor : public Actor { ... };
// class RobotActor : public Actor { ... };
// class DogActor : public Actor { ... };
//
// And there is this function that could return any of the above:
// Actor* GetActor() { ... }
//
// The problem is that on the C# side, GetActor() will always return a new
// base class object (Actor), instead of the derived class (such as HumanActor).
// There is no easy way to deal with this, but I've written a macro that can
// help if you really need it. To use it, first, invoke the macro:
//
// %cs_return_derived_proxy_by_code(Actor, ActorOrDerivedClass, 0)
//
// The second name is a special typedef name that tells SWIG when to use the
// macro's special behavior. In order to make the macro apply to GetActor(),
// change the function declaration to this:
//
// typedef Actor ActorOrDerivedClass;
// ActorOrDerivedClass* GetActor() { ... }
//
// Next, you have to write a global function that obtains a code that will
// be put in a "SwigPtrAndTypeCode" structure to represent the derived type:
//
// %{
// int GetSwigTypeCode(Actor* a)
// {
// if (dynamic_cast<HumanActor*>(a) != NULL) return 1;
// if (dynamic_cast<RobotActor*>(a) != NULL) return 2;
// if (dynamic_cast<DogActor*> (a) != NULL) return 3;
// return 0;
// }
// %}
//
// Now, when you call GetActor() from C#, the C-side wrapper will return
// SwigPtrAndTypeCode, a special 8-byte structure, instead of Actor*.
// SwigPtrAndTypeCode contains the pointer to the Actor together with the
// type code returned from your function.
//
// Finally, the C# side will call a function that you must write called
// NewActorProxy() inside the P/Invoke class. Define it by inserting something
// like the following into your main .i file:
//
// %pragma(csharp) imclasscode=%{
// internal static Actor NewActorProxy(SwigPtrAndTypeCode p, bool memoryOwn)
// {
// switch(p.TypeCode) {
// case 1: return new HumanActor(p.Ptr, memoryOwn);
// case 2: return new RobotActor(p.Ptr, memoryOwn);
// case 3: return new DogActor(p.Ptr, memoryOwn);
// default: return new Actor(p.Ptr, memoryOwn);
// }
// }
// %}
//
// The third argument is an integer flag saying whether the object is reference-
// counted via %counted_obj. If you use %counted_obj for this type, then set
// IsCountedObj=1. They both change csout, so they are not compatible without this
// little hack.
//
// Note: this macro assumes IntPtr is used instead of HandleRef, but it only
// makes a difference if BaseClassPtrType is used as an input argument type.
//
// Finally, sometimes you may want to return more information to C++ than just
// a type code. In that case you use your own structure in place of struct
// SwigPtrAndTypeCode, and use %cs_return_derived_proxy_by_code2() to specify
// two extra arguments: the name of your structure on the C++ side, then the
// name of the structure on the C# side. Use SwigPtrAndTypeCode as an example.
%define %cs_return_derived_proxy_by_code(BaseClass, BaseClassPtrType, IsCountedObj)
%cs_return_derived_proxy_by_code2(BaseClass, BaseClassPtrType, IsCountedObj, SwigPtrAndTypeCode, $imclassname.SwigPtrAndTypeCode)
%enddef
%define %cs_return_derived_proxy_by_code2(BaseClass, BaseClassPtrType, IsCountedObj, CppPtrAndTypeCode, CsPtrAndTypeCode)
typedef BaseClass BaseClassPtrType;
%typemap(ctype, fragment="CppPtrAndTypeCode", out="CppPtrAndTypeCode") BaseClassPtrType* %{ BaseClassPtrType* %}
%typemap(in) BaseClassPtrType* %{ $1 = (BaseClassPtrType*)$input; %}
%typemap(varin) BaseClassPtrType* %{ $1 = (BaseClassPtrType*)$input; %}
//%typemap(memberin) BaseClassPtrType* %{ $1 = (BaseClassPtrType*)$input; %}

%typemap(imtype, out="CsPtrAndTypeCode") BaseClassPtrType* "IntPtr"
%typemap(cstype) BaseClassPtrType* "$csclassname"
%typemap(csin) BaseClassPtrType* "$csclassname.getCPtr($csinput)"
%typemap(csout, excode=SWIGEXCODE) BaseClassPtrType* {
CsPtrAndTypeCode p = $imcall;$excode
#if IsCountedObj
return $imclassname.New ## %mangle(BaseClass) ## Proxy(p, true);
#else
return $imclassname.New ## %mangle(BaseClass) ## Proxy(p, $owner);
#endif
}
%enddef

// REAL LIFE EXAMPLE (redacted for simplicity)

// Allow Element* to be returned using the correct proxy class.
// Not Compact Framework compatible.
%cs_return_derived_proxy_by_code2(Element, ElementOrDerived, 1, ElementPtrEtc, $imclassname.ElementPtrEtc)
%fragment("ElementPtrEtc","header") {
struct ElementPtrEtc
{
ElementPtrEtc() {}
ElementPtrEtc(Element* p) { *this = p; }

void* ptr;
ElemType elemType;

void operator= (Element* p) {
ptr = p;
if (p) {
try {
elemType = (ElemType)p->Type();
} catch(...) {}
}
}
};
}
%pragma(csharp) imclasscode=%{
// Used by %cs_return_derived_proxy
[StructLayout(LayoutKind.Sequential)]
internal struct ElementPtrEtc {
public IntPtr Ptr;
public ElemType ElemType;
}
%}
%pragma(csharp) imclasscode=%{
// Name comes from New ## %mangle(Element) ## Proxy
internal static Element NewElementProxy(ElementPtrEtc p, bool memoryOwn)
{
Element e;
switch((ElemType)p.ElemType) {
case ElemType.ITSC: e = new ItscElem(p.Ptr, memoryOwn); break;
case ElemType.NAMEDPOINT: e = new NamedPoint(p.Ptr, memoryOwn); break;
case ElemType.NAMEDLINE: e = new NamedLine(p.Ptr, memoryOwn); break;
case ElemType.NAMEDPOLYGON: e = new NamedPolygon(p.Ptr, memoryOwn); break;
default: e = new Element(p.Ptr, memoryOwn); break;
}
return e;
}
%}