Copying Files from the Device to the Desktop using .NET
Recently, I’ve been working on a tool that creates a backup of a SQL Server Compact Edition database on the device to the desktop. To accomplish this, I used the Remote API (RAPI). Unfortunately, the Remote API is not yet available in managed code. In this article I would like to demonstrate how to P/Invoke methods from the Remote API for copying files from the device to the desktop using managed code.
First, we’ll need some P/Invokes to rapi.dll
[DllImport("rapi.dll")]
static extern int CeRapiInit();
[DllImport("rapi.dll")]
static extern int CeRapiUninit();
[DllImport("rapi.dll")]
static extern int CeCloseHandle(IntPtr hObject);
[DllImport("rapi.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CeCreateFile(
string lpFileName,
uint dwDesiredAccess,
int dwShareMode,
int lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
int hTemplateFile);
[DllImport("rapi.dll", CharSet = CharSet.Unicode)]
static extern int CeReadFile(
IntPtr hFile,
byte[] lpBuffer,
int nNumberOfbytesToRead,
ref int lpNumberOfbytesRead,
int lpOverlapped);
const int ERROR_SUCCESS = 0;
const int OPEN_EXISTING = 3;
const int INVALID_HANDLE_VALUE = -1;
const int FILE_ATTRIBUTE_NORMAL = 0x80;
const uint GENERIC_READ = 0x80000000;
Now let’s create a method called CopyFromDevice(string remote_file, string local_file). The remote_file parameter is the source file on the device that you wish to copy. The local_file parameter is the destination filename on the desktop.
public static void CopyFromDevice(string remote_file, string local_file)
{
bool rapi = CeRapiInit() == ERROR_SUCCESS;
if (!rapi) {
return;
}
IntPtr remote_file_ptr = CeCreateFile(
remote_file,
GENERIC_READ,
0,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (remote_file_ptr.ToInt32() == INVALID_HANDLE_VALUE) {
return;
}
FileStream local_file_stream = new FileStream(
local_file,
FileMode.Create,
FileAccess.Write);
int read = 0;
int size = 1024 * 4;
byte[] data = new byte[size];
CeReadFile(remote_file_ptr, data, size, ref read, 0);
while (read > 0) {
local_file_stream.Write(data, 0, read);
if (CeReadFile(remote_file_ptr, data, size, ref read, 0) == 0)
{
CeCloseHandle(remote_file_ptr);
local_file_stream.Close();
return;
}
}
CeCloseHandle(remote_file_ptr);
local_file_stream.Flush();
local_file_stream.Close();
if (rapi) {
CeRapiUninit();
}
if (!File.Exists(local_file)) {
throw new FileNotFoundException("The file was not copied to the desktop");
}
}
To use the code above you will have to know the full path of the file on the device. The way I did it was to read the registry on the device and check if my application was installed, if it was then I get the path of the application and pass as the path in my remote_file parameter.
Retrieving the Icon Image within the System Image List in .NETCF 2.0
Here’s a nice trick for retrieving the icon image of a file or folder from the system image list. All we actually need is to P/Invoke SHGetFileInfo
and use Icon.FromHandle()
to get the Icon.
First, we need to declare our P/Invokes.
[StructLayout(LayoutKind.Sequential)]
stru-ct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
const uint SHGFI_ICON = 0x000000100;
const uint SHGFI_LARGEICON = 0x000000000;
const uint SHGFI_SMALLICON = 0x000000001;
const uint SHGFI_SELECTICON = 0x000040000;
[DllImport("coredll.dll")]
static extern IntPtr SHGetFileInfo(
string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbSizeFileInfo,
uint uFlags);
To get an instance of System.Drawing.Icon for the small icon of a file
Icon GetSystemIconSmall(string file)
{
SHFILEINFO shinfo = new SHFILEINFO();
IntPtr i = SHGetFileInfo(file, 0, ref shinfo,
(uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SMALLICON);
return Icon.FromHandle(shinfo.hIcon);
}
For the large icon of a file
Icon GetSystemIconLarge(string file)
{
SHFILEINFO shinfo = new SHFILEINFO();
IntPtr i = SHGetFileInfo(file, 0, ref shinfo,
(uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_LARGEICON);
return Icon.FromHandle(shinfo.hIcon);
}
For the small icon of a file when it is selected
Icon GetSystemIconSmallSelected(string file)
{
SHFILEINFO shinfo = new SHFILEINFO();
IntPtr i = SHGetFileInfo(file, 0, ref shinfo,
(uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SMALLICON | SHGFI_SELECTICON);
return Icon.FromHandle(shinfo.hIcon);
}
And last for the large icon of a file when it is selected
Icon GetSystemIconLargeSelected(string file)
{
SHFILEINFO shinfo = new SHFILEINFO();
IntPtr i = SHGetFileInfo(file, 0, ref shinfo,
(uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_SELECTICON);
return Icon.FromHandle(shinfo.hIcon);
}
Ok, now how is this helpful? Well if you want to implement a File Explorer-ish control, then wouldn’t have to include Icons and other images in your application. You can just use the icons in the system image list
Logging Unhandled Exceptions
Bugs are sometimes unavoidable. They’re best caught during the development or testing phase. There might be some cases where the developer forgot to handle possible exceptions in a function. It could be possible that this exception isn’t handled anywhere at all. But even so, it is still possible to catch this exception. To do this we handle the UnhandledException event of the current AppDomain. We should do this in our static void Main()
before calling Application.Run([Main Form])
Here’s a small snippet to accomplish this task.
[C# CODE]
static void Main()
{
AppDomain.CurrentDomain.UnhandledException +=
new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Application.Run(new MainForm());
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception exception = (Exception)e.ExceptionObject;
if (exception != null) {
Error.Append(exception.Message, exception.StackTrace);
}
}
Now we need a mechanism for saving to a Error log file. Let’s create a simple class called Error() and add a function called Append(string message, string stacktrace)
[C# CODE]
public class Error
{
internal static void Append(string message, string stacktrace)
{
string file = string.Format("{0}\\Errors.txt",
Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase));
if (File.Exists(file)) {
FileInfo fi = new FileInfo(file);
if (fi.Length > 100 * 1024) {
fi.Delete();
}
}
StreamWriter sw = new StreamWriter(file, true, Encoding.UTF8);
sw.WriteLine(
string.Format(
"-=-=-=-=-=--=-=-\n{0}\nMESSAGE:\n{1}\nSTACK TRACE:\n{3}\n",
DateTime.Now,
message,
stacktrace));
sw.Close();
}
}
}
The Error()
class should be put in a namespace that is accessible throughout the application. This will be very helpful tool for finding those nasty almost impossible to reproduce bugs.