Importing from DLL/SO and function pointers

Interoperability in .NET is an interesting topic, but it would be much better to refer to proper resources dedicated to it. Here, we will only consider .NET's analogs of function pointers and misbelief dynamic importing of functions exported by DLLs and shared objects. But, first, let's construct the class, import the GetPointers() procedure, and define function pointer delegates:

internal class Crypto
{
Funcs functions;
IntPtr buffer;
byte[] data;

// The following two lines make up the properties of the class
internal byte[] Data { get { return data; } }
internal int Length { get { return data.Length; } }

// Declare binding for GetPointers()
// The following line is written for 64-bit targets, you should
// change the file name to crypto_32.so when building for
// 32-bit systems.
// Change the name to crypto_wXX.dll when on Windows, where XX

// stands for 32 or 64.
[DllImport("crypto_64.so", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr GetPointers();

// Declare delegates (our function pointers)
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void dSetDataPointer(IntPtr p);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void dSetDataSize(int s);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void dEncrypt();

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void dDecrypt();

// Constructor
internal Crypto()
{
// Luckily when we get a pointer to structure by calling
// GetPointers() we do not have to do more than just let
// the framework convert native structure to managed one
functions = (Funcs)Marshal.PtrToStructure(
GetPointers(),
typeof(Funcs));

// Set initial buffer ptr
buffer = IntPtr.Zero;
}

// SetDataPointer() method is the most complex one in our class,
// as it includes invocation of SetDataLength()
internal void SetDataPointer(byte[] p)
{
// If an unmanaged buffer has been previously allocated,
// then we need to free it first.
if(IntPtr.Zero != buffer)
Marshal.FreeHGlobal(buffer);
buffer = Marshal.AllocHGlobal(p.Length);

// Copy data to both the local storage and unmanaged buffer
data = new byte[p.Length];
Array.Copy(p, data, p.Length);
Marshal.Copy(p, 0, buffer, p.Length);

// Call f_set_data_pointer with a pointer to unmanaged buffer
((dSetDataPointer) Marshal.GetDelegateFromFunctionPointer(
functions.f_set_data_pointer,
typeof(dSetDataPointer)))(buffer);

// Tell the core what the length of the data buffer is
((dSetDataSize) Marshal.GetDelegateFromFunctionPointer(
functions.f_set_data_length,
typeof(dSetDataSize)))(p.Length);
}

// The remaining two methods are more than simple
internal void Encrypt()
{
// Encrypt the data in the unmanaged buffer and copy it
// to local storage
((dEncrypt)Marshal.GetDelegateFromFunctionPointer(
functions.f_encrypt,
typeof(dEncrypt)))();
Marshal.Copy(buffer, data, 0, data.Length);
}

internal void Decrypt()
{
// Decrypt the data in the unmanaged buffer and copy it
// to local storage
((dDecrypt)Marshal.GetDelegateFromFunctionPointer(
functions.f_decrypt,
typeof(dDecrypt)))();
Marshal.Copy(buffer, data, 0, data.Length);
}
}

The preceding code is for the Linux version; however, it may easily be changed to the Windows version by changing the name of the shared object to the name of a DLL. With this class, working with our Crypto Core is rather simple and may be summarized by the following code:

Crypto c = new Crypto();
string message = "This program uses "Crypto Engine" written in Assembly language.";
c.SetDataPointer(ASCIIEncoding.ASCII.GetBytes(message);
c.Encrypt();
c.Decrypt();

However, despite the fact that, if we implement the preceding class and try to use it in our code, it would compile well, we are still unable to actually run it. This is because we need to supply the DLL or shared object, depending on the platform of our choice. The easiest way to supply the libraries is to copy them into the solution folder and tell the IDE (Visual Studio or Monodevelop) to handle them properly.

The first step is to copy the libraries (DLLs on Windows and SOs on Linux) into the project folder. The following screenshot shows the Monodevelop project folder on Linux, but the procedure is just the same for both Linux and Windows:

The next step would be to actually tell the IDE how to treat these files. First, add them to the project by right-clicking on the project and then navigating to Add | Existing Item for Visual Studio or Add | Add Files for Monodevelop, and then set the properties for each of the libraries as shown in the following screenshots.

To set the properties in Visual Studio:

To set the properties in Monodevelop:

Although the GUI is different, both need to have Build Action set to Content and Copy to Output Directory set to Copy always in Visual Studio and checked in Monodevelop.

Now we can build out project (either on Windows or Linux) and run it. We may either watch the data being encrypted/decrypted in memory or add a tiny function that would print out the content of memory within a specific range.

If everything is set up correctly, then the output should be similar to the following when on Windows:

The output on Linux would be similar to this:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset