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.
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#ifdef __cplusplus // If used by C++ code,
extern "C" { #endif
typedef struct Temp
{ int * iVal;
char * chVal;
}MyTemp;
#ifdef UNIX
#define EXPORT extern
#elif (defined (_WINDOWS))
#define EXPORT extern __declspec( dllexport )
#endif
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:
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
.
valTemp.m_iVal = (IntPtr)0;
History
- This is a first draft version.
- Please visit this link to read Part II.