Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tips for using a C library from C#

Tags:

c

c#

.net

interop

I've got a library in C which I'd like to use from C#.

From what I've gleaned off the internet, one idea is to wrap it in a C++ dll, and DllImport that.

The problem is that the function I want to call has a fairly nasty parameter set: including a reference to a function (which will be a .NET function), and a couple of arrays (some write, some read).

int lmdif(int (*fcn)(int, int, double*, double*, int*), 
          int m, int n, double* x, double ftol, double xtol, double gtol, 
          int maxfev, double epsfcn, double factor)

Given an interface like this, what are the nasties I should be looking out for? (And solutions too, please)

Why don't you...

- Rewrite in C#? I started, but it's already machine-translated from FORTRAN, and I don't much like coding stuff I can't understand.

- Use an existing .NET library? I'm trying it right now, but results are not exactly the same

- Recompile in C++? I'm thinking about it, but it looks like a lot of pain

like image 798
Benjol Avatar asked Jun 01 '12 09:06

Benjol


People also ask

Can I use C library in C++?

Yes - C++ can use C libraries.

What is ac library good for?

The C standard library provides macros, type definitions and functions for tasks such as string handling, mathematical computations, input/output processing, memory management, and several other operating system services.

What is the difference between C executable and C library?

A library is exactly like an executable, except instead of running directly, the library functions are invoked with parameters from your executable. You would be familiar about the compilation of a C file.


6 Answers

For the record, I got most of the way to getting this working, then ended up pulling the appropriate C files into the C++ project (because I was fighting with compatibility issues in code that I didn't even need).

Here are some tips I picked up along the way which may be helpful for people happening on this question:

Marshalling arrays

In C, there's no difference between a pointer to double (double*) and an array of doubles (double*). When you come to interop, you need to be able to disambiguate. I needed to pass arrays of double, so the signature might look like this:

[DllImport(@"PathToDll")]
public static extern Foo(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)[In] double[] x, 
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)[Out] double[] y, 
    int x_size, 
    int y_size)

C needs the extra information of how long the array is, which you have to pass in as a separate parameter. The marshalling also needs to know where this size is, so you specify the SizeParamIndex, which indicates the zero-based index of the size parameter in the list of parameters.

You also specify which direction the array is intended to be passed. In this example x is passed into Foo, which 'sends back' y.

Calling convention

You don't actually need to understand the finer details of what this means (in other words, I don't), you just need to know that different calling conventions exist, and that they need to match on both sides. The C# default is StdCall, the C default is Cdecl. This means that you only need to explicitly specify the calling convention if it differs from the default on which ever side you're using it.

This is particularly hairy in the case of a callback. If we're passing a callback to C from C#, we're intending for that callback to be invoked with StdCall, but when we pass it in, we're using Cdecl. This results in the following signatures (see this question for context):

//=======C-code======
//type signature of callback function
typedef int (__stdcall *FuncCallBack)(int, int);

void SetWrappedCallback(FuncCallBack); //here default = __cdecl

//======C# code======
public delegate int FuncCallBack(int a, int b);   // here default = StdCall 

[DllImport(@"PathToDll", CallingConvention = CallingConvention.Cdecl)]
private static extern void SetWrappedCallback(FuncCallBack func);

Packaging the callback

Obvious, but it wasn't immediately obvious to me:

int MyFunc(int a, int b)
{
   return a * b;
}
//...

FuncCallBack ptr = new FuncCallBack(MyFunc);
SetWrappedCallback(ptr);

.def file

Any functions you want to expose from C++ project (to be DllImported), need to figure in the ModDef.def file, who's contents would look something like this:

LIBRARY MyLibName
EXPORTS
    SetWrappedCallback  @1

extern "C"

If you want to use C functions from C++, you have to declare them as extern "C". If you are including a header file of C functions, you go like this:

extern "C" {
  #include "C_declarations.h"
}

Precompiled headers

Another thing I had to do to avoid compilation errors was Right-click -> Properties -> C/C++ -> Precompiled Headers and set Precompiled header to Not Using Precompiled Headers for each 'C' file.

like image 157
Benjol Avatar answered Oct 13 '22 14:10

Benjol


The only 'nasty' parameter is the function pointer. But luckily .NET handles them fairly well via delegates.

