Showing posts with label .Net Compact FrameWork. Show all posts
Showing posts with label .Net Compact FrameWork. Show all posts

Wednesday, November 3, 2010

Driver wrapper for C#

From .NET Compact Framework, there is no API allowing access to the driver interfaces, as the main reason of the framework existance is to be able to run on whatever hardware an application without having to recompile it, this by the usage of the CLR (Common Language Runtime).

But developper working on embedded systems usually have to access those drivers that are specific to the platform. So in that case they need an access to the native APIs.

Identify the needs
Before getting access to the driver from C# you have to identify the APIs that have to be mapped from native to managed environment. Accessing a driver from native code is performed using the following APIs :
- CreateFile : in order to open a driver instance
- CloseHandle : to close the opened instance
- ReadFile : read data from the stream
- WriteFile : write data into the stream
- Seek : move data pointer in the stream
- DeviceIoControl : to perform driver specific actions with the usage of the CTL_CODE macro for commands IDs

Map the native API
In order to map the native API into C#, the usage of the interop services is required.
Note : you can find native API mapping from pinvoke.net (http://www.pinvoke.net/) website.

Create a driver class
We have to define the Driver class that will handle all the wrapping actions for us.


using System;
using System.Runtime.InteropServices;

namespace Adeneo_Embedded
{
public class Driver
{
}
}


Map the mandatory native functions

  • CreateFile function

// This function creates, opens, or truncates a file, communications
// resource, disk device, or console. It returns a handle that can be
// used to access the object. It can also open and return a handle to
// a directory.
[DllImport ("coredll.dll")]
private static extern int CreateFile(
string lpFileName,
int dwDesiredAccess,
int dwShareMode,
int lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
int hTemplateFile);


  • CloseHandle function

// This function closes an open object handle
[DllImport ("coredll.dll")]
private static extern int CloseHandle(int hObject);


  • ReadFile function

// This function reads data from a file, starting at the position indicated
// by the file pointer. After the read operation has been completed, the
// file pointer is adjusted by the number of bytes actually read.
[DllImport ("coredll.dll")]
private static extern int ReadFile(
int hFile,
byte[] lpBuffer,
int nNumberOfBytesToRead,
ref int lpNumberOfBytesRead,
ref OVERLAPPED lpOverlapped);


  • WriteFile function

// This function writes data to a file. WriteFile starts writing data to
// the file at the position indicated by the file pointer. After the write
// operation has been completed, the file pointer is adjusted by the number
// of bytes actually written.
[DllImport ("coredll.dll")]
private static extern int WriteFile(
int hFile,
byte[] lpBuffer,
int nNumberOfBytesToWrite,
ref int lpNumberOfBytesWritten,
ref OVERLAPPED lpOverlapped);


  • DeviceIoControl function

// This function sends an IOCTL directly to a specified device driver,
// causing the corresponding device to perform the specified operation.
[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);


Redefine the CTL_CODE macro
When driver developper is implementing the driver IoControls and when the application developper want to execute this command, then both should refer to the same identifier. To get a unique identifier for a driver command, the CTL_CODE macro is used.


//
// Macro definition for defining IOCTL and FSCTL function control codes. Note
// that function codes 0-2047 are reserved for Microsoft Corporation, and
// 2048-4095 are reserved for customers.
//

public uint CTL_CODE(uint DeviceType, uint Function, uint Method, uint Access )
{
}


Redefine the constant values
CreateFile, DeviceIoControl, and CTL_CODE code is using constant that also have to be redefined in our driver class.


#region "constants"
private const int GENERIC_READ = unchecked((int)0x80000000);
private const int GENERIC_WRITE = 0x40000000;
private const int OPEN_EXISTING = 3;
private const int INVALID_HANDLE_VALUE = -1;

#region "CTL_CODE"
#region "Method"
//
// Define the method codes for how buffers are passed for I/O and FS controls
//
public const uint METHOD_BUFFERED = 0;
public const uint METHOD_IN_DIRECT = 1;
public const uint METHOD_OUT_DIRECT = 2;
public const uint METHOD_NEITHER = 3;
#endregion // "Method"

#region "Access"
//
// Define the access check value for any access
//
//
// The FILE_READ_ACCESS and FILE_WRITE_ACCESS constants are also defined in
// ntioapi.h as FILE_READ_DATA and FILE_WRITE_DATA. The values for these
// constants *MUST* always be in sync.
//

public const uint FILE_ANY_ACCESS = 0;
public const uint FILE_READ_ACCESS = ( 0x0001 ); // file & pipe
public const uint FILE_WRITE_ACCESS = ( 0x0002 ); // file & pipe
#endregion // "Access"

#region "DeviceType"
// begin_ntddk begin_nthal begin_ntifs
//
// Define the various device type values. Note that values used by Microsoft
// Corporation are in the range 0-32767, and 32768-65535 are reserved for use
// by customers.
//
public const uint FILE_DEVICE_BEEP = 0x00000001;
public const uint FILE_DEVICE_CD_ROM = 0x00000002;
public const uint FILE_DEVICE_CD_ROM_FILE_SYSTEM = 0x00000003;
public const uint FILE_DEVICE_CONTROLLER = 0x00000004;
public const uint FILE_DEVICE_DATALINK = 0x00000005;
public const uint FILE_DEVICE_DFS = 0x00000006;
public const uint FILE_DEVICE_DISK = 0x00000007;
public const uint FILE_DEVICE_DISK_FILE_SYSTEM = 0x00000008;
public const uint FILE_DEVICE_FILE_SYSTEM = 0x00000009;
public const uint FILE_DEVICE_INPORT_PORT = 0x0000000a;
public const uint FILE_DEVICE_KEYBOARD = 0x0000000b;
public const uint FILE_DEVICE_MAILSLOT = 0x0000000c;
public const uint FILE_DEVICE_MIDI_IN = 0x0000000d;
public const uint FILE_DEVICE_MIDI_OUT = 0x0000000e;
public const uint FILE_DEVICE_MOUSE = 0x0000000f;
public const uint FILE_DEVICE_MULTI_UNC_PROVIDER = 0x00000010;
public const uint FILE_DEVICE_NAMED_PIPE = 0x00000011;
public const uint FILE_DEVICE_NETWORK = 0x00000012;
public const uint FILE_DEVICE_NETWORK_BROWSER = 0x00000013;
public const uint FILE_DEVICE_NETWORK_FILE_SYSTEM = 0x00000014;
public const uint FILE_DEVICE_NULL = 0x00000015;
public const uint FILE_DEVICE_PARALLEL_PORT = 0x00000016;
public const uint FILE_DEVICE_PHYSICAL_NETCARD = 0x00000017;
public const uint FILE_DEVICE_PRINTER = 0x00000018;
public const uint FILE_DEVICE_SCANNER = 0x00000019;
public const uint FILE_DEVICE_SERIAL_MOUSE_PORT = 0x0000001a;
public const uint FILE_DEVICE_SERIAL_PORT = 0x0000001b;
public const uint FILE_DEVICE_SCREEN = 0x0000001c;
public const uint FILE_DEVICE_SOUND = 0x0000001d;
public const uint FILE_DEVICE_STREAMS = 0x0000001e;
public const uint FILE_DEVICE_TAPE = 0x0000001f;
public const uint FILE_DEVICE_TAPE_FILE_SYSTEM = 0x00000020;
public const uint FILE_DEVICE_TRANSPORT = 0x00000021;
public const uint FILE_DEVICE_UNKNOWN = 0x00000022;
public const uint FILE_DEVICE_VIDEO = 0x00000023;
public const uint FILE_DEVICE_VIRTUAL_DISK = 0x00000024;
public const uint FILE_DEVICE_WAVE_IN = 0x00000025;
public const uint FILE_DEVICE_WAVE_OUT = 0x00000026;
public const uint FILE_DEVICE_8042_PORT = 0x00000027;
public const uint FILE_DEVICE_NETWORK_REDIRECTOR = 0x00000028;
public const uint FILE_DEVICE_PARTITION = 0x00000029;
public const uint FILE_DEVICE_STORE = 0x00000030;

#endregion // "DeviceType"


#endregion // "CTL_CODE"
#endregion // "constants"


Filling the empty egg
A bit of additional work is required to accomplish our task, the driver class goal is to map the driver access within C#, but also an abstraction class to deeply simplify the access to the driver and then offer a servicing class for your application.

#region "private members"
private int mintHandle = INVALID_HANDLE_VALUE; // driver handle
#endregion // "private members"

#region "Constructor-Destrcutor"
public Driver()
{
}

~ Driver ()
{
if (mintHandle != INVALID_HANDLE_VALUE)
Close ();
}
#endregion // Constructor-Destrcutor

#region "Open"
public bool Open(String strDriverName)
{
if (mintHandle != INVALID_HANDLE_VALUE)
{
Close ();
}

mintHandle = CreateFile(strDriverName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);

return mintHandle != INVALID_HANDLE_VALUE;
}
#endregion // open the driver

#region "Close"
public bool Close()
{
bool bResult = false;
int iValue;
if (mintHandle != INVALID_HANDLE_VALUE)
{
iValue = CloseHandle (mintHandle);
if (iValue != 0)
{
bResult = true;
mintHandle = INVALID_HANDLE_VALUE;
}
}

return bResult;
}
#endregion // close the driver

#region "Read"
public bool Read(byte []buffer, ref int lpBytesToRead)
{
bool bResult = false;
int lpNumberOfBytesRead = 0;
int intResult = 0;
OVERLAPPED lpOverlapped = new OVERLAPPED();

if (mintHandle != INVALID_HANDLE_VALUE)
{
intResult = ReadFile(mintHandle, buffer, lpBytesToRead, ref lpNumberOfBytesRead, ref lpOverlapped);

if (intResult == 0)
{
throw new Exception("Error reading driver");
}
else
{
lpBytesToRead = lpNumberOfBytesRead;
bResult = true;
}
}

return bResult;
}
#endregion // read data from the driver

#region "Write"
public bool Write(byte []buffer, ref int lpBytesToWrite)
{
bool bResult = false;
int lpNumberOfBytesWrite = 0;
int intResult = 0;
OVERLAPPED lpOverlapped = new OVERLAPPED();

if (mintHandle != INVALID_HANDLE_VALUE)
{
intResult = WriteFile(mintHandle, buffer, lpBytesToWrite, ref lpNumberOfBytesWrite, ref lpOverlapped);

if (intResult == 0)
{
throw new Exception("Error writing driver");
}
else
{
lpBytesToWrite = lpNumberOfBytesWrite;
bResult = true;
}
}

return bResult;
}
#endregion // read data from the driver

#region "IOControl"
public bool IOControl(uint IoControlCode, byte []bufferIn, uint bufferSizeIn, byte []bufferOut, ref uint lpBufferSizeOut)
{
bool bResult = false;
uint lpNumberOfBytesReturned = 0;
int intResult = 0;
OVERLAPPED lpOverlapped = new OVERLAPPED();

if (mintHandle != INVALID_HANDLE_VALUE)
{
intResult = DeviceIoControl(mintHandle, IoControlCode, bufferIn, bufferSizeIn, bufferOut, lpBufferSizeOut, ref lpNumberOfBytesReturned, ref lpOverlapped);

if (intResult == 0)
{
throw new Exception("Error IOcontrol driver");
}
else
{
lpBufferSizeOut = lpNumberOfBytesReturned;
bResult = true;
}
}

return bResult;
}
#endregion // read data from the driver

This is it, now we have a class that really simplify the access to the drivers.


Driver myI2CDriver = new Driver();
myI2CDriver.Open("I2C1:");
…
myI2CDriver.Close();


- Nicolas

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

Thursday, March 20, 2008

.Net Compact Framework 3.5

The January Windows CE 4.x, 5.0 and 6.0 QFEs (Quick Fix Engineering) enable the .Net CF 3.5 for Windows CE. Now this feature can be directly integrated inside a runtime image by selecting the associated component in the platform builder catalog. The .Net CF 3.5 introduce new graphical components and fix some well know issues of the framework.





Windows Embedded CE QFEs are accessible here

- Nicolas

Business Solution Accelerator 2008

Need help to implement features in your .NET Compact Framework 3.5 application, Microsoft just released the Business Solution Accelerator 2008 for Windows Mobile, providing free and reusable source code and associated documentation.
In the mean time, the Windows Mobile blog will in the future discuss of the best practices in .Net CF development (blog link).

[Updated] Microsoft's Reed and Steve wrote a complete article on this LOB Solution Accelerator.
[Updated] See also Fabien's blog article for details about it.

The complete package and detail are accessible from the Microsoft Web site.
- Nicolas