NET:Inner Space Bootstrap

From Lavish Software Wiki
Revision as of 17:15, 2 December 2006 by Lax (talk | contribs)
Jump to navigation Jump to search

Overview

This article explains one process of exposing unmanaged API to .NET programs, the Inner Space bootstrap. This method uses a GetProcAddress-like mechanism that allows function versioning, and does not require DLL exports. Instead, a library is registered with Inner Space, which can be queried via LavishVM.GetAPI.

The Process

Create a .NET class library

First, select a .NET language to create the .NET portion of the API. Any language will do, as .NET libraries are designed to work across all .NET languages. I recommend C# and will use C# for examples here (though equivalents in other languages will surely be added), but again, any language will work. After selecting a language, create a class library (note: for some languages, such as C++, when creating the project in Visual Studio, you need to select the project type under CLR). This library is to be linked against by developers wishing to use your API, although they could also create their own library with essentially the same code in it for the same purpose.

Your class library should first of all provide a set of delegates that will correspond directly to equivalent API in your C++ Inner Space extension. Therefore, the prototype should exactly match the internal function.

For example
public delegate void Echo(string Output); // C# delegate
static void __stdcall Echo(const char *Output); // unmanaged C++ equivalent

Delegates default to the stdcall calling convention, but can be modified with attributes to use other calling conventions, such as cdecl. It is recommended that the C++ API function use stdcall.

After creating the delegates, the .NET API can be implemented by instantiating the delegate, with a delegate that attaches to your function by pointer. The function pointer is retrieved at runtime by calling LavishVM.GetAPI. The recommended implementation for this is to create a stub function, which will attempt to attach the API and call it, or generally return a failure result if the API could not be attached.

For Example
        static public InnerSpaceAPI.Delegates.Echo Echo = Stubs.Echo;
            static public void Echo(string Output)
            {
                IntPtr Address = LavishVM.GetAPI("Inner Space", "Echo", 1);
                if (Address.ToInt32() == 0)
                {
                    Console.WriteLine(Output);
                    return;
                }
                InnerSpace.Echo = (InnerSpaceAPI.Delegates.Echo)Marshal.GetDelegateForFunctionPointer(Address, typeof(InnerSpaceAPI.Delegates.Echo));
                InnerSpace.Echo(Output);
            }

This particular case is a special case, in which the functionality of Echo uses a surrogate: Console.WriteLine. If the API function has a return value, return the value from the stub, like this:

return InnerSpace.Echo(Output); // Note: InnerSpace.Echo has a void return value, therefore this will fail to compile.

With this implementation, the stub automatically instantiates the delegate in the Echo field with the function pointer returned by LavishVM.GetAPI, although Echo originally pointed to the stub.

A problem would occur here if the C++ library is unloaded after API is attached -- attempting to continue to use the API would cause a crash. Therefore a final portion of the .NET API, if the C++ API can be unloaded, should detach all of the API, and replace the value with the original stub. This can be implemented via Events, or otherwise calling a function in the .NET API from the C++ API at the time of unloading. Care should be taken to ensure that the C++ API is not currently in context when the DLL is unloaded.

Recommended API layout

This system is designed to work very similar to using the System libraries, in that everything can be transparent to the target developer. However, this implementation imposes a few restrictions due to the way that namespaces work. Namespaces are not allowed to contain fields, so each function implemented with our method must be a static public member of a class. The use of nested classes should be limited, as class usage cannot be implicit through the usage keyword.

namespace FooAPI
{
    namespace Delegates
    {
        public delegate void Bar();
    }

    class Foo
    {
        public static FooAPI.Delegates.Bar Bar = Stubs.Bar;

        internal class Stubs
        {
            static public void Bar()
            {
                IntPtr Address = LavishVM.GetAPI("Foo", "Bar", 1);
                if (Address.ToInt32() == 0)
                    return;
                Foo.Bar = (FooAPI.Delegates.Bar)Marshal.GetDelegateForFunctionPointer(Address, typeof(FooAPI.Delegates.Bar));
                Foo.Bar();
            }
        }

        class FooChildClass
        {
            /* same fashion as Foo */
        }
    }

    class AdditionalFooClass
    {
        /* same fashion as Foo */
    }
}

Create equivalent API in Inner Space extension

Implementing the extension API requires ISXDK v29d or later


Register the library

See Also