Thursday, September 25, 2008

Copying files from Desktop to a CE device

http://blogs.msdn.com/harsh/archive/2008/05/08/copying-files-from-desktop-to-a-ce-device.aspx

So I have this problem, where I need to create a tool that creates a folder with a bunch of files on the PC and then copies all these files over to a CE device. The method of choice here was via ActiveSync over USB. One of the design goals was to be able to copy these files to a number of devices simultaneously. However, research revealed that this is not really possible since, activesync only supports one active connection at a time. So much for the great idea.

So now we are faced with this problem of copying files over to the device over ActiveSync. Since the app had a lot of GUI, I created that in C#. The .NET framework exposes excellent API for file manipulation based on paths. The problem with ActiveSync connections is when the device is cradled, there is no drive letter associated with the connection. So all device paths are then \My Documents\ or \Program Files\ etc.

This creates a problem as far as the File manipulation API in System.IO. Since these methods accept relative as well as fully qualified paths, any path like the above, would be treated as a relative path and not a fully qualified path, thereby causing a lot of grief.

The only way to get past this so far has been to use RAPI.

RAPI stands for Remote Application Programming Interface, and was created ages ago with the first wave of activesync and windows mobile products.

RAPI has some documentation on MSDN at: http://msdn.microsoft.com/en-us/library/aa458022.aspx

However since RAPI is a native API, almost all the documentation is geared towards native implementation.

After searching the intertubes for a suitable solution, I couldn’t find one that was recent, and that was guaranteed to work.

Below is the solution:




using System;
using System.Threading;
using System.Runtime.InteropServices;
using COMTYPES = System.Runtime.InteropServices.ComTypes;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Logger = Microsoft.MobileDevices.Tools.Utilities.Logger;