The only problem is with the calling convention. In C#, it only emits one type (iirc stdcall), whereas the C code might expect cdecl. The latter problem can be handled on an IL level though (or using Reflection.Emit).

Here is some code that does it via Reflection.Emit (this is to help understand what psuedo-attribute needs to be placed on the delegate's Invoke method).

like image 41
leppie Avatar answered Oct 13 '22 12:10

leppie


Microsoft documentation for Marshal.GetFunctionPointerForDelegate here:

"Converts a delegate into a function pointer that is callable from unmanaged code."

like image 20
Prof. Falken Avatar answered Oct 13 '22 14:10

Prof. Falken


This is how I typically interact with C DLLs from C#:

  public static unsafe class WrapCDll {
    private const string DllName = "c_functions.dll";

    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
    public static extern void do_stuff_in_c(byte* arg);
  }

No need to wrap in C++ and you get the needed calling convention.

like image 43
HonkyTonk Avatar answered Oct 13 '22 14:10

HonkyTonk


Here is a way to send a LOT of numeric data to your C function via a StringBuilder. Just dump your numbers in a StringBuilder, while setting delimiters at appropriate positions et voilá:

class Program
    {  
        [DllImport("namEm.DLL", CallingConvention = CallingConvention.Cdecl, EntryPoint = "nameEm", 
            CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
        public static extern int nameEm([MarshalAs(UnmanagedType.LPStr)] StringBuilder str);
        static void Main(string[] args)
        {
            int m = 3;
            StringBuilder str = new StringBuilder();
            str.Append(String.Format("{0};", m));
            str.Append(String.Format("{0} {1:E4};", 5, 76.334E-3 ));
            str.Append(String.Format("{0} {1} {2} {3};", 65,45,23,12));
            m = nameEm(str);
        }
    }

And at C-side, pick up the StringBuilder as a char*:

extern "C"
{
    __declspec(dllexport) int __cdecl nameEm(char* names)
    {
        int n1, n2, n3[4];
        char *token,
             *next_token2 = NULL,
             *next_token = NULL;
        float f;

        sscanf_s(strtok_s(names, ";", &next_token), "%d", &n2);
        sscanf_s(strtok_s(NULL, ";", &next_token), "%d %f", &n1, &f);
        // Observe the use of two different strtok-delimiters.
        // the ';' to pick the sequence of 4 integers,
        // and the space to split that same sequence into 4 integers.
        token = strtok_s(strtok_s(NULL, ";", &next_token)," ",&next_token2);
        for (int i=0; i < 4; i++)
        {
             sscanf_s(token,"%d", &n3[i]);
             token = strtok_s(NULL, " ",&next_token2);
        }    
        return 0;
    }
}
like image 23
Tom Avatar answered Oct 13 '22 13:10

Tom


There has been a MSDN article years ago which resulted in the PInvoke Interop Assistant. This little tool is still useful to marshall C interfaces. Copy and past interface code into the "SigImp Translation Sniplet" and watch the results.

The result is the following but I have no idea how the delegate is used or if it works. So if it works add some comments.

/// Return Type: int
///param0: int
///param1: int
///param2: double*
///param3: double*
///param4: int*
public delegate int Anonymous_83fd32fd_91ee_45ce_b7e9_b7d886d2eb38(int param0, int param1, ref double param2, ref double param3, ref int param4);

public partial class NativeMethods {
    
    /// Return Type: int
    ///fcn: Anonymous_83fd32fd_91ee_45ce_b7e9_b7d886d2eb38
    ///m: int
    ///n: int
    ///x: double*
    ///ftol: double
    ///xtol: double
    ///gtol: double
    ///maxfev: int
    ///epsfcn: double
    ///factor: double
    [System.Runtime.InteropServices.DllImportAttribute("<Unknown>", EntryPoint="lmdif", CallingConvention=System.Runtime.InteropServices.CallingConvention.Cdecl)]
public static extern  int lmdif(Anonymous_83fd32fd_91ee_45ce_b7e9_b7d886d2eb38 fcn, int m, int n, ref double x, double ftol, double xtol, double gtol, int maxfev, double epsfcn, double factor) ;

}
like image 35
Totonga Avatar answered Oct 13 '22 12:10

Totonga