The IrSquirtCF Example
The IrSquirtCF example program demonstrates many of the features of the Compact Framework as well as some of the techniques discussed in this chapter. IrSquirtCF is a managed version of the IrSquirt program provided with the Pocket PC and the Smartphone, discussed in Chapter 14. The two programs are compatible. It's possible to send or receive a file using IrSquirtCF to or from another Windows CE system or a Windows XP system running IrSquirt. The source code for IrSquirtCF is shown in Listing 23-5.Listing 23-5: The IrSquirtCF source code
MyMsgWindow.cs
using System;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.WindowsCE.Forms;
namespace MySquirtCF
{
/// <summary>
/// Summary description for MyMsgWindow.
/// </summary>
public class MyMsgWindow : MessageWindow
{
public MyMsgWindow()
{
//
// TODO: Add constructor logic here
//
}
public string Text
{
get
{
StringBuilder sbText = new StringBuilder (256);
GetWindowText (this.Hwnd, sbText, sbText.Capacity);
return sbText.ToString();
}
set
{
SetWindowText (this.Hwnd, value);
}
}
public static IntPtr FindOtherWindow (string strTitle)
{
return FindWindow (null, strTitle);
}
protected override void WndProc(ref Message m)
{
base.WndProc (ref m);
}
/// <summary>
/// Returns true when running on a Pocket PC
/// </summary>
/// <returns></returns>
public static bool IsPocketPC ()
{
bool fPocketPC = false;
StringBuilder sb = new StringBuilder (256);
PrivGetPlatString (257, sb.Capacity, sb, 0);
string strPlat = sb.ToString();
if (strPlat == "PocketPC")
fPocketPC = true;
return fPocketPC;
}
// Used by IsPocketPC routine to get the platform string
[DllImport ("Coredll.dll", EntryPoint="SystemParametersInfoW")]
private static extern bool PrivGetPlatString (int Cmd,
int StrLen, StringBuilder strPlat, int fWinIni);
[DllImport ("coredll.dll", EntryPoint="FindWindowW")]
private static extern IntPtr FindWindow (string strClass,
string strTitle);
[DllImport ("coredll.dll", EntryPoint="SetWindowTextW")]
private static extern void SetWindowText (IntPtr h, string s);
[ DllImport( "coredll.dll", EntryPoint="GetWindowTextW")]
public static extern int GetWindowText (IntPtr h,
StringBuilder sysDirBuffer, int size );
}
}
Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Data;
using System.Net.Sockets;
using System.IO;
using System.Text;
using System.Threading;
namespace MySquirtCF
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.MainMenu mainMenu1;
private System.Windows.Forms.Button btnGo;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button btnBrowse;
private System.Windows.Forms.TextBox textFileName;
private System.Windows.Forms.ListBox listOut;
private System.Windows.Forms.OpenFileDialog openFileDialog1;
private Microsoft.WindowsCE.Forms.InputPanel inputPanel1;
private bool m_fPocketPC;
private bool isRunning;
private Thread thServ;
private MenuItem menuFile;
private MenuItem menuExit;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
// See if we're running on a Pocket PC
if (MyMsgWindow.IsPocketPC())
m_fPocketPC = true;
if (m_fPocketPC)
{
// On a Pocket PC, adjust control position since
// Commandbar isn't used.
this.label1.Location = new Point (4, 5);
this.textFileName.Location = new Point (46, 4);
this.btnBrowse.Location = new Point (4, 30);
this.btnGo.Location = new Point (100, 30);
this.listOut.Location = new Point (4, 54);
// Set size of listbox depending on SIP
int height = this.inputPanel1.VisibleDesktop.Height -
this.listOut.Top;
if (!this.inputPanel1.Enabled)
height -= 26;
this.listOut.Size = new Size (this.listOut.Width, height);
// Place OK button on Nav Bar to close application
this.ControlBox = true;
this.MinimizeBox = false;
}
else
{
// On an embedded device, add a File | Exit menu item
menuFile = new MenuItem();
menuExit = new MenuItem();
menuExit.Text = "Exit";
menuExit.Click += new EventHandler(menuExit_Click);
menuFile.Text = "File";
menuFile.MenuItems.Add (menuExit);
mainMenu1.MenuItems.Add (menuFile);
}
int widthC = this.ClientRectangle.Width;
int heightC = this.ClientRectangle.Height;
this.Text = "IrSquirt";
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.mainMenu1 = new System.Windows.Forms.MainMenu();
this.btnGo = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.textFileName = new System.Windows.Forms.TextBox();
this.btnBrowse = new System.Windows.Forms.Button();
this.listOut = new System.Windows.Forms.ListBox();
this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
this.inputPanel1 = new Microsoft.WindowsCE.Forms.InputPanel();
//
// btnGo
//
this.btnGo.Location = new System.Drawing.Point(160, 58);
this.btnGo.Size = new System.Drawing.Size(80, 20);
this.btnGo.Text = "Send";
this.btnGo.Click += new System.EventHandler(this.btnGo_Click);
//
// label1
//
this.label1.Location = new System.Drawing.Point(8, 33);
this.label1.Size = new System.Drawing.Size(32, 20);
this.label1.Text = "File:";
//
// textFileName
//
this.textFileName.Location = new System.Drawing.Point(48, 32);
this.textFileName.Size = new System.Drawing.Size(192, 22);
this.textFileName.Text = ";
//
// btnBrowse
//
this.btnBrowse.Location = new System.Drawing.Point(8, 58);
this.btnBrowse.Size = new System.Drawing.Size(80, 20);
this.btnBrowse.Text = "Browse";
this.btnBrowse.Click += new
System.EventHandler(this.btnBrowse_Click);
//
// listOut
//
this.listOut.Location = new System.Drawing.Point(8, 82);
this.listOut.Size = new System.Drawing.Size(232, 198);
//
// inputPanel1
//
this.inputPanel1.EnabledChanged += new
System.EventHandler(this.inputPanel1_EnabledChanged);
//
// Form1
//
this.ClientSize = new System.Drawing.Size(250, 292);
this.Controls.Add(this.listOut);
this.Controls.Add(this.btnBrowse);
this.Controls.Add(this.textFileName);
this.Controls.Add(this.label1);
this.Controls.Add(this.btnGo);
this.MaximizeBox = false;
this.Menu = this.mainMenu1;
this.Text = "Form1";
this.Closing += new
System.ComponentModel.CancelEventHandler(this.Form1_Closing);
this.Load += new System.EventHandler(this.Form1_Load);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
Application.Run(new Form1());
}
private void Form1_Load(object sender, System.EventArgs e)
{
isRunning = true;
this.thServ = new Thread (new ThreadStart(this.SrvRoutine));
this.thServ.Start();
}
private void Form1_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
isRunning = false;
Thread.Sleep(550);
}
private void menuExit_Click(object sender, System.EventArgs e)
{
this.Close();
}
private void btnBrowse_Click(object sender, System.EventArgs e)
{
DialogResult dr = this.openFileDialog1.ShowDialog();
if (dr != DialogResult.OK)
return;
this.textFileName.Text = openFileDialog1.FileName;
}
private void inputPanel1_EnabledChanged(object sender,
System.EventArgs e)
{
// Adjust the listbox to avoid being covered by the SIP.
if (m_fPocketPC)
{
int height = this.inputPanel1.VisibleDesktop.Height -
this.listOut.Top;
if (!this.inputPanel1.Enabled)
height -= 26;
this.listOut.Size = new Size (this.listOut.Width,
height);
}
}
private void btnGo_Click(object sender, System.EventArgs e)
{
string strFileName = this.textFileName.Text;
if (strFileName.Length > 0)
SendFileToIR (strFileName);
}
private void StringOut (string str)
{
this.listOut.Items.Add (str);
return;
}
/// <summary>
/// Sends a file to the other device
/// </summary>
/// <param name="strFileName"></param>
private void SendFileToIR (string strFileName)
{
Stream s;
FileStream fs;
int rc;
IrDAClient irClient;
try
{
fs = new FileStream (strFileName, FileMode.Open,
FileAccess.Read);
}
catch (IOException ex)
{
StringOut (string.Format("Error opening file {0}",
ex.Message));
return;
}
StringOut ("File opened");
irClient = new IrDAClient ();
//
// Connect to service
//
StringOut ("Waiting at connect");
try
{
irClient.Connect("MySquirt");
}
catch (SocketException ex)
{
StringOut (string.Format ("Sock connect exception {0}",
ex.ErrorCode));
fs.Close();
return;
}
StringOut ("Connected");
//
// Start transfer
//
try
{
s = irClient.GetStream();
}
catch (SocketException ex)
{
StringOut (string.Format ("Sock GetStream exception {0}",
ex.ErrorCode));
fs.Close();
irClient.Close();
return;
}
// Parse path to get only the file name and extension
char[] parse = "\\".ToCharArray();
string[] fnParts = strFileName.Split (parse);
string strNameOnly = fnParts[fnParts.Length-1];
int nLen = strNameOnly.Length;
// Allocate transfer buffer
byte[] buff = new byte[4096];
// Send name length
StringOut ("Sending file name");
if (!SendDWord (s, nLen+1))
{
StringOut (string.Format ("Error sending name length"));
return;
}
// Send name
UTF8Encoding UTF8enc = new UTF8Encoding();
UTF8enc.GetBytes (strNameOnly, 0, nLen, buff, 0);
buff[nLen] = 0;
try
{
s.Write (buff, 0, nLen+1);
}
catch (SocketException ex)
{
StringOut (string.Format ("Sock Write exception {0}",
ex.ErrorCode));
}
StringOut ("Sending file");
// Send file length
nLen = (int)fs.Length;
if (!SendDWord (s, nLen))
{
StringOut ("Error sending file list");
}
// Read back file open return code
StringOut ("Reading file create ack");
RecvDWord (s, out rc);
if (rc != 0)
{
StringOut (string.Format ("Bad Ack code {0}", rc));
fs.Close();
irClient.Close();
s.Close();
return;
}
StringOut ("ack received");
// Send file data
while (nLen > 0)
{
int j = -1;
try
{
j = (nLen > buff.Length) ? buff.Length : nLen;
StringOut (string.Format("Sending {0} bytes", j));
fs.Read (buff, 0, j);
s.Write (buff, 0, j);
nLen -= j;
if (!RecvDWord (s, out j))
break;
if (j != 0)
{
StringOut ("Error ack");
break;
}
}
catch (SocketException socex)
{
StringOut (string.Format ("5 Sock Err {0} ({1},{2}",
socex.ErrorCode, nLen, j));
break;
}
catch (IOException ioex)
{
StringOut (string.Format ("File Error {0}",
ioex.Message));
break;
}
// Allow other events to happen during loop
Application.DoEvents();
}
StringOut (string.Format("File sent"));
s.Close(); // Close the stream
irClient.Close(); // Close the socket
fs.Close(); // Close the file
return;
}
/// <summary>
/// Sends a DWORD to the other device
/// </summary>
/// <param name="s"></param>
/// <param name="i"></param>
/// <returns></returns>
bool SendDWord (Stream s, int i)
{
byte[] b = BitConverter.GetBytes (i);
try
{
s.Write (b, 0, 4);
}
catch (SocketException ex)
{
StringOut (string.Format ("Err {0} writing dword",
ex.ErrorCode));
return false;
}
return true;
}
/// <summary>
/// Receiveds a DWORD from the other device
/// </summary>
/// <param name="s"></param>
/// <param name="i"></param>
/// <returns></returns>
bool RecvDWord (Stream s, out int i)
{
byte[] b = new byte[4];
try
{
s.Read (b, 0, 4);
}
catch (SocketException ex)
{
StringOut (string.Format ("Err {0} reading dword",
ex.ErrorCode));
i = 0;
return false;
}
i = BitConverter.ToInt32 (b, 0);
return true;
}
/// <summary>
/// Server thread
/// </summary>
public void SrvRoutine()
{
IrDAListener irListen;
FileStream fs;
string strFileName;
int nLen;
IrDAClient irClientSrv;
Stream s;
byte[] buff = new byte[4096];
try
{
irListen = new IrDAListener("MySquirt");
irListen.Start();
}
catch (SocketException ex)
{
StringOut (string.Format("Err {0} creating IrDAListener",
ex.ErrorCode));
return;
}
StringOut ("IrdaListener created");
while (isRunning)
{
if (irListen.Pending())
{
try
{
StringOut ("Calling AcceptIrDAClient");
irClientSrv = irListen.AcceptIrDAClient();
StringOut ("AcceptIrDAClient returned");
s = irClientSrv.GetStream();
}
catch (SocketException ex)
{
StringOut (string.Format ("Sock exception {0}",
ex.ErrorCode));
continue;
}
// Get name length
StringOut ("Getting file name");
if (!RecvDWord (s, out nLen))
{
StringOut ("Error getting name length");
s.Close();
continue;
}
// Read name
try
{
s.Read (buff, 0, nLen);
}
catch (SocketException ex)
{
StringOut (string.Format ("Read exception {0}",
ex.ErrorCode));
s.Close();
continue;
}
UTF8Encoding UTF8enc = new UTF8Encoding();
//Trim terminating zero
char[] ch = UTF8enc.GetChars (buff, 0, nLen-1);
strFileName = new string (ch);
StringOut ("Receiving file " + strFileName);
// Get file length
if (!RecvDWord (s, out nLen))
{
StringOut ("Error getting file length");
}
StringOut (string.Format ("File len: {0}", nLen));
try
{
fs = new FileStream (strFileName,
FileMode.Create,
FileAccess.Read|FileAccess.Write);
}
catch (IOException ioex)
{
StringOut (string.Format("Error opening file"));
StringOut (ioex.Message);
SendDWord (s, -3);
s.Close();
continue;
}
StringOut ("File opened");
// Send file open return code
StringOut ("Send file create ack");
if (!SendDWord (s, 0))
{
StringOut ("fail sending ack code");
fs.Close();
s.Close();
break;
}
int nTotal = 0;
// Send file data
while (nLen > 0)
{
int BlkSize = -1;
try
{
BlkSize = (nLen > buff.Length) ?
buff.Length : nLen;
int k = 0, BytesRead = 0;
while (BlkSize > k)
{
// Wait for data
if (!((NetworkStream)s).DataAvailable)
Thread.Sleep(100);
// Read it
BytesRead = s.Read (buff, k, BlkSize-k);
StringOut (string.Format ("Bytes: {0}",
BytesRead));
k += BytesRead;
}
fs.Write (buff, 0, BlkSize);
StringOut ("Send Ack");
if (!SendDWord (s, 0))
{
StringOut ("Error sending ack");
break;
}
nLen -= BlkSize;
nTotal += BlkSize;
}
catch (SocketException socex)
{
StringOut (string.Format ("Sock Err {0}",
socex.ErrorCode));
break;
}
catch (IOException ioex)
{
StringOut (string.Format ("File Err {0}",
ioex.Message));
StringOut (ioex.Message);
break;
}
}
StringOut (string.Format("File received {0} bytes.",
nTotal));
RecvDWord (s, out nLen);
fs.Close();
s.Close();
}
if (isRunning)
Thread.Sleep (500);
}
irListen.Stop();
return;
}
}
}
The user interface code of IrSquirt was generated with the designer in Visual Studio .NET. There are places, however, where the designer doesn't provide the flexibility necessary for this example. IrSquirtCF can run on both Pocket PC and embedded Windows CE devices. Because the menu in the application is implemented with a command bar on embedded devices and a menu bar on Pocket PCs, the controls need to be moved up over the blank spot left for the command bar when the program is running on a Pocket PC. In addition, the list box that's used to provide status messages is resized if the SIP is displayed so that the SIP doesn't cover any text when it's enabled. Figure 23-4 shows IrSquirtCF running on a Pocket PC, and Figure 23-5 shows the same program running on an embedded Windows CE device.

Figure 23-4: IrSquirtCF running on a Pocket PC

Figure 23-5: IrSquirtCF running on an embedded Windows CE device
IrSquirtCF contains a second class, MyMsgWindow, that contains a number of handy routines used by the program. The IsPocketPC method returns true when running on a Pocket PC. The routine is implemented by calling the Windows CE API, SystemParametersInfo, to get the platform string for the device. IsPocketPC compares the platform string returned with the expected strings for a Pocket PC and returns true if the strings match.
The other routines of MyMsgWindow provide a method for finding other instances of the application already running on the device. Whereas the Compact Framework runtime on the Pocket PC automatically enforces the single-instance requirement of a Pocket PC application, the IrSquirtCF program checks for other copies when it's running on non–Pocket PC systems. Because the IrDA port is a shared resource, it doesn't make sense to have two copies of the application potentially receiving the same file at the same time on the same device. Detecting another copy of the application is accomplished by using a MessageWindow class. The window text of the message window is set when the Text property of the class is set. Using a unique name for the window along with a call to the Windows CE function FindWindow provides a simple way to locate other copies of the application.Like IrSquirt, IrSquirtCF is a multithreaded application. The program creates a separate thread for monitoring the infrared port waiting for other devices to send files using the IrSquirt protocol. This routine, named SrvRoutine, demonstrates the use of the IrDAListener class, which monitors the IR port.The SendFileToIR routine encloses the code that sends a file from one device to the other. It uses an IrDAClient class to detect devices in range, connect to the IrSquirt service on the other device, and read and send the bytes of the file.
This overview of the .NET Compact Framework barely scratches the surface of the capabilities of the runtime. The overview also covers only the first version of the Compact Framework, which I expect to be greatly enhanced over the next few versions. Still, the information presented in this chapter should provide a running start for those developers wanting to leave Kansas and try their hand at this new and radically different approach to embedded programming.Implementing a version of the .NET Compact Framework on Windows CE provides the best of both worlds to the embedded programmer. The managed runtime provides a great infrastructure for quickly creating a highly functional user interface, while the speed and flexibility of calling into unmanaged code provides the baseline support for doing just about anything necessary for getting the program working. Viewed in this light, the Compact Framework is more a new feature of Windows CE than a competitor that requires programmers to choose a favorite.This book has attempted to be a guide to the many features of Windows CE, from its base threading API to the managed runtime of the .NET Compact Framework. The componentized design of Windows CE, coupled with both a Win32-standard API and a .NET standard runtime, provides a unique combination of flexibility and familiarity that is unmatched among today's operating systems. All in all, it's not a bad operating system. Have fun programming Windows CE. I do.