Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

C#.NET Interoperability with Native C Libraries on Cross Platform: PART II

4.90/5 (8 votes)
17 Jan 2014CPOL3 min read 20.3K   168  
An attempt to taking up an existing published article “C#.NET Interoperability with Native C Libraries on Cross Platform” towards the next level.

Introduction

This tip is all about how to manage C#.NET framework interoperability at the cross platform level when the C#.NET application needs to be portable on Windows as well as Linux. This time, I am discussing on a somewhat more advanced level than my previous tip: C#.NET Interoperability with Native C Libraries on Cross Platform: PART I”.

Background

In Part I, I demonstrated how to write C# code for interoperability with native C libraries to make it portable at the code level. It means we need to have separate builds for each platform. So for two platforms, we will have two C# executables, one for the Windows platform and the second for the Unix platform (even though the codebase is the same but having different compile time platform specific switches that makes the difference).

In this discussion, we are demonstrating how to make a single C# executable for Interoperability with Native 'C' call on cross platform to support Windows and Unix operating systems . It is very much tested on WinXP Service Pack-3 as well as on Debian Ubuntu 11.04.

Note: ‘C’ Native libraries will be platform-specific and same built library cannot be used for both the platforms, i.e., Windows and Linux. It means for each platform, 'C' library needs to be built explicitly which is inevitable .

Using the Code

There are two parts of this post. The first one focuses on the C Native Libraries code to make it portable at code build level. Please visit the previous tip PART-I to get C Native Library code and how to build on Windows as well as Linux. After building (Compiling & Linking), we will get libctest.dll on Windows and libctest.so on Linux. For a quick native C Library build, unzip NativeCLib and open NativeTestLib.sln in MS Visual Studio on Windows and for Linux run build.sh located in the same folder of source.c file.

Second part is C# code, which is very different compared to Part I. Please have a look.

Below is demo C# code to use the above native C library APIs:

C#
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Reflection;
 
namespace UseSharedObject
{
        // Generic Extension for all classes to invoke member functions on multi-platform :-)
        public static class MethodInvoker
        {
            public static void InvokeMethod(this object main, 
            string strMethodName, Object[] parameters, ref object retObj)
            {
                if ("Unix" == Environment.OSVersion.Platform.ToString())
                {
                    strMethodName = strMethodName + "Ux";
                }
                else // it is running on Windows OS                {
                    strMethodName = strMethodName + "Win";
                }
 
                MethodInfo info = main.GetType().GetMethod(strMethodName); 
                if(info != null) info.Invoke(retObj, parameters);
                return;
            }
        } 
    
        [StructLayout(LayoutKind.Sequential)]
 
        public struct Temp
        {
            public IntPtr m_iVal;
            public string m_strVal;
        }
            
        public class MainClass
        {
            //-- START : IMPORTING DYNAMIC LIBRARIES FUNCTIONS
 
                //Import ctestFillStructure
               [DllImport ("./assembly/libctest.so", EntryPoint="ctestFillStructure")]
                public static extern void ctestFillStructureUx(out Temp i); //For Unix
 
               [DllImport("./assembly/libctest.dll", EntryPoint = "ctestFillStructure")]
                public static extern void ctestFillStructureWin(out Temp i); //For Windows
 
                //Import FreeResource
                [DllImport("./assembly/libctest.so", EntryPoint = "FreeResource")]
                public static extern void FreeResourceUx(IntPtr ptr); //For Unix
 
                [DllImport("./assembly/libctest.dll", EntryPoint = "FreeResource")]
                public static extern void FreeResourceWin(IntPtr ptr); //For Windows
 
            //-- END : IMPORTING DYNAMIC LIBRARIES FUNCTIONS
 
 
            public static void Main(string[] args)
            {                
                MainClass MyClass = new MainClass();
                object retValue=null;
 
                Console.WriteLine
                ("----------------------------- DEMO STARTS ----------------------------- \n");
                Console.WriteLine(" CALL 'C' LIBRARY FUNCTION, RETURN VALUES AS FOLLOWS");
                object[] arguments = new object[1];
                Temp valTemp;
                //Equivalent to ctestFillStructure(ref valTemp) 
                //but it checks runtime which method to be called based on current platform
                MyClass.InvokeMethod("ctestFillStructure", arguments, ref retValue); 
                valTemp = (Temp) arguments[0];
                Console.WriteLine("\t   valTemp.m_iVal = {0} valTemp.m_strVal = {1} 
                \n", Marshal.ReadInt32(valTemp.m_iVal), valTemp.m_strVal);
 
                //Equivalent to FreeResourceWin((IntPtr)(valTemp.m_iVal));
                //but it checks runtime which method to be called based on current platform
                Console.WriteLine(" FREEING RESOURCE WHICH WAS ALLOCATED BY 'C' LIBRARY \n ");
                MyClass.InvokeMethod("FreeResource", new object[] { valTemp.m_iVal }, ref retValue);
                valTemp.m_iVal = (IntPtr)0;
 
 
                Console.WriteLine(" CURRENT  PLATFORM = {0} 
                \n", Environment.OSVersion.Platform.ToString());
                Console.WriteLine
                ("----------------------------- DEMO ENDS ----------------------------- \n");
            }
        }//Ends class MainClass
   
}//Ends namespace UseSharedObject

Declare DllImport for each imported 'C' function and for each platform, i.e., Win and Unix by passing library name and EntryPoint with C native function name followed by Ux and Win word to associate function call to the target platform. Later, it will be used by C# managed code to invoke native 'C' calls . Also define equivalent Temp structure in C# similar to Temp structure in C Library so it can be passed as a parameter.

To identify run time which method to be called based on the current platform, we need the MethodInvoker generic C# extension. To call ‘C’ Native function calls, the InvokeMethod extension call will be used which will take the C function name, the array of the passed parameter and the return value.

C#
MyClass.InvokeMethod("FreeResource", 
new object[] { valTemp.m_iVal }, ref retValue);

Copy the above generated libctest.so and libctest.dll in the assembly folder on the same path of the UseSharedObject .NET executable.

For a quick demonstration, unzip the Managed_C#_Code folder from the downloaded demo project and open UseSharedObject.sln project in the MS Visual Studio on the Windows operating system and MonoDevelop IDE or any other equivalent IDE on mono platform on Linux OS and build the solution. And run it.

Points of Interest

To make a C# generic extension to be supported to all classes, please pass the first parameter of type this object.

C#
public static void InvokeMethod
(this object main, string strMethodName, Object[] parameters, ref object retObj)

History

  • This is the second draft version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)