Wednesday, January 25, 2006 12:57 AM bart

A beginner's guide to C library interop in C# (cont'd)

Previously I posted a sample on C interop basics in C#. In this post I try to avoid a "reputation mismatch" that could be caused by providing the least possible example, so here is a more exhaustive one. Have fun.   

 

This is the C#-code. Build this code in a Console Application Project.

 

using System;
using
System.Text;
using
System.Collections.Generic;
using
System.Runtime.InteropServices;

namespace
CallC
{
    /// <summary>
    /// Helper struct to pass Coordinate object through.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct Coordinate
    {
        /// <summary>
        /// X-coordinate
        /// </summary>
        public int x;

        /// <summary>
        /// Y-coordinate
        /// </summary>
        public int y;

        /// <summary>
        /// Z-coordinate
        /// </summary>
        public int z;
    }

    /// <summary>
    /// Demo application of interop with a native C library.
    /// </summary>
    class Program
    {
        /// <summary>
        /// Makes the sum of two integer numbers.
        /// </summary>
        /// <param name="a">First value (a)</param>
        /// <param name="b">Second value (b)</param>
        /// <returns>Sum of a and b</returns>
        [DllImport("c_test_lib.dll")]
        public static extern int Sum(int a, int b);

        /// <summary>
        /// Returns the square of a given number using an output parameter.
        /// </summary>
        /// <param name="input">Input value</param>
        /// <param name="output">Output value (square)</param>
        [DllImport("c_test_lib.dll")]
        public static extern void Square(int input, out int output);

        /// <summary>
        /// Returns the square of a given number by changing the number by reference.
        /// </summary>
        /// <param name="a">Input/output value (will be squared)</param>
        [DllImport("c_test_lib.dll")]
        public static extern void Square2(ref int a);

        /// <summary>
        /// Demo of array manipulations; will double all of the elements in the array.
        /// </summary>
        /// <param name="a">The array with integer numbers</param>
        /// <param name="n">The length of the array</param>
        [DllImport("c_test_lib.dll")]
        public static extern void DoubleElements(int[] a, int n);

        /// <summary>
        /// Simple callback function.
        /// </summary>
        /// <param name="a">Some integer parameter.</param>
        public delegate void CBFUNC(int a);

        /// <summary>
        /// Demo of a simple callback.
        /// </summary>
        /// <param name="f">Function to call back to</param>
        /// <param name="a">Parameter which will be returned through the callback</param>
        [DllImport("c_test_lib.dll")]
        public static extern void DoCallback(CBFUNC f, int a);

        /// <summary>
        /// A second simple callback function with arguments and a return value.
        /// </summary>
        /// <param name="a">First parameter (a)</param>
        /// <param name="b">Second parameter (b)</param>
        /// <returns>Some binary operation result on a and b</returns>
        public delegate int CBFUNC2(int a, int b);

        /// <summary>
        /// Demo of more complex callback.
        /// </summary>
        /// <param name="f">Function to call back to</param>
        /// <param name="a">First parameter (a)</param>
        /// <param name="b">Second parameter (b)</param>
        /// <returns>Some callback value</returns>
        [DllImport("c_test_lib.dll")]
        public static extern int DoComplexCallback(CBFUNC2 f, int a, int b);

        /// <summary>
        /// Demo of working with structures (Coordindate sample).
        /// </summary>
        /// <param name="a">Coordinate a</param>
        /// <param name="b">Coordinate b</param>
        /// <param name="c">Sum of a and b</param>
        [DllImport("c_test_lib.dll")]
        public static extern void AddCoordinates(Coordinate a, Coordinate b, ref Coordinate c);

        /// <summary>
        /// Reverse a given string in-place.
        /// </summary>
        /// <param name="s">StringBuilder instance containing the string to be reversed</param>
        /// <remarks>Because strings are immutable, we need to supply a StringBuilder instead</remarks>
        [DllImport("c_test_lib.dll")]
        public static extern void Reverse(StringBuilder s);

        /// <summary>
        /// Calls a crashing function in the C-library.
        /// </summary>
        [DllImport("c_test_lib.dll")]
        public static extern void BeCareful();

        static void Main(string[] args)
        {
            //
            // Calling a simple function.
            //
            {
                Console.WriteLine(Sum(1,2));
            }

            //
            // Using output parameters.
            //
            {
                int res;
                Square(2, out res);
                Console.WriteLine(res);
            }

            //
            // Using pass-by-reference.
            //
            {
                int a = 3;
                Square2(ref a);
                Console.WriteLine(a);
            }

            //
            // Working with arrays.
            //
            {
                int[] arr = new int[] { 1, 2, 3 };
                DoubleElements(arr, arr.Length);
                List<int> l = new List<int>(arr);
                l.ForEach(delegate(int a) {Console.Write("{0}\t",a);});
                Console.WriteLine();
            }

            //
            // Callback mechanisms.
            //
            {
                DoCallback(Callback, 1);

                Console.WriteLine(DoComplexCallback(Adder, 1, 2));
                Console.WriteLine(DoComplexCallback(Multiplier, 1, 2));
            }

            //
            // Passing structures.
            //
            {
                Coordinate a = new Coordinate();
                a.x = 1; a.y = 2; a.z = 3;

                Coordinate b = new Coordinate();
                b.x = 4; b.y = 5; b.z = 6;

                Coordinate c = new Coordinate();
                AddCoordinates(a, b, ref c);

                Console.WriteLine("({0},{1},{2})",c.x,c.y,c.z);
            }

            //
            // Working with strings.
            //
            {
                StringBuilder s = new StringBuilder();
                s.Append("Hello world!");
                Reverse(s);
                Console.WriteLine(s.ToString());
            }

            //
            // Crashing in C.
            //
            try
            {
                BeCareful();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// Callback method to be called by the C-library.
        /// </summary>
        /// <param name="a">Some integer number parameter</param>
        public static void Callback(int a)
        {
            Console.WriteLine("Callback: {0}", a);
        }

        /// <summary>
        /// Callback method to be called by the C-library. Adds two numbers.
        /// </summary>
        /// <param name="a">Integer value a</param>
        /// <param name="b">Integer value b</param>
        /// <returns>Sum of a and b (a+b)</returns>
        public static int Adder(int a, int b)
        {
            return a + b;
        }

        /// <summary>
        /// Callback method to be called by the C-library. Multiplies two numbers.
        /// </summary>
        /// <param name="a">Integer value a</param>
        /// <param name="b">Integer value b</param>
        /// <returns>Product of a and b (a*b)</returns>
        public static int Multiplier(int a, int b)
        {
            return a * b;
        }
    }
}

 

Below you can find the C-code of the native library to interop with. Create a Visual C++ Win32 DLL empty project, create a file test.cpp and paste the following in it. Finally build the file.

 

#include <string.h>

typedef
void (__stdcall * CBFUNC) (int);
typedef
int (__stdcall * CBFUNC2) (int, int);

extern
"C" __declspec(dllexport) int _stdcall Sum(int a, int b)
{
return a+b;
}

extern
"C" __declspec(dllexport) void _stdcall Square(int a, int *b)
{
*b = a*a;
}

extern
"C" __declspec(dllexport) void _stdcall Square2(int* a)
{
*a = (*a)*(*a);
}

extern
"C" __declspec(dllexport) void _stdcall DoubleElements(int arr[], int n)
{
int i;
for
(i = 0; i < n; i++)
arr[i] *= 2;
}

extern
"C" __declspec(dllexport) void _stdcall DoCallback(CBFUNC f, int in)
{
f(in);
}

extern
"C" __declspec(dllexport) int _stdcall DoComplexCallback(CBFUNC2 f, int a, int b)
{
return f(a, b);
}

typedef
struct { int x; int y; int z; } Coordinate;

extern
"C" __declspec(dllexport) void _stdcall AddCoordinates(Coordinate a, Coordinate b, Coordinate *out)
{
out->x = a.x + b.x;
out->y = a.y + b.y;
out->z = a.z + b.z;
}

extern
"C" __declspec(dllexport) void _stdcall Reverse(char *str)
{
int i, j;
char
t;
for
(i = 0, j = strlen(str) - 1; i < j; i++, j--)
{
t = str[i];
str[i] = str[j];
str[j] = t;
}
}

extern
"C" __declspec(dllexport) void BeCareful(void)
{
int *p = NULL;
*p = 0;
}

 

Copy the resulting .dll file from the Visual C++ project release build output to the bin\Debug or bin\Release folder of the C# project and run the C# project. Have fun!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# VB6 AddressOf operator Migration

Tuesday, March 31, 2009 1:15 PM by Mauricio Rojas Blog

I was looking into ways to migrate something like the AddressOf operator in VB6. I read in some forums