Interop Without PInvoke - Consuming Native Libraries in C#.

Recently I have been working on a project where we have a few separate native libraries (built in C++) that we have to consume from a managed (C #) architecture.  I haven't been swimming in the C++ pool for a few years and have taken a dive in this weekend and spent time refamiliarizing myself with C++ and creating a few examples of consuming native libraries without using PInvoke.  Fortunately we are writing code for an appliance so we have knowledge and control over the hardware we are deploying on.  I'll include a few sample projects to get you started consuming either a native static library (*.lib) or a native dynamic library (*.dll) .

There are a couple different ways to approach native to managed interop.  One of them is to just PInvoke directly into a native *.dll from C#.  While this would work it feels a bit brutish to me and has some performance issues.  The exposed API that I need to consume is much more complex than the consuming app needs and I'll be writing a Facade to consume the native functionality whichever route is taken.  Repeatedly marshalling the entire surface area of classes in the native API seems like a huge hit to take for the small slice of functionality that I'll actually be consuming and performance is something we have to take into consideration for this project.

This is a perfect case for building the Facade directly into the interop layer in C++/CLI and compiling in mixed mode for consumption by C#.  Building the Facade in C++/CLI gives us more granular control over the calls made across the boundary which is called a "thunk" and is pretty expensive.  Generally speaking, getting "thunked" on the head by a native can be painful experience and should be avoided as much as possible.  By putting the logic of the Facade in native code helps in architecting a solution where we can keep the majority of logic on the "dark side" (native) and only marshal a minimal set of calls across the boundary back to the managed side.  This optimization is not really possible with the PInvoke approach.

image001.png


PInvoke - requires marshaling a larger surface area than we need to consume across the native to managed boundary.

image002.png

In the interop layer, all of the implementation in the Facade would be on the Managed side and we would have to marshal coarse calls across to the native side more often.

 

versus

image003.png

C++/CLI facade - enables tighter control over what passes across the native to managed boundary.

image004.png

In the interop layer, most of the implementation of the Facade would be on the native side and we have the opportunity to build the interface in a way to reduce the number of times we have to marshal across the boundary and can make the calls more granular.

 

Scenario I.  Native as a static library (.lib) on 64 bit machine.

I'll go over an over-simplified sample so you can take the solution appearing with this article as a template for working on your projects or just to poke around and learn how to build this kind of solution.  The details of designing the Facade is a subject for a whole article in itself and not something I'll really dig into.  The important thing is getting set up to experiment with the interop layers.

Let's say we have a static native library (.lib) in C++ with the following header:

namespace Native
{
      class IntGetter
      {
      public:
            IntGetter(void);
            ~IntGetter(void);
            int GetInt();
      };
}

Because we are dealing with a static library, it will be pulled into our Facade layer so when we compile we'll just end up with one *.dll for the C# project to consume.

Here is a sample layer that will marshal our native C++ "int" to a CLR System.Int32 type.  Because the type is bittable, the transition across the boundary is relatively simple and we can have native and CLR constructs in the same C++ class.  This sample is ultra-simple and keep in mind that more complex types will require a bit more work to push over the boundary but that is a larger topic and there are many books and articles written on the subject of marshaling types across the native to managed boundary.

namespace Facade
{
      public ref class Getter
      {
            public :

                  System::Int32 GetInt()
                  {
                        Native::IntGetter * getter = new Native::IntGetter();
                        return getter->GetInt();
                        delete getter;
                  }
 
      };
}

Finally, in C# we want to be able to consume the library.

class Program
{
    static void Main(string[] args)
    {
        Facade.Getter obj = new Facade.Getter();
        Console.WriteLine(obj.GetInt());
    }
}

In order to make all of this work there are a couple things to keep in mind.  If the target machine platforms do no line up for each project, we will get a runtime exception:

System.BadImageFormatException was unhandled

Message="Could not load file or assembly '[***]' or one of its dependencies. An attempt was made to load a program with an incorrect format."

To solve this we need to make sure the target machine platform in our Facade layer is lined up with the target machine in our managed project

image005.jpg 

C++ project settings

image006.jpg

C# project settings

The Facade interop layer can now be compiled with the /clr setting which produces a mixed-mode assembly containing both native and managed code.

image007.jpg

C++ project setting

This mixed mode *.dll can be consumed from our C# code just by adding a project reference.

The project attached to this article StaticLib.zip will give you a starting point for experimenting with consuming static libraries with C# and will hopefully help you start "thunking" a bit more efficiently.

Scenario II. Consuming a native dynamic library (*.dll) with C#.

The code for these two approaches are almost identical. The additional problem we'll face with consuming a dynamic library is that the code in the native *.dll won't be rolled into the Facade layer and we have to explicitly keep track of the dynamic library file.  If the *.dll is unavailable, we will get a runtime exception.

System.IO.FileNotFoundException was unhandled

Message="The specified module could not be found. (Exception from HRESULT: 0x8007007E)"

There are many ways to fix this.   For this sample, as part of the C# build process, we will copy over the *.dll as a pre-build step.

copy "$(SolutionDir)$(ConfigurationName)\Native.dll" "$(TargetDir)Native.dll"

image008.jpg

Another possible alternative would be to set the output directory of the *.dll during its build process.  The 'best' solution would really depend on how the projects are set up in your environment.

Again, we have the requirement we saw in the static library example where the C# platform target must be the same as what the C++ target was compiled for or we'll get a runtime exception.

The solution attached to this article DynamicLib.zip has the source for you to start experimenting with loading a dynamic native *.dll and consuming the functionality with C#.

I hope you find this article and the sample solutions useful.

Until next time,
Happy coding

References:

·         Intro to C++/CLI: http://msdn.microsoft.com/en-us/magazine/cc163681.aspx

·         PInvoke tutorial: http://msdn.microsoft.com/en-us/library/aa288468(VS.71).aspx

·         Performance consideration for interop: http://msdn.microsoft.com/en-us/library/ky8kkddw.aspx

·         Improving interop performance: http://msdn.microsoft.com/en-us/library/ms998551.aspx

B


Similar Articles