Windows CE to Raspbian – Pt. 2: Preparation

This is part two of a series of posts: click here for the first post.

The purpose of this particular post is to give a more in-depth look at the particulars when porting .NET code to a non-Windows operating system: in this case Raspbian. This will also cover preparing an environment for debugging the solution now it’s no longer running on Windows.

Preparing the Solutions


Changing Filepaths

As mentioned in the previous post one of the most awkward differences between Windows and Linux is the file system.

When porting how a program uses the file system the first thing to check is how file paths are built up. In the case of these services simply searching across the solution for any use of the back slash ‘\’ was enough to find all the issues.

string path = Utils.BasePath + "\\" + filename;

The above will result in the back slash being included in the filename, as well as the ‘BasePath’ potentially being appended to it. There are two ways to resolve this. First using the platform specific path separator:

string path = Utils.BasePath + Path.PathSeparator + filename;

This will use the correct syntax for separating directories and files. However a better way would be to use the combine function:

string path = Path.Combine(Utils.BasePath, filename);

In .NET 2.0 this function will only accept two strings and combines them appropriately for the platform, but in later versions of .NET it accepts any number of strings. Using the combine function is a cleaner and more readable way of building up file paths.

Changing Data File Locations

Linux does not have the same file structure as Windows, not only are the path separators different but the location of ‘Program Files’, ‘My Documents’ and the various application data storages are completely different.

In the case of these services the Utils.BasePath is generated using ‘Program Files’ and finding the installed parent folder. This is bad not only in Linux but for later versions of Windows, such as Windows 8, as programs no longer have the right to write to ‘Program Files’ unless explicitly stated.

The best way to find a place to store data is to query .NET for an appropriate location using the environments special folders. There are several special folders that .NET can find, a full list of which can be found in a previous post, some of which exist in Linux and some do not.

public string BasePath
{
    get
    {
        return Path.Combine
        (
            Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
            "MyService"
        );
    }
}

In this case we’re going to use ‘Common Application Data’, as this is accessible regardless of the user for Windows and it exists in Raspbian. For Windows this would return something similar to the following:

C:\ProgramData\MyService

But in Raspbian this would return:

/usr/share/MyService

For Linux the case used is important as file paths are case sensitive.

The actual file path generated is largely irrelevant as this is dynamically generated during run time and as long as  the same path is referenced then the logical outcome should be identical between Windows and Raspbian.

Abstracting Logging

Windows Event Log is the common way of logging events from a Windows service, however unsurprisingly this is not available in Linux. Creating a layer between the Windows Event Log and the event logging used in the service is the cleanest way of using platform specifics like the Windows Event Log.

In this case an abstract class was utilised that contains all the same functions the Windows Event Log requires in .NET, then a subclass was created that passes the variables straight to the Windows Event Log and another subclass where the events are stored in a text file located within ‘Common Application Data’.

using System;
using System.Text;
using System.Collections.Generic;

#if WindowsCE
using CEHelpers.EventLog;
#else
using System.Diagnostics;
#endif

namespace MyService.EventLog
{
    public abstract class EventLog
    {
        public static EventLog Log { get; set; }

        static EventLog()
        {
            switch (OSVersion.RunningPlatform)
            {
                case OSVersion.Platform.Widnows:
                case OSVersion.Platform.WindowsCE:
                    Log = new EventLogEventsWindows();
                    break;

                case OSVersion.Platform.Linux:
                    Log = new EventLogEventsLinux();
                    break;
            }
        }

        public void LogMessage(string source, string message, EventLogEntryType type)
        {
            LogMessage(source, message, type, false);
        }

        public abstract void LogMessage(string source, string message, EventLogEntryType type, bool alwaysSendToServer);
    }
}

By accessing the new event log through a static pointer the event log can be initialised during static construction  for the appropriate platform. This is also useful for debugging, as you can create an event log that simply outputs each event to the console.

The above example creates the same logging class for both Windows and Windows CE, despite Windows CE 6.0 not supporting the Windows Event Log. The reason for this is due to that class having some special handling for Windows CE based on preprocessor directives, however best practice would be to have a third subclass for Windows CE and instantiate that instead.

Changing FTP Library

Using .NET 2.0 compact edition required a third party library to be used for FTP; the library chosen was Rebex. In 4.5 full fat however there is an FTP library included. Although this works fine on Windows, there was an issue using this library on Linux via Mono. After some extensive debugging and searching online it appeared that the issue occurred when an FTP connection had previously failed, then another connection was attempted. Somewhere a failed state was being stored and causing the connection to throw an exception on another thread, which caused the entire program to crash.

After trying many different FTP library the one chosen was System.Net.FtpClient, which despite appearances is not part of the .NET framework.

