• entries
    146
  • comments
    436
  • views
    197746

Even More DLL goodness.

Sign in to follow this  

91 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 once

using 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].
Sign in to follow this  


2 Comments


Recommended Comments

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now