wrapping C++ API with virtual functions

G

Gerhard Prilmeier

Hello,

I have an unmanaged C++ API that uses virtual functions, like this:

class A
{
public:
virtual void handleMe(){}
};

The user would use A by inheritance:

class B : public A
{
public:
void handleMe(){ /* doSomething */ }
};

Now I need to port this API to .net, such that it is available in C#. To do
so, I will write managed wrapper classes in C++/CLI and access them from C#.
Ideally, I would also use inheritance in C# to overwrite the virtual
functions.

As managed classes can not inherit from unmanaged classes, I'm not sure how
to go on. Seems like some workaround is required. Can you help?
Sorry if this question has been asked before, it seems like a usual problem.
However, I could not find any good tutorials on the web. Maybe you could
just point me to some resource?

Thank you very much,
Gerhard
 
W

William DePalo [MVP VC++]

Gerhard Prilmeier said:
I have an unmanaged C++ API that uses virtual functions, like this:
...
Now I need to port this API to .net, such that it is available in C#. To
do so, I will write managed wrapper classes in C++/CLI and access them
from C#. Ideally, I would also use inheritance in C# to overwrite the
virtual functions.

As managed classes can not inherit from unmanaged classes, I'm not sure
how to go on. Seems like some workaround is required. Can you help?

Here is a sketch of one way to go:

Write a managed class which has one public method for every public member
function in the native base class. The constructor of that class then
creates an instance of the native object somehow - (P/Invoke, C++ Interop
aka IJW). A pointer to the native object is stored as a private member of
the managed class. The implementation of every public member function of the
managed class delegates the call to the corresponding method on the native
object again using some interop technique.

That done, your clients derive their C# classes or VB.Net classes or
whatever from your managed classes.

Regards,
Will
 
D

David Wilkinson

William said:
Here is a sketch of one way to go:

Write a managed class which has one public method for every public member
function in the native base class. The constructor of that class then
creates an instance of the native object somehow - (P/Invoke, C++ Interop
aka IJW). A pointer to the native object is stored as a private member of
the managed class. The implementation of every public member function of the
managed class delegates the call to the corresponding method on the native
object again using some interop technique.

That done, your clients derive their C# classes or VB.Net classes or
whatever from your managed classes.

Will:

Maybe I am missing something here, but what is the mechanism by which
polymorphism of the managed class "piggy-backs" onto that of the
unmanaged class? I can imagine a mechanism, but you didn't explain that
part (or if you did I missed it).

David Wilkinson
 
B

Ben Voigt

David Wilkinson said:
Will:

Maybe I am missing something here, but what is the mechanism by which
polymorphism of the managed class "piggy-backs" onto that of the unmanaged
class? I can imagine a mechanism, but you didn't explain that part (or if
you did I missed it).

It can be done, but it's going to be *ugly*.

First, you'll need a managed wrapper class that declares all the same
virtual functions, and associates with a pointer to the native class type
(the native peer).
Then, a derived native class (but built with /clr) that accepts an instance
of the managed class (the managed peer), and forwards all virtually called
functions to the peer's member functions.
Now, you'll need to implement the managed base class's functions. Each
virtual function in the managed class, needs to call the native peer's
function non-virtually, so it will look like calling the base class
implementation of the function if the managed class had been able to
directly derive from the native base class.

Then, every instance of the managed class or any subclass thereof, will have
an associated native forwarder which is derived from the native class and
therefore can be used in polymorphic containment, etc. However, you will be
switching between native and managed code frequently, and you may even have
to do conversions and re-conversions of things like strings to make the
managed API sane.

Something like (adjust for IDisposable if appropriate):

class Original
{
virtual void CallMe();
};

public ref class Managed
{
Original* peer;
public:
Managed(Original* p);
virtual void CallMe();
!Managed();
};

class Shim : public Original
{
gcroot<Managed^> peer;
public:
Shim(Managed^ m) : Original(), peer(m) {}
virtual void CallMe() override { peer->CallMe(); }
};

void Managed::CallMe()
{
peer->Original::CallMe(); // looks like base::CallMe(), sort of
}

Managed::Managed()
: peer(new Shim(this))
{}

