Friday, January 30, 2009

Using Unmanaged Arrays in C#

Yesterday I faced an interesting problem I had not dealt with in a real world scenario before. We hired the services of a C++ developer to create a native C++ class for us that would allow our applications to control some of the cameras we use for street level imagery collection and processing. And when I received the first version of the DLL for testing I realized that my team was a pure C# team with little to no exposure to C++.

After testing the DLL in native C++ and asserting it had the desired performance and reliability I was ready to wrap it using a managed C++ class. I had done that several times and was confident I would be done in minutes. I openned the UnmanagedClass.h file to see the method signatures and thought it was going to be a piece of cake. The code segment below represents part of the UnmanagedClass.h file.

UnmanagedClass(void);
virtual ~UnmanagedClass(void);
unsigned int GetNumberOfImages();
bool InitializeConvert(string a);
bool CreateJPGs(int selectedImagesToProcess[]);


With that information in hands I created my ManagedClass.cpp file with the following code:

public ref class ManagedClass{
public:
ManagedClass(){o = new UnmanagedClass();}
virtual ~ManagedClass(){o->~UnmanagedClass();}
bool InitializeConvert(System::String ^ a){
return o->InitializeConvert(a);}

bool CreateJPGs(int selectedImagesToProcess[]){
return o->CreateJPGs(selectedImagesToProcess);}


private:
UnmanagedClass * o;
};



The initial problem I faced was the fact that the managed class used System::String and the native class used std::string. I did not want to have our C# developers use std::string and decided to let them pass in a managed string data type and handle the conversion in that wrapper class (ManagedClass). I did some digging and found some old code from my teaching days to do that. I remember that was copied straight from MSDN. Here's the InitializeConvert method after changes:


bool InitializeConvert(System::String ^ a){
string s;
using namespace Runtime::InteropServices;
const char* x = (const char*)(Marshal::StringToHGlobalAnsi(a)).ToPointer();
s = x;
Marshal::FreeHGlobal(IntPtr((void*)s));
return o->InitializeConvert(a);

}

Finally, I was ready to tackle the CreateJPEGs method. The issue here is that the method expects an unmanaged array. I tried to solve the issue inside the wrapper class but after some reasearch I found an awesome article on codeproject that explained how, and more importantly, why, I needed to use unamanged arrays. You can see the article here.

Following the article I created the exact same class they refer as example, copied below.


public ref class Unmanaged abstract sealed
{
public:
generic where T : value class
static void* New(int elementCount)
{
return Marshal::AllocHGlobal(sizeof(T) * elementCount).ToPointer();
}
generic where T : value class
static void* NewAndInit(int elementCount)
{
int sizeInBytes = sizeof(T) * elementCount;
void* newArrayPtr = Marshal::AllocHGlobal(sizeInBytes).ToPointer();
memset(newArrayPtr, 0 , sizeInBytes);
return newArrayPtr;
}
static void Free(void* unmanagedPointer)
{
Marshal::FreeHGlobal(IntPtr(unmanagedPointer));
}
generic where T : value class
static void* Resize(void* oldPointer, int newElementCount)
{
return Marshal::ReAllocHGlobal(IntPtr(oldPointer),
IntPtr((int) sizeof(T) * newElementCount)).ToPointer();
}
};


With that there were no changes required for my wrapper. All I needed now was to teach the C# developers how to use the Unmanaged class to create an unmanaged array and pass that as a parameter to the wrapper. With that said, here's a piece of C# code using the wrapper class:

ManagedClass pgr = new ManagedClass();
unsafe{
int selectedItems = lstImages.SelectedItems.Count; //lstImages is a listbox
int* imagesToProcess = (int*)Unmanaged.NewAndInit(selectedItems);
int i = 0;
foreach (int index in lstImages.SelectedIndices)
imagesToProcess[i++] = index;
pgr.CreateJPGs(imagesToProcess);
Unmanaged.Free(imagesToProcess);

}

Finally I had some code I could use from C# to use the native C++ class created by our vendor.

No comments:

Post a Comment