Monday, April 28, 2008

Accessing native API from C#

The .Net Compact Framework, in his CLR (Common Language Runtime), contains a fixed set of API, and when working on an embedded device running Windows Embedded CE, most of the time, the managed code developers require access to some native APIs like custom DLL provided by a third party company. In this case the required APIs cannot be directly accessed in C# by the application and developer have to use the P/Invoke mechanism for the marshalling of the data from the managed to the native environment.

[DllImport ("coredll.dll")]
private static extern int CreateFile(
string lpFileName, int dwDesiredAccess,
int dwShareMode, int lpSecurityAttributes,
int dwCreationDisposition, int dwFlagsAndAttributes,
int hTemplateFile);


The DllImport macro is used to specify to the CLR that the next function declared is located inside a native dll, coredll.dll for instance. The tricky operation when defining Marshalling operation is the way to define the native types into managed types. Because if the wrong type definition is used, the CLR is going to Marshall the data in a way that is not expected by the native functions, and can be the reason of a crash or memory leaks on the system (null pointer, bad type conversion, …).

Accessing Device Drivers from C#
You have the same issue when trying to access device drivers from a C# application as the .Net Compact Framework do not contain any classes that allow developer to load a open driver and access it. Under Windows Embedded CE, device drivers are managed by the device manager, and in any case you do not access a driver directly through its Dll. You always have to use file system API to do so. For this reason, the CreateFile, CloseHandle, WriteFile, ReadFile, SetFilePojnter and DeviceIoControl functions should be redefined in C# to specify the Marshalling of the various functions’ parameters.

[DllImport ("coredll.dll")]
private static extern int CreateFile(
string lpFileName,
int dwDesiredAccess,
int dwShareMode,
int lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
int hTemplateFile);

[DllImport ("coredll.dll")]
private static extern int CloseHandle(int hObject);

[DllImport ("coredll.dll")]
private static extern int ReadFile(
int hFile,
byte[] lpBuffer,
int nNumberOfBytesToRead,
ref int lpNumberOfBytesRead,
ref OVERLAPPED lpOverlapped);

[DllImport ("coredll.dll")]
private static extern int WriteFile(
int hFile,
byte[] lpBuffer,
int nNumberOfBytesToWrite,
ref int lpNumberOfBytesWritten,
ref OVERLAPPED lpOverlapped);

[DllImport ("coredll.dll")]
private static extern int SetFilePointer(
int hFile,
int nDistanceToMove,
ref int lpDistanceToMoveHigh,
uint nMoveMethod);


[DllImport ("coredll.dll")]
private static extern int DeviceIoControl(
int hFile,
uint dwIoControlCode,
byte[] lpInBuffer,
uint nInBufferSize,
byte[] lpOutBuffer,
uint nOutBufferSize,
ref uint lpBytesReturned,
ref OVERLAPPED lpOverlapped);


The OVERLAPPED parameter, used by ReadFile, WriteFile and DeviceIoControl, should also be re-defined as this type is unknown in C#. In native code this object is a structure of basic types and will be defined like this in C#:
private class OVERLAPPED
{
public int Internal; // Reserved for operating system use.
public int InternalHigh; // Reserved for operating system use.
public int Offset; // Specifies a file position at which to start the transfer.
public int OffsetHigh; // Specifies the high word of the byte offset at which to start the transfer.
public int hEvent; // Handle to an event set to the signaled state when the operation has been completed.
}


To have a nice way to use those different native functions, the DllImport definition should be wrapped inside a standard C# class and public function with managed types parameters can be provided to the developer.
For example the Open function published by a device driver wrapper class could be write like this :

int m_intHandle = INVALID_HANDLE_VALUE;

public bool Open(String strDriverName)
{
m_intHandle = CreateFile(strDriverName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
return m_intHandle != INVALID_HANDLE_VALUE;
}

Avoid Memory Leaks
Unlike managed code when allocating memory in native, do not forget to explicitly destroy those objects to avoid memory leaks. Even if you are accessing the native code from a managed application, the Garbage Collector is not able to detect those memory objects and freed them for you.


- Nicolas

3 comments:

Unknown said...

How does one use this to query the battery? Pleas help...

Nicolas BESSON [MVP] said...

Vicken,
The battery informations under Windows CE are retrieved using the GetSystemPowerStatusEx function, and unfortunately this feature is not accessible in the .Net Compact Framework. So are discussed in the post, you have to manually map this API (GetSystemPowerStatusEx) located inside coredll.dll using the DllImport macro in your C# code. It should be done like this :
[DllImport("coredll.dll")]
private static extern bool GetSystemPowerStatusEx(ref SYSTEM_POWER_STATUS_EX pstatus, bool fUpdate);
Then all the native types have to be translated into C# types.
private struct SYSTEM_POWER_STATUS_EX
{
public byte ACLineStatus;
public byte BatteryFlag;
public byte BatteryLifePercent;
public byte Reserved1;
public int BatteryLifeTime;
public int BatteryFullLifeTime;
public byte Reserved2;
public byte BackupBatteryFlag;
public byte BackupBatteryLifePercent;
public byte Reserved3;
public int BackupBatteryLifeTime;
public int BackupBatteryFullLifeTime;
}

BR

Nicolas

Anonymous said...

Once again I stumbled upon your blog and I would like to thank you now for your very good articles. Please keep them going...