var host = string.Format
(
	"ftp://{0}:{1}@{2}/",
	_username,
	_password,
	_ipAddress
);

using (var get = FtpClient.OpenRead(new Uri(host + _ftpReadFilename.ToUpperInvariant())))
using (var reader = new StreamReader(get))
using (var fs = new FileStream(ftpTargetTemp, FileMode.OpenOrCreate))
using (var bw = new BinaryWriter(fs))
{
	bw.Write(reader.ReadToEnd());
	reader.Close();
	get.Close();
}

This library is fairly easy to use and actually required a lot less lines to use than the .NET FTP library or Rebex for Windows CE.

Calculating the Platform

Although .NET has a way of calculating the running platform, the diference between Mac OSX and Linux isn’t always correct. This class is based off an example which I can no longer find, however expanding it to take into account Windows CE.

using System;
using System.IO;

namespace MyServices.Utilities
{
    public static class OSVersion
    {
        public enum Platform
        {
            Windows,
            WindowsCE,
            Linux,
            Mac
        }

        public static Platform RunningPlatform { get; private set; }

        static OSVersion()
        {
            RunningPlatform = CalculateRunningPlatform();
        }

        /// <summary>
        /// Calculates the current platform type. This should only be called from static constructors as after this the platform has already been calculated and stored.
        /// </summary>
        /// <returns>Current platform type.</returns>
        public static Platform CalculateRunningPlatform()
        {
            // Calculate the platform
#if WindowsCE
            RunningPlatform = Platform.WindowsCE;
#else
            switch (Environment.OSVersion.Platform)
            {
                case PlatformID.Unix:
                    // Well, there are chances MacOSX is reported as Unix instead of MacOSX.
                    // Instead of platform check, we'll do a feature checks (Mac specific root folders)
                    if (Directory.Exists("/Applications")
                        & Directory.Exists("/System")
                        & Directory.Exists("/Users")
                        & Directory.Exists("/Volumes"))
                        return Platform.Mac;
                    else
                        return Platform.Linux;

                case PlatformID.MacOSX:
                    return Platform.Mac;

                default:
                    return Platform.Windows;
            }
#endif
        }
    }
}

The concept behind using an enumeration for the running platform is to reduce the need to use preprocessor directives, but instead switch dynamically which leads to cleaner code. In the above code a preprocessor directive is used for Windows CE, this was used as it already exists for compilation. It’s difficult to remove all checks as some platforms use libraries which are only available for that platform, for example Windows CE.

Changing Visual Studio ‘ToolsVersion’ Value

When migrating the solutions from Visual Studio 2013 to Mono Develop they failed to load due to the ‘ToolsVersion’ being too high. Looking at the XML for the project files (.csproj) for each project associated with the solutions the tools version defaults to twelve.

<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

Changing the value down to four allowed Mono Develop to successfully load each project.

After having created several projects more recently from Visual Studio and loaded them in Mono Develop this has not caused a problem, however if it ever does become an issue this is the way to resolve it.

Choosing the Environment


Operating System

The original development environment used was Windows, this was due to .NET only being abailable on Windows at the time as well as the platforms being very similar in run time. However, moving forward to Raspbian and other Debian distros this is no longer the case. It’s good practice to develop on a platform as close to the target platform as possible in order to locate some of the more obscure caveats for that environment and debug them more effectively.

Screenshot_2015-05-31_23-08-44

The operating system used to develop the port for these services was Xubuntu, simply because both Raspbian and Xubuntu are based on Debian and Xubuntu is quite easy to work with.

During the initial port an interesting issue occurred when using  the asynchronous UDP listener for .NET 2.0 via Mono. When reading the packet a string was return of the correct length, however every character was null. This did not occur with the synchronous UDP listener and not on later versions of .NET via Mono. As this has never happened previously on Windows, this is a good example of why using a platform closer to the intended platform is an efficient way of testing and debugging ports.

IDE

In order to debug on Xubuntu a new IDE was needed, as Visual Studio is not available on Linux. The IDE chosen was Mono Develop, this was largely due to Mono Develop being able to read Visual Studio solutions files with few issues (see “Changing Visual Studio ‘ToolsVersion’ Value” above).Screenshot_2015-05-31_23-09-57

Mono Develop is an open source IDE that’s available via the package manager.

There are a number of differences between Visual Studio and Mono Develop, the most stark of which is how C# code is styled by default (for example: hanging braces). That being said changing the default is incredibly easy and there are more themes out of the box than Visual Studio, so if you like a darker theme for reading then there are plenty more to choose from.

Advertisements

3 thoughts on “Windows CE to Raspbian – Pt. 2: Preparation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s