Export (0) Print
Expand All

P/Invoking Serial APIs in the Compact Framework

.NET Compact Framework 1.0
 

Chris Tacke, Windows Embedded MVP
Applied Data Systems

October 2003

Applies to
   Microsoft® .NET Compact Framework
   Microsoft® Windows CE .NET
   Microsoft® Visual Studio .NET 2003

Summary: This paper considers specific challenges the .NET Compact Framework presents when controlling serial communications from managed code. Design principles, portable code, and implementation of P/Invoking are discussed. (7 printed pages)

Download SerialCommunicationsSampleSetup.exe.


Contents

Design Principles
Platform-Agnostic P/Invoking
Implementation
Conclusion

If you read John Hind's excellent article entitled "Use P/Invoke to Develop a .NET Base Class Library for Serial Device Communications" in the October, 2002 issue of MSDN Magazine, you've got good insight into the nuts and bolts of getting at the APIs for controlling serial communications from managed code. If you haven't, I recommend you read it before continuing with this article because instead of doing a start-to-finish walk through of P/Invoking the APIs with the .NET Compact Framework, I'm going to highlight the differences and specific challenges the .NET Compact Framework presents.

Design Principles

I chose to approach the serial APIs differently than John did in his article. Instead of providing a base class, which you then can inherit to provide a robust serial driver class of your own, I chose to build a replacement MSComm OLE Control Extension (OCX). This is especially important with the .NET Compact Framework because there is no COM interop, so using the existing control is not an option without a lot of work writing a "flat" shim or wrapper.

While I tried to maintain interface compatibility with the legacy control, I did take some liberties and change things that I personally don't like about the old control. I also made modifications that are more conducive to the .NET paradigm.

First, instead of having a single OnComm event in which the developer must put a gigantic switch or Case statement, I opted to expose several different events like DataReceived, OnError, and RingChange. This allows the developer to choose the events he or she is interested in and only implement event handlers for those events.

Second, the original control bothered me in its extremely limited ability to adjust settings on the serial port. The OCX allowed you to change the baud rate, parity, stop bits, and byte length and that was about it. In my class, I've exposed just about everything in the DCB (port settings) structure. (For a better understanding of the options take a look in the Help files for eMbedded Visual Tools or Platform Builder under "DCB"). Now, instead of the original control's Settings string (which accepted something like "9600,8,N,1") the class accepts a BasicSettings structure (which wraps these four parameters) or a DetailedSettings class (which adds an additional 16 settings). Furthermore you can inherit the DetailedSettings class yourself to provide a common settings structure, which I've done for the four most common serial handshaking protocols.

Third, I implemented the Input and Output properties to read and write to the port as byte arrays instead of text. I feel that it's more useful to provide byte capabilities and let the developer convert that to text rather than force the developer to do code gymnastics to get their binary data into a string format.

Finally, since one of the promises of .NET is to make code portable, and since code compiled for the .NET Compact Framework will in fact run on the full .NET Framework (with a bit of massaging for the P/Invoke declarations), I decided to make the code work both on the desktop and in Microsoft® Windows® CE.

All of these I wrapped up into a Port class, along with several helper classes, structs and enums plus an internal class with all of the desktop and Windows CE P/Invoke declarations and the wrapper functions that allow portability.

The Original Control: A Review

For those of you unfamiliar with the original control, it is worth highlighting how it is used, since it's not exactly intuitive.

Changing the settings, opening and closing the port, and most of the properties are self-explanatory. What isn't so obvious is reading and writing data and what the Rthreshold and SThreshold are.

The control has two internal buffers: one for sending and one for receiving. When you write an array of bytes to Output they go to the output buffer to be sent. When the number of bytes in the output buffer is equal to or greater than SThreshold, the bytes are sent.

As bytes are received on the port they go into the input buffer, which is a first-in, first-out (FIFO) buffer. When the number of bytes in the input buffer is equal to or greater than RThreshold, the DataReceived event fires, telling you to remove the data from the buffer. When you call Input, an array of bytes comes out of the input buffer and those bytes are removed from the buffer. InputLen returns the actual number of bytes in the input buffer.

The Port class defaults both buffers to 1024 bytes, so if you aren't reading input fast enough to prevent it from reaching that limit, you will get an RxOverrun event and the bytes past 1024 will get thrown away.

Platform-Agnostic P/Invoking

The first big challenge I ran into was my desire to make the compiled DLL work not just on any device running the .NET Compact Framework, but also any desktop running the full .NET Framework. Sure, the promise of .NET says that code is compiled to platform-agnostic Microsoft Intermediate Language (MSIL), but that assumes that all of the native functions the MSIL is calling exist.

As you are probably aware, Windows CE exposes quite a bit less in its core Windows API than the desktop, but fortunately it's a subset. This means that everything available to the desktop might not be available to Windows CE, but everything available in Windows CE is available on the desktop.

It is this premise that makes it possible to write portable code. If we get MSIL that compiles and runs on a Windows CE device, it will "automagically" run on the desktop. Well almost. There are a couple additional twists.

