Wednesday, June 24, 2009

How to use C++ APIs in C# application?Presenting a C++ library (.lib) for use in C# project?

Presenting a C++ library (.lib) for use in C# project

Q:
I have a collection of C++ programs and the C++ libraries they use. The libraries currently compile to a .lib format. Changing the project property to use a .dll results in multiple reference errors.

Without having to go through the entire collection of source files, is there a way to create any kind of project in a .NET language that would provide a "wrapper" for the existing .lib files so that they could be used in that environment.

Thanks.

A:

Yes (sort of) but it's non-trivial.

Let's start with some background. Your project to compile to a .lib format is generating something known as a "static library". Static libraries are chunks of native code built to be combined with other pieces of code via the linker. You #include a header file that describes the functions/classes that you want to use and then link to the .lib file to provide the implementations of those functions/classes.

You can't use that approach with a C# client because C# generates managed code and you just can't directly link native code to managed code.

There are three ways (I can think of) to consume native code from a managed code client

  1. You turn the native code into a native code COM object using the static library to provide the implementation of the COM object's methods and then use COM Interop to invoke the (native) COM object from the managed code client
  2. You turn the native code into a native code DLL that exports some suitable entry points (again, using the static library to provide the actual implementation) and then call those DLL entry points from the managed code client using P/Invoke
  3. You create a managed code Class Library using C++ and use the static library to provide the underlying implementation of that managed class library

Let's say your static library's header file looks like this




namespace MathFuncs
{
class MyMathFuncs
{
public:
// Returns a + b static double Add(double a, double b);
// Returns a - b static double Subtract(double a, double b);
// Returns a * b static double Multiply(double a, double b);
// Returns a / b // Throws DivideByZeroException if b is 0 static double Divide(double a, double b);
};
}

Using approach #1, you'd create a new project using the Visual C++ | ATL | ATL Server project template. You'd add a new simple ATL object to the project that exported Add, Subtract, Multiply and Divide methods. You'd #include your static library's header file to provide the underlying implementation of Add, Subtract etc and you'd have your COM object's Add method call the underlying MathFuncs::MyMathFuncs::Add method (and return the result). Finally, you'd make sure your COM object project linked to your static library .lib file as part of the build process.

To consume the 'wrapper' from a C# program, you'd use Add | Reference to add a COM reference to your COM object and then just cal the COM object's methods as necessary.

Using approach #2, you'd create a new project using the Visual C++ | Win32 | Win32 Project template and use Application Settings to change Application Type to DLL and set Additional Options to "Export symbols". Then you would create some exported entry points (like Add, Subtract etc) to be consumed by the client. Again, you would #include your static library's header file to get function descriptions and link to the .lib file to get actual implementations. You'd code your exported DLL entry points to call the static library's 'real' implementation methods and return the result.

To consume this kind of wrapper, you would use the C# DllImportAttribute to declare the DLL entry point in C# terms and then just call the function as needed.

Finally, using appoach #3, you'd create a managed C++ project using Visual C++ | CLR | Class Library. You'd create a managed class (a 'ref' class) which would be called by your C# client. Again, you'd #include the native code header file and link to the .lib file to get the actual implementation of the methods you want the client to be able to call. The managed code wrapper for the above header file might look something like this...




#include "MathFuncsLib.h" #pragma once
using
namespace System; namespace ManagedMathFuncsLib {
public ref class Class1
{
public:
Class1 ()
{
};
// Returns a + b static double Add(double a, double b)
{
return MathFuncs::MyMathFuncs::Add (a, b);
}
// Returns a - b static double Subtract(double a, double b)
{
return MathFuncs::MyMathFuncs::Subtract (a, b);
};
// Returns a * b static double Multiply(double a, double b)
{
return MathFuncs::MyMathFuncs::Multiply (a, b);
};
// Returns a / b // Throws DivideByZeroException if b is 0 static double Divide(double a, double b)
{
return MathFuncs::MyMathFuncs::Divide (a, b);
};
};}

To consume this managed code wrapper, you'd use Add | Reference to add a .NET Reference (to your managed wrapper) to your client program and then would call the managed (ref) class's methods as necessary.

In all three cases above, I simplified things by using static methods in the original native code implementation. If your static library implements classes with non-static functions then in all three approaches above you would have to conceal a private member variable that pointed to an instance of your native class and would have to new up that instance at some suitable point (in the constructor of the wrapper for example) and would have to use that private member variable to actually invoke the actual implementation of the method you were trying to call.