Managed::!Managed()
{ delete peer; }
 
T

Tamas Demjen

Gerhard said:
Ideally, I would also use inheritance in C# to overwrite the virtual
functions.

Will has already explained how you encapsulate native classes inside
managed wrappers. However, your override requirement is tricky to implement.

For example, consider the following sample:

class Native
{
public:
void ExecuteAll()
{
for(int i = 0; i < 5; ++i)
OnItem(i);
}
virtual void OnItem(int i) { }
};

If you want to make OnItem overridable in the managed wrapper, you have
to do some extra work:

ref class Managed; // forward declare

class NativeProxy : public Native
{
public:
NativeProxy(Managed^ managed_source) : managed(managed_source) { }
protected:
virtual void OnItem(int i);
private:
gcroot<Managed^> managed;
};

public ref class Managed
{
public:
Managed() : native(new NativeProxy(this)) { }
~Managed() { delete native; }
protected:
!Managed() { delete native; }

public:
void ExecuteAll() { native->ExecuteAll(); }
protected:
virtual void OnItem(int i) { }

private:
NativeProxy* native;
};

void NativeProxy::OnItem(int i) { managed->OnItem(i); }

This looks pretty straightforward: I simply set up a mechanism that
redirects Native::OnItem to Managed OnItem, so it can be overriden
later. The only problem is that it doesn't compile:

'Managed::OnItem': candidate function(s) not accessible

Unfortuantely I can't declare NativeProxy a friend of Managed, it's
illegal in C++/CLI (don't quite understand why). Fortunately .NET has a
special access modifier called public protected, which means public from
the current unit and protected from everywhere else. With the following
modification, it works fine:

public ref class Managed
{
[...]
public protected:
virtual void OnItem(int i) { }
[...]
};

This eliminates the need of declaring the proxy a friend.

This brings to another big problem. I saw that your virtual function is
declared public, so I intentionally did the same and declared
Native::OnItem public, which is a bad design in this case, as it should
have been protected. That being public greatly complicates matters,
because Native::OnItem can be called publically and internally as well.
I intentionally did not declare Managed::OnItem public, because that
would break the entire wrapper.

If your expectation is that your virtual function can be called
publically, you can't simply make Managed::OnItem public. The following
would cause an infinite recursion:

ref class Managed
{
[...]
public:
virtual void OnItem(int i) { native->OnItem(i); } // stack overflow!
[...]
};

What happens here is that Managed::OnItem calls NativeProxy::OnItem,
which in turn calls Managed::OnItem, and it goes on until you run out of
memory.

If you insist that your virtual function must be available publically,
you really have to name that function differently, and preferably
declare it non-virtual:

ref class Managed
{
[...]
public:
void PublicOnItem(int i) { native->OnItem(i); } // non-virtual
[...]
};

To test this class at runtime, I implemented OnItem:

void Managed::OnItem(int i) { Console::WriteLine(i); }

and wrote a command line app around it:

int main(array<String ^> ^args)
{
Managed m;
m.ExecuteAll();
}

It should display on the console window
0
1
2
3
4


Final note: First I was trying to use auto_gcroot in the NativeProxy,
but it crashed with stack overflow. It seems like Managed dtor called
auto_gcroot dtor, which in turn called Managed dtor again. This is one
of those cases when gcroot should be used, not auto_gcroot (I'm still
confused when to use which).

Hope this helps, not sure if anyone has anything to add to it. I just
wanted to show a complete example to the original poster (and it was a
great excercise to myself as well).

Tom
 
W

William DePalo [MVP VC++]

David Wilkinson said:
Maybe I am missing something here, but what is the mechanism by which
polymorphism of the managed class "piggy-backs" onto that of the unmanaged
class? I can imagine a mechanism, but you didn't explain that part (or if
you did I missed it).

Ah, sorry. Seems I missed one of the OP's requirements.

Normally, penance for that would require that I fill in the blanks with
code. Happily, Ben and Tamas have already paid the price for me. :)

Regards,
Will
 
G

Gerhard Prilmeier

I'd like to thank all of you for the helpful discussion and the efforts you
made! Things became much clearer.

Best regards,
Gerhard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top