When using P/Invoke, you must name the library (DLL) from which you are calling your functions. Unfortunately, the functions that are on both the desktop and Windows CE are rarely in the same library. In the case of the Comm APIs, they're in kernel32.dll on the desktop and coredll.dll on CE. In addition, the desktop uses ANSI by default for its strings, but CE always uses Unicode. Both of these must be taken into account for the code to be portable.

I've addressed both of these by creating API wrapper methods in the CommAPI class. For example, consider CreateFile, which faces both of these hurdles. First, I created the P/Invoke declarations and provided the exposed names of each with a prefix, so on Windows CE it's called CECreateFile and on the desktop it's called WinCreateFile.

I then created a wrapper function called CreateFile that first determines the platform on which it is running and then calls the appropriate P/Invoke method.

internal static IntPtr CreateFile(string FileName)
{
   uint access = GENERIC_WRITE | GENERIC_READ;
   if(Environment.OSVersion.Platform == 
                  PlatformID.Win32Windows)
      {
           return WinCreateFileW(FileName, access, 0,               
                            IntPtr.Zero, OPEN_EXISTING, 
                            0, IntPtr.Zero);
      }
      else
      {
            return CECreateFileW(FileName, access, 0, 
                                 IntPtr.Zero, OPEN_EXISTING, 
                                 0, IntPtr.Zero);
      }
}
[DllImport("kernel32.dll", EntryPoint="CreateFileW", 
           SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr WinCreateFileW(String lpFileName, 
           UInt32 dwDesiredAccess, UInt32 dwShareMode,
           IntPtr lpSecurityAttributes, UInt32 dwCreationDisposition, 
           UInt32 dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("coredll.dll", EntryPoint="CreateFileW", 
           SetLastError = true)]
private static extern IntPtr CECreateFileW(String lpFileName, 
           UInt32 dwDesiredAccess, UInt32 dwShareMode,
           IntPtr lpSecurityAttributes, UInt32 dwCreationDisposition, 
           UInt32 dwFlagsAndAttributes, IntPtr hTemplateFile);

Also note that my P/Invoke declarations include SetLastError = true. Strangely, this is set to false by default in C#, so if you don't include this directive, all calls to Marshal.GetLastWin32Error will return zero.

Implementation

If you browse through the source code for the Port() class you will see that the bulk of activity happens in Open and CommEventThread. These two are also intertwined with some thread synchronization, so let's take a look at them both.

Open

The Open method starts by opening the port with a call to CreateFile and then, after checking for success, setting the serial queue sizes with a call to SetupComm. Once that has been taken care of, the actual port settings are set up using a DCB structure. The native DCB structure is a bit of a challenge to implement because it has 13 settings packed into the lower 15 bits of a DWORD.

Managed code doesn't allow us to create a structure like C in which we can identify members to a specific bit position within a byte or set of bytes, so to simplify things (but still make them readable to a developer), it is useful to wrap the bit shifting logic into another class – in this case the DCB class. Instead of reinventing the wheel I've opted to use the one provided by Microsoft as part of the Microsoft .NET Framework SDK Code Samples.

The DCB class exposes the individual bits through properties, each of which is prefixed with an "f". The class then uses internally declared masks against a single internal 32-bit control value to set or get the value at a specific bit position.

Since the DCB class still exposes a bit more than I would like a consumer of my Serial class to have access to, I've wrapped the DCB class with yet another pair of classes called BasicPortSettings and DetailedPortSettings.

These classes allow a developer to either create one of the base classes or pass it to the Settings property of the Port class and the Port class fills in the rest. In addition, you can easily inherit these classes to create your own settings. I've provided 4 classes that inherit from DetailedPortSettings as examples. These derived classes provide settings for four industry standard serial handshaking protocols: none, XOn/Off, CTS/RTS and DSR/DTR. Once the DCB is populated, they are applied to the open port with a call to SetCommState.

After setting the port state, the Port class sets the timeouts for both sending and receiving. It is important to note here that the published documentation on the read timeouts is incorrect. If you look up ReadIntervalTimeout in help it says "A value of zero indicates that interval time-outs are not used." After some testing, frustration, and then a closer look at the newer source for John's article, I came to the conclusion that this is just not true. Setting ReadIntervalTimeout to zero blocks infinitely. Instead, setting it to uint.MaxValue (0xFFFFFFFF) returns immediately.

The final task that Open achieves is to start a worker thread. This worker thread runs in the background as long as the port is open and listening for port events or incoming data. We'll go into its implementation in a bit, but first we need to look at a basic synchronization object – the ManualResetEvent.

When we start the worker thread, we want to make sure it successfully spins up and begins listening on the port just in case our Port class consumer tries to use the port immediately after calling Open. We do this by using a class-scoped ManualResetEvent I've called threadStarted.

"threadStarted" is created when the class is instantiated and initially set to non-signaled or false. Once we start the thread from Open by calling Thread.Run, we then wait for threadStarted to become signaled by calling its WaitOne method. WaitOne causes the current thread (the one running the Open method) to block until our worker thread calls threadStarted.Set. Using a ManualResetEvent is far better than polling because while the primary thread is waiting, it consumes no processor resources.

CommEventThread

CommThreadEvent is the real workhorse of the Port class. It continually monitors the open serial port for events, errors, and incoming data. From that information it raises .NET events, throws exceptions, or passes data to the input buffer. Let's do a high-level walk through of the implementation.

The first order of business is a call to CommSetMask to let the serial driver know that we want to be notified of all events. This means that our thread will receive all driver events on the port, but because of the way .NET events work, applications that consume our class only need to implement EventHandlers for those events they are interested in.

Next, the thread alerts the main calling thread that we've started (by setting the ManualResetEvent I mentioned before) and then enters a loop. This loop will continue as long as we have a valid port handle, which in normal circumstances, will be true as long as the port is open.

Next comes the heart of the thread: the call to WaitCommEvent. In our case WaitCommEvent does not block, but returns immediately with eventFlags holding bit values for any events the driver has to report.

It is possible for WaitCommEvent to fail, which should only happen if the port handle (hPort) becomes invalid. This, in turn, should happen only if the port is closed with a call to CloseHandle. While I've never seen a port handle become invalid for any other reason, this case provides an example of some thread synchronization routines.

The Close Diatribe

In a perfect world, we would like to avoid the handle becoming invalid and causing an error when we close the port – and doing so would most easily be accomplished by just killing the thread before closing the port. With the full .NET Framework that's as simple as calling Thread.Abort, but the .NET Compact Framework's Thread class is very limited and doesn't support Abort, so we have to use a little ingenuity.

As a workaround, I decided to use a system event to signal that the thread should kill itself. A system event is created by P/Invoking CreateEvent and then is set by calling EventModify. If you're familiar with the SetEvent, ResetEvent and PulseEvent APIs, these are all just kernel macros for EventModify, so I've implemented each as a wrapper in this example for continuity. Keep in mind that this technique will only work with a thread that can check for the system event, so if your thread can possibly block indefinitely, like with a call to WaitForSingleObject with INFINITE, then this will not help.

At any rate, the idea for the Port class is that when the port becomes invalid we fall into a handling routine. Inside that routine we then wait for a second to see if our system event is signaled, which Close does immediately after calling CloseHandle. If the event is signaled, then we know it was a valid call to close and we exit the thread gracefully, otherwise we throw an exception.

Another option here would have been to wait on the event outside the error handler so that in every loop iteration, after calling WaitCommEvent, we could have then checked for the exit event, which we would have set in Close prior to calling CloseHandle.

The reason I did not go this route is two-fold. First, since Close will happen far less often than I'll be calling WaitCommEvent, this should have less of an impact on performance. Second, and more importantly, if we change the timeouts and use WaitCommEvent so that it blocks for any amount of time, if the port becomes invalid during that blocking time, the code will immediately step into the error handler and the outer event check would be bypassed.

Continuing with CommThreadEvent

Next, CommThreadEvent does some bit masking of the eventFlags returned from WaitCommEvent to determine what, if anything, has happened and it then fires the appropriate .NET events to any class consumers.

The main event of interest is RXCHAR, which means data has been received. If the RXCHAR bit is found to be set, a call to ReadFile on the open port retrieves the data from the serial driver and we then put it into a global buffer in the class.

This raises the possibility of an error. Since we're in a worker thread, it is very possible that while we are writing to the global buffer, in the main thread that global buffer could be being read. In this implementation, reading the buffer causes the read data to be removed, which can result in data loss.

Once again we need the worker and main threads to be able to work together and once again we need to look at another synchronization object. This time we'll use a Mutex (which stands for Mutually Exclusive).

The idea behind the Mutex is that only one thread in the entire system, even across applications, can hold it at any given time. In our Port class we've created a Mutex that a thread must acquire by calling Mutex.WaitOne before they modify the buffer and then release the Mutex by calling Mutex.ReleaseMutex after modifying it.

So let's say that the worker thread is holding the Mutex and updating the buffer when a call to the Output property is made on the main thread. When the main thread hits the call to Mutex.WaitOne, it blocks until the worker thread is done and calls Mutex.ReleaseMutex. The converse is true if the main thread gets to WaitOne first – the worker thread must wait until the main thread releases the Mutex.

Conclusion

The Port class includes a lot of stuff and covering it all thoroughly is beyond the scope of this article, but I've tried to highlight all of the points and areas I think are key. The ability to generate code that is portable from Windows CE device to Windows CE device, as well as to the desktop has immense implications, many of which probably haven't really even been looked at yet. Keeping your code portable only makes your code more robust and more marketable.

I've also given you a cursory look at some of the available synchronization objects available in .NET and some of the limitations of the Thread class in the .NET Compact Framework. Again, this was just cursory and I recommend that you become familiar with all of the Threading namespace and synchronization objects as they are an essential part of any successful application.

Lastly I can't stress enough how important it is to get a good understanding of P/Invoke, especially for Smart Devices. The .NET Compact Framework simply cannot provide wrapper classes for everything you might encounter and if you understand how to both read and author P/Invoke declarations you'll be well ahead when it comes to extending the .NET Compact Framework capabilities to meet your applications needs.

Show:
© 2014 Microsoft