tags:

views:

1568

answers:

3

I have a code library written in plain old C++ (no .NET/managed code) and I'm porting the application that uses this code to C#. I'm faced with two options:

  1. Rewrite the C++ code in C# to achieve the same functionality;
  2. Compile the C++ as a DLL and use it as a library in the C# application.

I'm relatively new to C# and am pretty unfamiliar with the implications of using an unmanaged code library in a C# app (or if there even are any). The code itself is moderate in size; it will likely take only a few days to rewrite in C#, but my thought is that leaving the code as a it is would allow me to use it in other applications as well (and to compile it on UNIX, etc).

What sort of things should I be aware of when making this decision? Are there any major drawbacks or gotchas to using the DLL in the C# application?

+4  A: 

I would make a wrapper library using C++/CLI to expose the library to C#. This can leave your library unchanged, and just wrap it for use from .NET, providing the best of both options.

Reed Copsey
Beat me by 20 seconds. But *I* went and found a link. :)
Randolpho
Using a C++/CLI wrapper is *the* way to go. P/Invoke will give you dll loading and versioning issues. Using an unmanaged library will make the line clear for managed unmanaged compilation. Beware of exposing STL classes, though. You may find that your low-level code that expects to use unmanaged STL will end up using managed versions with a lot of managed/unmanaged transitions.
plinth
+1  A: 

One thing I've found useful is to delve into C++/CLI when dealing with unmanaged C++ libraries. Create a managed wrapper using C++/CLI and call it from your C# code. The managed wrapper can include the library (I assume it's statically linked) in its DLL, and a project reference is all you need for your C# code.

Randolpho
FYI: DLL's are not statically linked. Dynamic Link Library.
yodaj007
@yodaj007: I believe he was suggesting to statically link the original library into the C++/CLI assembly, which is correct.
Reed Copsey
@yodaj007: @Reed Copsey said it better than I could. But I shall still try: the original library, based on things mentioned in the original question, appears to be a statically linked library. Meaning that if the library is used by the C++/CLI project, it will be compiled into (and subsumed by) the C++/CLI .DLL.
Randolpho
A: 

It's not necessary to write a wrapper in C++/CLI. You can directly use Platform Invoke from C#:

http://msdn.microsoft.com/en-us/library/aa288468%28VS.71%29.aspx

EDIT: If you do it using C++/CLI, you'll need to be doing LoadLibrary calls and creating function pointers. This is significantly easier in C#. This is from the MSDN tutorial linked above, but with my own added comments:

class PlatformInvokeTest
{
    [DllImport("msvcrt.dll")]  // Specify the DLL we're importing from
    public static extern int puts(string c); // This matches the signature of the DLL function.  The CLR automatically marshals C++ types to C# types.
    [DllImport("msvcrt.dll")]
    internal static extern int _flushall();

    public static void Main() 
    {
        puts("Test");
        _flushall();
    }
}

EDIT: Complex types can also be marshaled, though it's necessary to define the structs. This example taken from my own code that invokes GDI+. I've snipped it a bit.

private static int SRCCOPY = 0x00CC0020;
private static uint BI_RGB = 0;
private static uint DIB_RGB_COLORS = 0;


[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);

[StructLayout(LayoutKind.Sequential)]
private struct BITMAPINFO
{
    public uint biSize;
    public int biWidth;
    public int biHeight;
    public short biPlanes;
    public short biBitCount;
    public uint biCompression;
    public uint biSizeImage;
    public int biXPelsPerMeter;
    public int biYPelsPerMeter;
    public uint biClrUsed;
    public uint biClrImportant;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public uint[] cols;
}

public static Bitmap Downsample(Bitmap input, int bpp)
{
    Bitmap retval = null;

    // We will call into this GDI functionality from C#. Our plan:
    // (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed)
    // (2) Create a GDI monochrome hbitmap
    // (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above)
    // (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed)

    IntPtr inputHandle = input.GetHbitmap();

    //
    // Step (2): create the monochrome bitmap.
    //
    BITMAPINFO bmi = new BITMAPINFO();
    bmi.biSize = 40;  // the size of the BITMAPHEADERINFO struct
    bmi.biWidth = input.Width;
    bmi.biHeight = input.Height;
    bmi.biPlanes = 1;
    bmi.biBitCount = (short)bpp; // 1bpp or 8bpp
    bmi.biCompression = BI_RGB;
    bmi.biSizeImage = (uint)(((input.Width + 7) & 0xFFFFFFF8) * input.Height / 8);
    bmi.biXPelsPerMeter = 0; // not really important
    bmi.biYPelsPerMeter = 0; // not really important

    //
    // Create the color palette.
    //
    uint numColors = (uint)1 << bpp; // 2 colors for 1bpp; 256 colors for 8bpp
    bmi.biClrUsed = numColors;
    bmi.biClrImportant = numColors;
    bmi.cols = new uint[256];

    if (bpp == 1)
    {
        bmi.cols[0] = MAKERGB(0, 0, 0);
        bmi.cols[1] = MAKERGB(255, 255, 255);
    }
    else
    {
        for (int i = 0; i < numColors; i++)
        {
            bmi.cols[i] = MAKERGB(i, i, i);
        }
    }

    // 
    // Now create the indexed bitmap
    //
    IntPtr bits0;
    IntPtr indexedBitmapHandle = CreateDIBSection(IntPtr.Zero, ref bmi, DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0);
    IntPtr sourceDC = GetDC(IntPtr.Zero);
    IntPtr hdc = CreateCompatibleDC(sourceDC);
    IntPtr hdc0 = CreateCompatibleDC(sourceDC);

    SelectObject(hdc, inputHandle);
    SelectObject(hdc0, indexedBitmapHandle);

    BitBlt(hdc0, 0, 0, input.Width, input.Height, hdc, 0, 0, SRCCOPY);

    retval = Bitmap.FromHbitmap(indexedBitmapHandle);

    //
    // Dispose of the crud
    //
    DeleteDC(hdc);
    DeleteDC(hdc0);
    ReleaseDC(IntPtr.Zero, sourceDC);
    DeleteObject(inputHandle);
    DeleteObject(indexedBitmapHandle);

    return retval;
}
yodaj007
This looks fairly straightforward. What about the marshalling of things such as STL containers and complex custom types?
Jeff L
You don't need to deal with LoadLibrary or function pointers - C++/CLI makes wrapping a complex C++ library SIMPLE. P/Invoke works great, provided the library provides a C api, but there is no (easy) way to wrap a C++ class instance using P/Invoke. It's great for C, but not so great for C++ classes.
Reed Copsey
It can be really hard to figure out the P/Invoke signatures if you are passing in more complex structures, though. Wrapping in a managed C++/CLI DLL will let you simplify the structures being passed, if you can't figure out a workable way to marshall the structures.
Jeff Leonard