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 I

4.33/5 (5 votes)
10 Jan 2014CPOL3 min read 37.3K   495  
This tip is to showcase how to write “Native C Library” so that it can be used for Cross Platform with .NET interoperability with Native C Libraries.
Image 1

Introduction

This tip is all about how to manage .NET framework interoperability at cross platform level when C#. NET application needs to be portable on Windows as well as Linux.

Background

As software technologies are very much advanced, that has encouraged many software firms to leverage it for changing Look and Feel of their old piece of software, improving the user experience by using available managed codes like C#. However, the task of converting huge stable legacy native code into equivalent C# is a real challenge and nightmare in terms of time and complexity of converting domain business logic written in C to equivalent C# managed code. Hence, one of the best approaches is to reuse existing stable native libraries with the help of .NET interoperability concept.

Secondly, many software firms are willing to support multiple freely available Operating Systems to reduce licensing cost and increase customer base by providing portability of their software on those freely available operating systems which need little tweak in Native C libraries as well as managed C# code. So, the above topics are addressed at beginner level. More articles would be added on the same in future.

Using the Code

There are two parts of this tip. First one focuses on the C Native Libraries, how to make it supported by cross platforms. Here below, I have given an example for libctest C Library.

C++
#include <stdio.h>
#include <string.h>
#include <malloc.h>
 
#ifdef __cplusplus    // If used by C++ code, 
extern "C" {          // we need to export the C interface
#endif
        typedef struct Temp
        {       int * iVal;
               char * chVal;
        }MyTemp;
 
        /*___________________PLATFORM BASED EXPORT DECLARATION STARTS_____*/
               #ifdef UNIX 
                       #define EXPORT extern
               #elif (defined (_WINDOWS))
                       #define EXPORT extern __declspec( dllexport )
               #endif
        /*___________________PLATFORM BASED EXPORT DECLARATION ENDS________*/
        
        EXPORT void ctestFreeResource (void * ptr)
        {
               if(ptr) free(ptr);
               ptr = NULL;
        }
 
        EXPORT void ctestFillStructure(MyTemp *iTempVal)
        {
               iTempVal->iVal = (int*) malloc(sizeof(int));
               iTempVal->chVal = (char*) malloc(5);  
               *((*iTempVal).iVal) = 500;
               strcpy(iTempVal->chVal,"Moh"); 
               return;
        }
#ifdef __cplusplus
}
#endif

Define EXPORT preprocessor macro based on the platform and declare each API with EXPORT to export API. Two APIs are defined for demonstration namely ctestFillStructure to fill structure with values and second is ctestFreeResource to free the heap memory allocated during native C APIs.

Compiling and Linking the C Library on Linux Platform

From downloaded demo project, unzip NativeCLib folder located under linux at /tmp or some other location on Linux system which is accessible and run the ./build.sh shell script file at command prompt that will execute below commands to compile and link the libctest library and will generate libctest.so shared object (Dynamic shared library) on same path:

LINUX BUILD:
 COMPILE       :       gcc -c -Wall -Werror -fpic ./ctest.c -DUNIX
 LINK          :       gcc -shared -o libctest.so ctest.o

Compiling and Linking the C Library on Windows Platform

From downloaded demo project, unzip NativeTestLib library located under win folder and open the NativeTestLib.sln solution in MS-Visual Studio on Windows system and build it.

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

C#
using System;
using System.Runtime.InteropServices;
using System.Collections;
 
namespace UseSharedObject
{
        [StructLayout(LayoutKind.Sequential)] 
        public  struct Temp
        {
               public IntPtr  m_iVal;
               public  string m_strVal;
        }
 
        class MainClass
        {              
        #if (UX_PLATFORM)      
               [DllImport ("./assembly/libctest.so", EntryPoint="ctestFillStructure")]
                private static extern void ctestFillStructure(ref Temp i);
                       
               [DllImport ("./assembly/libctest.so", EntryPoint="ctestFreeResource")]
               private static extern void ctestFreeResource(IntPtr ptr);    
 
        #elif (WINDOWS)
               [DllImport ("./assembly/libctest.dll", EntryPoint="ctestFillStructure")]
                private static extern void ctestFillStructure(ref Temp i);
                       
               [DllImport ("./assembly/libctest.dll", EntryPoint="ctestFreeResource")]
               private static extern void ctestFreeResource(IntPtr ptr);     
        #endif
               
               public static  void Main (string[] args)
               {
                       Temp valTemp = new Temp();                    
                       ctestFillStructure(ref valTemp);                      
                       Console.WriteLine ("RETURN VALUES FROM NATIVE LIBRARIES: 
                       valTemp.m_iVal = {0} valTemp.m_strVal = {1} ", 
                       Marshal.ReadInt32( valTemp.m_iVal), valTemp.m_strVal);
                       Console.WriteLine ("CURRENT  PLATFORM = {0}", 
                       Environment.OSVersion.Platform.ToString());                  
                       ctestFreeResource( (IntPtr) (valTemp.m_iVal));                       
                       valTemp.m_iVal = (IntPtr)0;                   
               }
        }
}

Declare DllImport by passing library name and EntryPoint with C native function name which will be called in C# managed code followed by signature of native call with private static extern. Also define equivalent Temp structure in C# similar to Temp structure in C Library so can be passed as a parameter. Add UX_PLATFORM switch while creating C# project on Linux platform using monoDevelop or any other available IDE based on mono framework.

Copy the above generated libctest.so on Linux or libctest.dll on Windows at assembly folder on same path of UseSharedObject C#.NET executable.

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

Points of Interest

As ref in C# doesn’t support for pointer data type function parameters i.e. IntPtr, so in function ctestFreeResource, after de-allocation of pointer type variable, we assign it NULL in native library function call, however it does not reflect at C# code after returning from function so explicitly it has to be assigned with NULL value i.e. 0.

C#
valTemp.m_iVal = (IntPtr)0; 

History

  • This is a first draft version.
  • Please visit this link to read Part II.   

License

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