Welcome to Atalasoft Community Sign in | Help

Using managed delegates from unmanaged code

We've done a fair amount of work that involves the interchange of managed and unmanaged code.  For most cases, the programming model makes it straightforward, if not simple to handle.

90% of the time, all you need to do is call an unmanaged function, have it perform some work and then it returns with your result.

This is simple.  If you use P/Invoke to get into the unmanaged code, the marshalling of data gets handled for you, including the sticky bits of translating strings back and forth.

If you use managed C++ paired with unmanaged C++, you need to do the marshalling of data yourself, but again, in most cases it's very straightforward.  Strings and arrays can be problematic, but I'll show you a trick that I use for working with strings.

Let's say that you have an external unmanaged function:

extern bool UnmanagedIsValidUsername(char *name);

and you need to call this from managed C++.  Typically, you would just convert the managed string using Marshal::StringToHGlobal(), but you need to remember to dispose the memory when you're done.  If you're like me, you forget about memory and you end up adding a memory leak to your code.  Oops.  I avoid that with this pattern:
__nogc class StringAdapter {
private:
    char *m_str;
    char __nogc *StringToCharStar(System::String *str) {
        return (char*)(void*)Marshal::StringToHGlobalAnsi(str);
    }
public:
    StringAdapter(System::String *str) {
        m_str = StringToCharStar(str);
    }
    virtual ~StringAdapter() {
        Marshal::FreeHGlobal(m_str);
    }
    operator const char *() { return m_str; }
    operator char *() { return (char *)m_str; }
};

This is a class called StringAdapter which is meant to be used from within managed code and allocated on the stack:

bool ManagedClass::IsValidUsername(String *managedStr)
{
    StringAdapter s(managedStr);
    return UnmanagedIsValidUsername(s);
}

In this code, we convert the string in the constructor of StringAdapter and the use of operator char * and operator const char * takes care of making it possible to use the class wherever we would use a char *, in this case the call to UnamangedIsValidUsername().  As an added bonus, the destructor in the class gets called whenever the code leaves the current method context, ensuring that the adapter frees its memory and doesn't leak.  This also works when an exception is thrown.

Where things get really tricky is when you have delegates.  Delegates are meant to be used in a particular way: you pass a delegate into an unmanaged function, it gets magically converted into a function pointer on the way in, gets called from the unmanaged function, the delegate does its work and then returns to the unmanaged code, then the unmanaged code returns.

Under these circumstances, everything works great.  From P/Invoke, the magical conversion creates a thunk that when called from unmanaged code does a transition back to managed land.  It is this magic conversion that we really need to be concerned about.

What if, instead of being called within the context of the unmanaged code, the unmanaged code squirrels away a copy of the magically converted delegate so that it can call it later.

In some code systems, it is possible to install function pointers to configure global behaviors of the entire system.  For example, suppose you have a code system that wants to put up a warning dialog box.  It could define a function pointer to do that and expose an API to set it.  There would be nothing wrong with sending in a delegate to do that work using the .NET tools.

The problem is that sometimes that magically converted delegate will go stale.  Most notably, in IIS sometimes your ASP.NET code gets put into its own execution environment separate from other assemblies that you provide.  Delegates need to do some clever marshalling to go across the barrier between environments.  On top of that IIS might run several instances of your code in several different execution environments, each requiring a different marshalled delegate in order to work correctly.  In .NET 2.0 in IIS, the delegate can go stale fairly often.  With some of our technology, we were inducing staleness too much to discount and as a result of a race condition.  Ecch.

In addition, we discovered in .NET 1.1, there are cases where the memory for the magically created function pointer could get garbage collected.  Apparently, there was a separate heap used for these thunks and it too could get garbage collected.

Long story short - keeping a globally thunked delegate in unmanaged code will eventually die a horrible death in some .NET hosting environments.  Best to avoid using global delegates entirely.

One last thing: if you need to hand-marshall the delegate in C++, you will find that there is no direct API for doing that.

Basically, you have to create a singleton struct to hold your delegate:
typedef bool (*fMyCallback)(unsigned long uUserData);

__delegate bool MyCallback(__int32 uUserData);

__gc struct MyCallbackWrapper {
    [MarshalAsAttribute(UnmanagedType::FunctionPtr)]
    MyCallback *m_call;
};

__nogc struct MyCallbackWrapperUnmanaged {
    fMyCallBack m_callUnmanged;
};

fMyCallBack MakeUnmanagedCallback(MyCallback call)
{
    __gc struct MyCallbackWrapper *wrapper = new MyCallbackWrapper();
    wrapper->m_call = call;
    MyCallbackWrapperUnmanaged wrapperUnmanaged;
    Marshal::StructureToPtr(wrapper, &wrapperUnmanaged, false);
    return wrapperUnmanaged.m_callUnmanaged;
}


Phew.
So what you do is make a structure that contains a member of your delegate's type and a parallel structure that contains a function pointer of your unmanaged type, call StructureToPointer and then return the unmanaged type.

Incidentally, you don't technically need a parallel structure - you could just use the address of an unmanaged void * and cast that to what you want.

Published Friday, July 28, 2006 10:44 AM by Steve Hawley

Comments

No Comments
Anonymous comments are disabled