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:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Reflection;
namespace UseSharedObject
{
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
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
{
[DllImport ("./assembly/libctest.so", EntryPoint="ctestFillStructure")]
public static extern void ctestFillStructureUx(out Temp i);
[DllImport("./assembly/libctest.dll", EntryPoint = "ctestFillStructure")]
public static extern void ctestFillStructureWin(out Temp i);
[DllImport("./assembly/libctest.so", EntryPoint = "FreeResource")]
public static extern void FreeResourceUx(IntPtr ptr);
[DllImport("./assembly/libctest.dll", EntryPoint = "FreeResource")]
public static extern void FreeResourceWin(IntPtr ptr);
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;
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);
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");
}
}
}
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.
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
.
public static void InvokeMethod
(this object main, string strMethodName, Object[] parameters, ref object retObj)
History
- This is the second draft version.