Using proxies for Managed/Unmanaged inheritance

Published June 20, 2005
Advertisement
Inheriting Managed classes from Unmanaged classes.
First of all, you can't. You can fudge it, however, in such a way that the user of your library will be unable to tell that they aren't inheriting from the unmanaged class.

For the examples I will be using here, we will be strictly inheriting from an interface. You could extend the system, if you wanted, to include inheriting from actual classes. Another thing that should be noted is that the examples I use here are just that, examples. In reality you would never stick such a simple class in a DLL and expose it via an interface. It's just plain wasteful.

Off on a tangent for a second, one of the many issues one finds with books and tutorials are the examples. One of the problems that authors face is the challenge of writing examples that suitably demonstrate the concepts without overburdening the reader with detail about the system being used (for instance, an example that uses bank accounts will typically not include the code for the bank, but instead small snippets where needed). You can easily see this problem just by perusing my journal. Most of the examples I use are very contrived (such as the ones you will find in this entry), but they demonstrate the concepts that I am concerned about fairly succinctly.

First up, we're going to be providing a list class from a DLL. This list class will enable users to add, remove, sort and print the elements of the list (integers in this case). Sorting of the list will be done through an interface pointer provided by the user of the DLL.
//Something.h#pragma once#ifdef MIXEDDLL_EXPORTS#define DLLIMP extern "C" __declspec(dllexport)#else#define DLLIMP extern "C" __declspec(dllimport)#endifstruct IComparer {	virtual bool Compare(int, int) = 0;};struct ISomething {	virtual void AddEntry(int) = 0;	virtual void RemoveEntry(int) = 0;	virtual void Sort(IComparer* other) = 0;	virtual void PrintList() = 0;	virtual void Release() = 0;};DLLIMP ISomething* GetSomething();//SomethingImpl.h#pragma once#include "Original.h"#include class SomethingImpl : public ISomething {public:	virtual void AddEntry(int entry);	virtual void RemoveEntry(int entry);	virtual void Sort(IComparer* other);	virtual void PrintList();	virtual void Release();private:	std::vector<int> entries;};//SomethingImpl.cpp#include "SomethingImpl.h"#include #include struct ComparerFunctor {	ComparerFunctor(IComparer* comparer) : comparer(comparer) {}	bool operator()(int lhs, int rhs) {		return comparer->Compare(lhs, rhs);	}private:	IComparer* comparer;};templatestruct ListPrinter {	void operator()(T const& entry) {		std::cout<	}};void SomethingImpl::AddEntry(int entry) {	entries.push_back(entry);}void SomethingImpl::RemoveEntry(int entry) {	entries.erase(std::remove(entries.begin(), entries.end(), entry), entries.end());}void SomethingImpl::Sort(IComparer* other) {	std::sort(entries.begin(), entries.end(), ComparerFunctor(other));}void SomethingImpl::PrintList() {	std::for_each(entries.begin(), entries.end(), ListPrinter<int>());}void SomethingImpl::Release() {	delete this;}ISomething* GetSomething() {	return new SomethingImpl();}

Well, as you can see, it's a fairly simple implementation. Nothing really spectacular to notice here. The one thing that you should note is that I wrap up the interface pointer in a functor and pass it to a sort algorithm. Since we assume that the user of the DLL is managing the memory of the interface, it doesn't matter if the pointer gets copied around a few times inside of the functor.
At this point, we can easily use the DLL in an unmanaged application, however for a managed application, it won't be quite so simple. To be useful for our managed applications we will have to expose the ISomething interface to a managed application, along with the IComparer interface in such a way that a managed application can provide their own comparison classes without having to write an unmanaged wrapper. First we write a simple interface that mimics that of our unmanaged counterpart, and then provide an unmanaged wrapper that will expose that interface to the unmanaged code:
public interface class IComparer {public:	bool Compare(int lhs, int rhs);};struct ManagedComparerWrapper : ::IComparer {	ManagedComparerWrapper(gcroot comparer) : comparer(comparer) {}	bool Compare(int lhs, int rhs) {		return comparer->Compare(lhs, rhs);	}private:	gcroot comparer;};

Note how the unmanaged wrapper simply delegates the call to the managed handle that was passed to it. You should also note that this wrapper has been derived from the unmanaged IComparer interface and hence can be used in with our unmanaged list.

Next we wrap up the ISomething interface, providing it with a simple wrapper that just delegates the majority of the functionality to the existing classes.
public ref class Something sealed {public:	Something() {		something = new SomethingImpl();	}	~Something() {		if(something) {			delete something;			something = 0;		}	}	!Something() {		this->~Something();	}	void AddEntry(int entry) {		something->AddEntry(entry);	}	void RemoveEntry(int entry) {		something->RemoveEntry(entry);	}	void Sort(IComparer^ comparer) {		ManagedComparerWrapper wrapper = ManagedComparerWrapper(comparer);		something->Sort(&wrapper);	}	void PrintList() {		something->PrintList();	}private:	ISomething* something;};

Things you should notice here: The Sort simply creates a stack allocated wrapper and passes it the managed comparison interface handle. It then passes the address to the sort function of the unmanaged interface. Also you should note the destructor and the finalizer, and how they are used to release the unmanaged resources.

Finally, what good is an example without something to use it, so here's a simple application that makes good use of our hard work:
using System;namespace ManagedTest {	class Program {		static void Main(string[] args) {			ManagedWrapper.Something something = new ManagedWrapper.Something();			Random r = new Random();			for(int i = 0; i < 10; ++i) {				something.AddEntry(r.Next(100));			}			Console.WriteLine("Unsorted:");			something.PrintList();			Console.WriteLine("Sorted:");			something.Sort(new TestComparer());			something.PrintList();		}	}	class TestComparer : ManagedWrapper.IComparer {		public bool Compare(int lhs, int rhs) {			return lhs < rhs;		}	}}


You could, of course, do the opposite of this. Which is to say, you could make unmanaged classes appear to derive from managed classes by using proxies.
Previous Entry Managed DLL goodness.
Next Entry Coding Guidelines
0 likes 6 comments

Comments

paulecoyote
Interesting stuff [grin]
June 21, 2005 03:48 AM
Saruman
Tonight will be the first time trying to inherit from an unmanaged class for me.. so the question flood will commence. I was waiting for this post before I attempted it hehehe.

Although it should be a relatively simple case in my situation, the only unmanaged class I need to inherit at this point is a simple IGameState interface with 4 pure virtuals.
June 21, 2005 11:55 AM
Saruman
Washu, tyvm :)

My engine now has the basics wrapped and is running in C# letting me build the tools. This rocks :)
June 23, 2005 12:34 AM
NickGeorgia
I don't understand yet, but I will someday or I will dll_exec myself and return false.
June 27, 2005 06:13 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement