Welcome to Atalasoft Community Sign in | Help

How to Build a Managed/Unmanaged Library

If you are tasked with exposing an unmanaged library through managed code, there are several approached that you can take.  The approach you take will depend upon what format your unmanaged code is in.

If you are given an unmanaged dll, it is sensible to simply use P/Invoke to expose the functionality.  This will work and will get you going quickly, but it leads to a problem of how to make sure that the dll you have is loaded.  If you leave it to the OS, this means that the unmanaged dll needs to live in the same folder as the calling assembly or in the system folder.  You can also change the path that is searched for loading a dll by calling a P/Invoke of SetDllDirectory, or you can manually load the dll in your own code.  If you do that, your dll loader needs to be called before the first P/Invoke into the dll.

Using P/Invoke works, but may be costly in the marshaling, especially if you have to call the routine repeatedly.

What I prefer to work with is an unmanaged static library.  With that, you can build a managed C++ wrapper that exposes the functionality that you need.  The C++ compiler does some fairly amazing things in terms of knowing when to do unmanaged/unmanaged transitions, but sometimes it does some surprising things that will cost.

Let's assume that you are a good developer and have built a customer management API using STL collections.   You might expose a method like this:

public __gc class CustomerRelations {
public:
    bool IsCustomerAvailable(String *name)
    {
        StringAdapter cstringName(name);
        vector<Customer *> customers = GetCustomers(); // lib call
         for (int i=0; i < customers.size(); i++) {
           if (cusomters[i]->MatchesName(name))
            return true;
         }
         return false;
   }
};

The problem here is that the API is chatty - it's nice to have the interface right to the metal of the vectory, but the question is, when this is compiled and linked, will the STL code be managed or unmanaged.  Worse than that, if the compiler generates a managed version of one of the STL routines, it will use it in all the unmanaged code as well.  In other words, code that you thought was supposed to be pure unmanaged will be doing very granular transitions between managed and unmanaged calls, and that will be be pretty much out of your control.

One way around this is to make sure that all code that is touching unmanaged APIs is also unmanaged.  This can be handled by writing classes or top level functions that are themselves unmanaged and less chatty.

This works, but is prone to error - if you screw up, you may inadvertently tank your performance.  I found this out, by the way, by stepping into an unmanaged library routine with unmanaged debugging off.  The debugger kept executing code until it hit the next unmanaged frame, which was in the middle of deep library code in STL.

Here's the flawless solution - make the exposed API as chunky as possible.  It's really tempting to give direct access to collections, but this is problematic.  It's far better, if you can, to use managed collections to hold your unmanaged objects.

One approach to make sure that you don't expose anything is to use the old-school C trick of making your API completely opaque.  You can do this by making two header files, one public and one private.  The public one defines opaque types:

// public API

#pragma managed(push, off)

namespace StupendoCustomer {
typedef struct t_CustomerStruct *t_CustomerHandle;
extern t_CustomerHandle GetCustomerByName(wchar_t *name);
extern bool IsCustomerAvailable(wchar_t *name);
extern INT32 GetCustomerCount();
extern void GetCustomers(t_CustomerHandle *arr, INT32 startIndex, INT32 count);
} // namespace


#pragma managed(pop)

 

// private API - in a different file
namespace StupendoCustomer {
const UINT32 kCustomerMagicNumber = 0x00C5708e6; // arbitrary
typedef struct t_CustomerStruct {
    UINT32 m_magic;
    CustomerObject *m_customer;
};
} // namespace

 

In this case, managed  code will see nothing more than an anonymous struct for the customer, whereas unmanaged code gets the actual definition of the struct.  The m_magic field is a trick to put in a sanity check so that if the lower level API gets passed a pointer to something other than the real structure, it can be checked quickly.

The GetCustomerCount/GetCustomers pair is a way to show how you can hide the collection code by letting the caller allocate space for the array and then fetch elements from it.  The calling code, presumably managed, would most likely wrap the objects in its own class and make a collection of those objects.

Beware that you understand who owns the pointer to the struct and who is responsible for disposing it (might also be a job for smart pointers).  Chances are that if you're writing a managed wrapper for the customer object, it will have to implement IDisposable.

Published Wednesday, July 02, 2008 9:55 AM by Steve Hawley

Comments

Thursday, July 03, 2008 12:45 AM by DotNetKicks.com

# How to Build a Managed/Unmanaged Library

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Thursday, July 03, 2008 11:43 AM by Visual Studio Hacks

# Visual Studio Links #46

My latest in a series of the weekly, or more often, summary of interesting links I come across related to Visual Studio. Greg Duncan posted a link to the DevExpress announcement of AgDataGrid Suite Beta 1 , available free of charge to Silverlight developers

Anonymous comments are disabled