namespace Microsoft.MobileDevices.Tools.RAPILibrary
{
public class RAPILibrary
{
# region RAPI EXTERN
// -------------------------------------------------------
// RAPI.DLL Definitions
// -------------------------------------------------------

public const int MAX_PATH = 260;
public const uint MAXDWORD = 0xFFFFFFFF;
public const uint S_OK = 0;

public struct RAPIINIT
{
public int cbSize;
public IntPtr heRapiInit;
public int hrRapiInit;
};

// From WINBASE.H -- for CeCreateFile
public enum ACCESS : uint
{
READ = 0x80000000,
WRITE = 0x40000000,
}
// From WINBASE.H -- for CeCreateFile
public enum SHARE
{
READ = 0x00000001,
WRITE = 0x00000002,
}
// From WINBASE.H -- for CeCreateFile
public enum ACTION
{
CREATE_NEW = 1,
CREATE_ALWAYS = 2,
OPEN_EXISTING = 3,
OPEN_ALWAYS = 4,
TRUNCATE_EXISTING = 5
}

// From WINBASE.H -- for CeCreateFile
public enum FILEFLAGATT : uint
{
ATTRIBUTE_READONLY = 0x00000001,
ATTRIBUTE_HIDDEN = 0x00000002,
ATTRIBUTE_SYSTEM = 0x00000004,
ATTRIBUTE_DIRECTORY = 0x00000010,
ATTRIBUTE_ARCHIVE = 0x00000020,
ATTRIBUTE_INROM = 0x00000040,
ATTRIBUTE_ENCRYPTED = 0x00000040,
ATTRIBUTE_NORMAL = 0x00000080,
ATTRIBUTE_TEMPORARY = 0x00000100,
ATTRIBUTE_SPARSE_FILE = 0x00000200,
ATTRIBUTE_REPARSE_POINT = 0x00000400,
ATTRIBUTE_COMPRESSED = 0x00000800,
ATTRIBUTE_OFFLINE = 0x00001000,
ATTRIBUTE_ROMSTATICREF = 0x00001000,
ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000,
ATTRIBUTE_ROMMODULE = 0x00002000,
FLAG_WRITE_THROUGH = 0x80000000,
FLAG_OVERLAPPED = 0x40000000,
FLAG_NO_BUFFERING = 0x20000000,
FLAG_RANDOM_ACCESS = 0x10000000,
FLAG_SEQUENTIAL_SCAN = 0x08000000,
FLAG_DELETE_ON_CLOSE = 0x04000000,
FLAG_BACKUP_SEMANTICS = 0x02000000,
FLAG_POSIX_SEMANTICS = 0x01000000,
}


///
/// Closes handle passed in
///

/// handle to be closed
///
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern int CeCloseHandle(IntPtr hObject);

///
/// Creates file on device
///

/// path of file
/// Read/Write
/// Share mode
///
/// File creation options
/// Flags and attributes
///
/// pointer to the created file
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr CeCreateFile(string lpFileName, ACCESS dwDesiredAccess, SHARE dwShareMode, int Res1, ACTION dwCreationDisposition, FILEFLAGATT dwFlagsAndAttributes, int Res2);

///
/// Reads the file
///

/// handle to the file
/// buffer to copy data to
/// file size to read
/// file size read
///
///
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern int CeReadFile(IntPtr hFile, IntPtr lpBuffer, int nNumberOfBytesToRead, ref int lpNumberOfBytesRead, int Reserved);

///
/// Writes file to device
///

/// handle of file to write to
/// data to write
/// length of buffer
/// length of file on device
///
///
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern int CeWriteFile(IntPtr hFile, IntPtr lpBuffer, int nNumberOfBytesToWrite, ref int lpNumberOfBytesWritten, int Reserved);

///
/// Copies file from desktop to device
///

/// Full path of file on the desktop including filename
/// Path of file name on the device. '\' denotes root
public static void CopyFiletoDevice(string deskFile, string devFilePath)
{
try
{
int numTries=0;
// Verify file exists on desktop
FileInfo fInfo = new FileInfo(deskFile);
if (fInfo.Exists)
{
IntPtr devFileHandle;
// create a file on the device
do // force valid handle in the loop
{
devFileHandle = CeCreateFile(devFilePath, ACCESS.READ | ACCESS.WRITE, SHARE.READ | SHARE.WRITE, 0, ACTION.CREATE_ALWAYS, FILEFLAGATT.ATTRIBUTE_NORMAL, 0);
if (devFileHandle.ToInt32() < 0)
{
CeCloseHandle(devFileHandle);
}
} while (devFileHandle.ToInt32() < 0);


// read file from desktop into a byte buffer
Byte[] buffer;
ReadFileAsBinary(deskFile, out buffer);

int numBytesWritten = 0;
unsafe
{
fixed(byte* bufferPtr = buffer)
{
int retVal = CeWriteFile(devFileHandle, (IntPtr)(bufferPtr), (int)fInfo.Length, ref numBytesWritten, 0);
if (retVal == 0)
{
throw new IOException("RAPILibrary:CopyFileToDevice() Error Writing file on device. " + CeGetLastError().ToString());
}
}
}

// close handles
CeCloseHandle(devFileHandle);
}
else
{
Logger.WriteLog("RAPILibrary:CopyFileToDevice() File does not exist on desktop: " + deskFile);
}

}
catch (IOException ioEx)
{
Logger.WriteLog(ioEx.Message);
}
catch (Exception ex)
{
Logger.WriteLog(ex.ToString());
}
}

///
/// Reads file and returns byte[]
///

/// Full path of file
/// byte array for file data
private static void ReadFileAsBinary(string path, out Byte[] buffer)
{
Logger.WriteLog("RAPILibrary:ReadFileAsBinary() Reading the file " + path);
FileInfo fInfo = new FileInfo(path);
long fileLength = fInfo.Length;
buffer = new Byte[fileLength];
FileStream fs = fInfo.OpenRead();
fs.Read(buffer, 0, (int)fileLength);
fs.Close();
Logger.WriteLog("RAPILibrary:ReadFileAsBinary() Exit");
}
}
}

Wednesday, September 10, 2008

How to create Customized Control in C#?

http://www.codeproject.com/KB/miscctrl/cutebutton.aspx

Introduction

This is a short and simple demonstration of .NET framework's capability of creating custom controls.

Here I'm going to make a custom control and then, test my control in a Windows application. I have implemented some custom properties for my control, so you can learn how it is done in C#.

Building the Control

  1. Open the Visual Studio and start a new project. Your project must be based on the Windows Control Library template. Call your project ctlCuteButton and click OK.

  2. Once you have your project open, delete the UserControl from the project. Just remove it because the 'User Control' is not exactly what we need here.

  3. Now go to the 'Project' menu: Project->Add User Control... and select the Custom Control template there. 'Custom Control' is what we need in this case. You may call it cuteButton. Now click OK. A new Custom control has been added to your project.

  4. The first thing we must do here is to change the base class of the cuteButton:

    Override the following line:

    public class cuteButton : System.Windows.Forms.Control

    by this one:

    public class cuteButton : System.Windows.Forms.Button

    Now your control is based on the System.Windows.Forms.Button class.

  5. Now let's create some custom properties for our control. This is done by inserting the following code inside the cuteButton class:

    Collapse
    private Color m_color1 = Color.LightGreen;  //first color
    

    private Color m_color2 = Color.DarkBlue; // second color

    private int m_color1Transparent = 64; // transparency degree

    // (applies to the 1st color)

    private int m_color2Transparent = 64; // transparency degree

    // (applies to the 2nd color)


    public Color cuteColor1
    {
    get { return m_color1; }
    set { m_color1 = value; Invalidate(); }
    }

    public Color cuteColor2
    {
    get { return m_color2; }
    set { m_color2 = value; Invalidate(); }
    }

    public int cuteTransparent1
    {
    get { return m_color1Transparent; }
    set { m_color1Transparent = value; Invalidate(); }
    }

    public int cuteTransparent2
    {
    get { return m_color2Transparent; }
    set { m_color2Transparent = value; Invalidate(); }
    }

    The Invalidate() methoid is used to refresh the design view and all controls inside (the tip from Tom Welch).

  6. And the last thing to do before compiling our control is to override the Paint event. So let's do it:

    // Calling the base class OnPaint
    

    base.OnPaint(pe);
    // Create two semi-transparent colors

    Color c1 = Color.FromArgb(m_color1Transparent , m_color1);
    Color c2 = Color.FromArgb(m_color2Transparent , m_color2);
    Brush b = new System.Drawing.Drawing2D.LinearGradientBrush(ClientRectangle,
    c1, c2, 10);
    pe.Graphics.FillRectangle (b, ClientRectangle);
    b.Dispose();
  7. Now you may compile the control by pressing ++.

Here is the complete contents of cuteButton.cs file (just in case…)

Testing the Control

  1. Open a new instance of the VS .NET. Create a new project choosing the Windows Application template.

  2. From a new Windows Forms project, we can add the compiled custom control to the toolbox. I do this by right-clicking the toolbox, selecting Customize Toolbox, and from the .NET Framework Components tab, clicking Browse and locating the Control Library DLL # (in our case, ctlCuteButton\bin\debug\cuteButton.dll). The component cuteButton will then appear in the Toolbox.

    You can play a bit changing it’s properties (cuteColor1, cuteColor2, cuteTransparent1, cuteTransparent2).

That’s all so far about building and using custom controls.

Good Luck.

(Or, do not need to add control from toolbox, add the control project to your project, then the control will appear automatically.)


Thursday, September 4, 2008

Window CE, how to get File size, version information in C#?

For file size, length, last write etc, you can use FileInfo as following:

FileInfo aa = new FileInfo("\\xxx");
aa.LastWriteTime;
aa.Length;
aa.CreationTime;

for file version, you have to use some C++ help here:

public class FileVersionInfo
{
#region Variables

private string m_sFileName;
private byte[] m_bytVersionInfo;

#endregion

#region Constants

private const int GMEM_FIXED = 0x0000;
private const int LMEM_ZEROINIT = 0x0040;
private const int LPTR = (GMEM_FIXED | LMEM_ZEROINIT);

#endregion

#region Constructors

///
/// Constructor.
///

/// File name and path.
private FileVersionInfo(string sFileName)
{
if (File.Exists(sFileName))
{
int iHandle = 0;
int iLength = 0;
int iFixedLength= 0;
IntPtr ipFixedBuffer = IntPtr.Zero;

// Get the file information.
m_sFileName = Path.GetFileName(sFileName);
iLength = GetFileVersionInfoSize(sFileName, ref iHandle);

if(iLength > 0)
{
// Allocate memory.
IntPtr ipBuffer = AllocHGlobal(iLength);

// Get the version information.
if(GetFileVersionInfo(sFileName, iHandle, iLength, ipBuffer))
{
// Get language independant version info.
if(VerQueryValue(ipBuffer, "\\", ref ipFixedBuffer, ref iFixedLength))
{
// Copy information to array.
m_bytVersionInfo = new byte[iFixedLength];
Marshal.Copy(ipFixedBuffer, m_bytVersionInfo, 0, iFixedLength);
}
}

// Free memory.
FreeHGlobal(ipBuffer);
}
}
else
{
m_bytVersionInfo = new byte[200];
}
}

#endregion

#region Properties

///
/// Get the file build part.
///

public int FileBuildPart
{
get
{
return Convert.ToInt32(BitConverter.ToInt16(m_bytVersionInfo, 14));
}
}

///
/// Get the file major part.
///

public int FileMajorPart
{
get
{
return Convert.ToInt32(BitConverter.ToInt16(m_bytVersionInfo, 10));
}
}

///
/// Get the file minor part.
///

public int FileMinorPart
{
get
{
return Convert.ToInt32(BitConverter.ToInt16(m_bytVersionInfo, 8));
}
}

///
/// Get the name of the file.
///

public string FileName
{
get
{
return m_sFileName;
}
}

///
/// Get the file private part.
///

public int FilePrivatePart
{
get
{
return Convert.ToInt32(BitConverter.ToInt16(m_bytVersionInfo, 12));
}
}

///
/// Get the product build part.
///

public int ProductBuildPart
{
get
{
return Convert.ToInt32(BitConverter.ToInt16(m_bytVersionInfo, 22));
}
}

///
/// Get the product major part.
///

public int ProductMajorPart
{
get
{
return Convert.ToInt32(BitConverter.ToInt16(m_bytVersionInfo, 18));
}
}

///
/// Get the product minor part.
///

public int ProductMinorPart
{
get
{
return Convert.ToInt32(BitConverter.ToInt16(m_bytVersionInfo, 16));
}
}

///
/// Get the product private part.
///

public int ProductPrivatePart
{
get
{
return Convert.ToInt32(BitConverter.ToInt16(m_bytVersionInfo, 20));
}
}

#endregion

#region Functions

///
/// Allocate unmanged memory.
///

/// Length to allocate.
/// IntPtr object.
private static IntPtr AllocHGlobal(int iLength)
{
return LocalAlloc(LPTR, (uint)iLength);
}

///
/// Free allocated memory.
///

/// IntPtr object to free.
private static void FreeHGlobal(IntPtr hGlobal)
{
LocalFree(hGlobal);
}

///
/// Get the file version information.
///

/// File name and path.
/// FileVersionInfo object.
public static FileVersionInfo GetVersionInfo(string sFileName)
{
return new FileVersionInfo(sFileName);
}

#endregion

#region Win32API

[DllImport("coredll", EntryPoint="GetFileVersionInfo", SetLastError=true)]
private static extern bool GetFileVersionInfo(
string filename,
int handle,
int len,
IntPtr buffer);

[DllImport("coredll", EntryPoint="GetFileVersionInfoSize", SetLastError=true)]
private static extern int GetFileVersionInfoSize(
string filename,
ref int handle);

[DllImport("coredll.dll", EntryPoint="LocalAlloc", SetLastError=true)]
private static extern IntPtr LocalAlloc(
uint uFlags,
uint Bytes);

[DllImport("coredll.dll", EntryPoint="LocalFree", SetLastError=true)]
private static extern IntPtr LocalFree(
IntPtr hMem);

[DllImport("coredll", EntryPoint="VerQueryValue", SetLastError=true)]
private static extern bool VerQueryValue(
IntPtr buffer,
string subblock,
ref IntPtr blockbuffer,
ref int len);

#endregion
}
}

Then:

FileVersionInfo fiWindows = FileVersionInfo.GetVersionInfo(@"\Windows\sdcgina.exe");
//MessageBox.Show("1 Window:" + fiWindows.FileMajorPart + "_" + fiWindows.FileMinorPart + "_" + fiWindows.FileBuildPart + "_"
// + fiWindows.ProductMajorPart + "_" + fiWindows.ProductMinorPart + "_" + fiWindows.ProductPrivatePart + "_" + fiWindows.ProductBuildPart
// + "SystemCF:" + fiSystemCF.FileMajorPart + "_" + fiSystemCF.FileMinorPart + "_" + fiSystemCF.FileBuildPart + "_"
// + fiSystemCF.ProductMajorPart + "_" + fiSystemCF.ProductMinorPart + "_" + fiSystemCF.ProductPrivatePart + "_" + fiSystemCF.ProductBuildPart);