• entries
146
436
• views
198153

Even More DLL goodness.

96 views

Inter-operating between your DLL and .Net
Seeing as how the .Net framework is my specialty, I thought I would cover inter-operating between the DLL we wrote last time and a .Net application.

First of all, interop isn't a particularly nice thing to look at. It usually involves a lot of code to perform mostly trivial conversions from one data type/heap to another. Strings are also less than pleasant to deal with, as they do not have a clean and simple conversion from managed to unmanaged. For that reason, I've written the following functions:
void ConvertToUnmanagedString(std::string& unmanaged_str, String^ str) {	IntPtr stringPtr = Marshal::StringToHGlobalAnsi(str);	unmanaged_str = std::string(reinterpret_cast<char const*const>(stringPtr.ToPointer()));	Marshal::FreeHGlobal(stringPtr);}void ConvertToUnmanagedString(std::wstring& unmanaged_str, String^ str) {	IntPtr stringPtr = Marshal::StringToHGlobalUni(str);	unmanaged_str = std::wstring(reinterpret_cast<wchar_t const*const>(stringPtr.ToPointer()));	Marshal::FreeHGlobal(stringPtr);}

They help to hide the nasty details of marshaling strings from managed to unmanaged code. Thankfully, the opposite is quite easy, since the System::String class has a constructor that takes a char const* and the wide character variant as well.

We're going to be bringing our interface that we wrote earlier (IStrange) into a .Net application. As such we need to wrap it up in a managed wrapper that deals with the details of marshaling the data between managed and unmanaged code. Without further delay, I present ManagedWrapper.Strange:
#pragma onceusing namespace System;using namespace System::Runtime::InteropServices;#include "../SimpleDll/Simple.h"#include "ConversionUtil.h"namespace ManagedWrapper {	public ref class Strange sealed {	public:		Strange() {			strange = GetStrange();		}		~Strange() {			if(strange) {				strange->Release();				strange = 0;			}		}		String^ GetName() {			return gcnew String(strange->GetName().c_str());		}		void PrintString(String^ str) {			std::wstring tmpstr;			ConvertToUnmanagedString(tmpstr, str);			strange->PrintString(tmpstr);		}	protected:		!Strange() {			this->~Strange();		}	private:		IStrange* strange;	};}

Note how it uses the constructor to call the GetStrange() function from the unmanaged DLL. Also note how it uses a destructor (which in C++/CLI is implemented as a IDisposable) to release the unmanaged resource. Finally you will note that the finalizer calls the destructor, which ensures that the unmanaged resource is freed. You must call the destructor (Dispose method) from the finalizer to ensure that the strange object is released. The destructor will not be called by default.
You should also see that it uses one of those System::String constructors in the GetName method, and uses our handy conversion function in PrintString.

Of course, this managed wrapper is incomplete without an application to use it, so here goes:
using ManagedWrapper;namespace ManagedApp {	class Program {		static void Main(string[] args) {			Strange s = new Strange();			s.PrintString(s.GetName());		}	}}

Beautifully simple to use isn't it? Just ensure that the DLLs are all in the right places before trying to run it [grin].

I've fallen in love with C#. I don't think I can turn back to C++ again.

You will if you need to wrap up some native code in CLI/C++ to use in C# :)