Q: Could you go into a little more detail regarding the last paragraph of your post. I'm unclear as to dealing with functions that are not declared static.

Where are you creating the private member?

How do you invoke that private member, PInvoke?

Would it be possible to see another example?

Thanks

Blue


A:

There's no need to use P/Invoke because the wrapper is written in Managed C++ so it can invoke the private member's functions directly.

Let's change the example slightly and create a statistical functions library that can be used to calculate a number of statistical properties of a group of numbers. Since I'm lazy I'll just calculate average but you could imagine calculating standard deviation and median and all sorts of things. To keep the example simple, I'll not store the actual data items, I'll just incorporate them into a sum variable as I go. Again though you could imagine a much more elaborate data structure underneath this native class.

The header file for the native code static library implementation of this statistical functions library might look something like this




// StatFuncsLib.h namespace StatFuncs
{
class MyStatFuncs
{
private:
double m_Count;
double m_Sum;

public:
// Constructs an instance and initialises private members
MyStatFuncs ();

// Adds item and returns updated count
double AddItem(double item);

// Returns average
// Throws DivideByZeroException if count is 0
double Average ();

// Returns count of items
double Count ();
};
}

The AddItem function will increment m_Count by 1 and will add item to m_Sum while the Average function will return m_Sum divided by m_Count.

Because data is accumulated in the private member variables m_Count and m_Sum we can't just use static functions, we have to use individual instances of the MyStatFuncs class. That means when we create our managed C++ wrapper class it will have to create and use an instance of the MyStatFuncs class. The wrapper class might look something like this




// ManagedStatFuncsLib.h #pragma once
#include
"StatFuncsLib.h" using namespace System; namespace ManagedStatFuncs {

public ref class MyManagedStatFuncs
{

private:
StatFuncs::MyStatFuncs * m_NativeStatFuncs;

public:

// Constructs an instance of the class and an instance
// of the underlying native class MyStatFuncs
MyManagedStatFuncs ()
{
m_NativeStatFuncs =
new StatFuncs::MyStatFuncs ();
};

// Adds item and returns updated count
double AddItem(double item)
{
return m_NativeStatFuncs->AddItem (item);
};

// Returns average
// Throws DivideByZeroException if b is 0
double Average ()
{
return m_NativeStatFuncs->Average ();
};

// Returns count of items
double Count ()
{
return m_NativeStatFuncs->Count ();
};
};
}

The managed C++ wrapper will compile up into an assembly that can be referenced from a C# program (using Add Reference). Then the C# program just creates instances of the managed wrapper class and invokes the various functions as needed. For example




ManagedStatFuncs.MyManagedStatFuncs myStatOne =
new ManagedStatFuncs.MyManagedStatFuncs();
ManagedStatFuncs.MyManagedStatFuncs myStatTwo =
new ManagedStatFuncs.MyManagedStatFuncs();

myStatOne.AddItem(1.0);
myStatOne.AddItem(3.0);
myStatOne.AddItem(5.0);

myStatTwo.AddItem(2.0);
myStatTwo.AddItem(4.0);
myStatTwo.AddItem(6.0);
myStatTwo.AddItem(8.0);

Console.WriteLine(
"First group consists of {0} items with an average value of {1}",
myStatOne.Count(), myStatOne.Average());
Console.WriteLine(
"Second group consists of {0} items with an average value of {1}",
myStatTwo.Count(), myStatTwo.Average());
Q:

This is a good example! Thanks!

I am trying to get this code to work on a Pocket PC device. Is it possible to build a managed C++ assembly for the Pocket PC. So far I have only been able to build the managed C++ assembly for Win32. Visual studio will not let me do Add Reference for a Win32 assembly in C#. When I try to add reference I get an error saying that ManagedStatFuncs can not be added because it is not a device project. Maybe this is because Win32 managed code is not compatible with compact frameworks. Maybe I need a different project template. Does anyone have any thoughts on this?


A:

Brian Wall wrote:
Is it possible to build a managed C++ assembly for the Pocket PC?

In a word, no. I can't find an authoritative reference to cite but if you search the web (or these forums) for "Managed C++" and "Smart Device" you'll find several posts that say it can't be done. For example this chat transcript

Q: Will there be support for Managed C++ with .NET CF 2.0?
A: Sorry, no. Only VB and C#.

http://msdn.microsoft.com/chats/transcripts/mobileembedded/netcf_020805.aspx

No comments: