Click here to Skip to main content
15,885,799 members
Articles / Programming Languages / C#
Tip/Trick

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

Rate me:
Please Sign up or sign in to vote.
4.33/5 (5 votes)
10 Jan 2014CPOL3 min read 36K   492   15   6
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)


Written By
Student
India India
I am homemaker, a computer savvy and like to learn new technologies in IT world.

Comments and Discussions

 
GeneralNot so portable Pin
Carlos190712-Jan-14 22:43
professionalCarlos190712-Jan-14 22:43 
GeneralRe: Not so portable Pin
FIorian Schneidereit17-Jan-14 10:59
FIorian Schneidereit17-Jan-14 10:59 
GeneralRe: Not so portable Pin
deepi201418-Jan-14 3:02
deepi201418-Jan-14 3:02 
GeneralMy vote of 4 Pin
Mohit Gonduley11-Jan-14 18:55
Mohit Gonduley11-Jan-14 18:55 
QuestionA little tip PinPopular
FIorian Schneidereit11-Jan-14 7:07
FIorian Schneidereit11-Jan-14 7:07 
Good work.

A little tip: To reduce the amount of #if UX_PLATFORM/#elif WINDOWS preprocessor directives and prevent unnecessary redeclaration of extern methods, you should define a constant for the native library names used by the DllImportAttribute and put them in a separate static class for this purpose. Example:
C#
static class ExternDll
{
    #if UX_PLATFORM
    public const String LibCTest = "./assembly/libctest.so";
    #elif WINDOWS
    public const String LibCTest = "./assembly/libctest.dll";
    #endif
}


Done this way, you can simply declare your P/Invoke methods once, like this:
C#
[DllImport(ExternDll.LibCTest, EntryPoint="ctestFillStructure")]
private static extern void ctestFillStructure(ref Temp i);

[DllImport(ExternDll.LibCTest, EntryPoint="ctestFreeResource")]
private static extern void ctestFreeResource(IntPtr ptr);  


It's less typing, and way easier to handle if you have a lot of extern methods or change the name/location of the native library.

modified 11-Jan-14 13:19pm.

AnswerRe: A little tip Pin
deepi201417-Jan-14 9:05
deepi201417-Jan-14 9:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.