There is plenty of unmanaged code out there and they expose a list of static entry points (functions that can be directly called from other applications) through DLLs. These functions are not organized in objects or interfaces but as simple list of methods. CLR enables interaction of managed boundary with unmanaged boundary (such as COM components, COM++ services, Win32 APIs and other types of unmanaged code)in a seamless way through PInvoke (or Platform Invoke Services). Interoperation can be achieved through the use of custom attributes-
DllImportAttribute
in the
System.Runtime.InteropServices
namespace.
Using Pinvoke in C#.NET (with Unmanaged C++ DLLs)
1. To consume services from unmanaged DLLs, the functions in unmanaged C/C++ must be preceded with "extern "C"
__declspec(dllexport)
". (This allows the compiler to export data, functions, classes, or class member functions from a DLL. This adds the export directive to the object file so you do not need to use the
.def file.)
2. To begin interoperation, the declaration in C#.NET (for example) can be in the following format:
class Wrapper
{
[DllImport(@"<PhysicalDllname>.dll",
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)
]
public static extern <returnType> <functionName>(<Parameters>);
...
...
...
}
Handling Unmanaged Data Types in Managed Code
As Data types, error-handling mechanisms, creation and destruction rules, and design guidelines vary between managed and unmanaged object models. To simplify this interoperation and to ease migration,the CLR interoperability layer conceals the differences in these object models
from both clients and servers.
Examples:
Example 1: Getting Array of Strings from an Unmanaged memory area as shown by the following code snippet:
extern "C" __declspec(dllexport) char** getSystemInformation()
{
}
class MyWrapper
{
[DllImport(@"<some>.dll",
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)
]
public static extern IntPtr getSystemInformation();
}
Note that the
char **
is a double pointer and in managed code
System.IntPtr
is a platform-specific type that is used to represent a pointer(any pointer) or
Handle
(more on
IntPtr
can be found on MSDN). The following code can be used to read the characters from the unmanaged area.
try
{
IntPtr ptrSI = MyWrapper.getSystemInformation();
for(int i=0; i<NUM_LINES ; i++)
{
Console.WriteLine(
Marshal.PtrToStringAnsi(
Marshal.ReadIntPtr(
ptrSI,
i*Marshal.SizeOf(
typeof(IntPtr)
)
)
)
);
}
}
catch(Exception ex){
Example 2: Reading the Structure elements. (useful when reading/writing Multi Dimensional Matrices)
Reading/writing Structure is not as simple as reading/writing character
strings, but it's a little tricky.
Let's consider the following example:
typedef struct
{
int width;
int height;
double *data;
}MATRIX;
extern "C" __declspec(dllexport) MATRIX getMatrixMultiplication
(MATRIX A , MATRIX B)
{
}
In C# (or any managed code), the structure is represented using a Dummy Structure that resembles the C Structure.
In this case:
[StructLayoutAttribute(LayoutKind.Sequential)]
public struct Matrix
{
public int row;
public int col;
public IntPtr data;
}
Explanation for [StructLayoutAttribute(LayoutKind.Sequential)]:
When declaring a type, the CLR at runtime automatically rearranges the
order of members for performance, to allow faster access to these members and to optimize memory allocations. But you can force control CLR to follow the sequence you defined, and this helps especially in the interoperability with some C/C++ APIs that accepts
struct
s.
Steps to follow:
1. First allocate a memory in unmanaged area to copy the matrix from managed code, using
Marshal.AllocHGlobal()
2. Copy the matrix using
Marshal.Copy()
as shown
3. Do Matrix Multiplication process (Exposed Function of DLL)
4. Copy back the Matrix to Managed Matrix Array
Finally to use it in your C# code:
double[] matA = new double[MAXDATA];
double[] matB = new double[MAXDATA];
int sizeA = Marshal.SizeOf(matA[0])*matA.Length;
int sizeB = Marshal.SizeOf(matB[0])*matB.Length;
IntPtr pA = Marshal.AllocHGlobal(sizeA);
IntPtr pB = Marshal.AllocHGlobal(sizeB);
Matrix AD = Wrapper.MatrixMultiplication(pA, pB);
double[] managedArray2 = new double[MAXDATA];
Marshal.Copy(AD.data, managedArray2, 0, MAXDATA);
"Attempted to Read/Write protected memory. This is often an indication
that other memory is corrupt".
If you encounter the above exception in your program, then it could mean the following:
a. Parameters passed may be wrong
b. Number of parameters may not be matching
c. DLL may not be present/deleted
d. DLL's architecture may not be matching (x64/x32)