Saturday, May 31, 2008

ActiveSync_Remote_Display

You might have noticed in my previous post that I used the ActiveSync remote display tool. This is quite a powerful in many senarios including pre-sales demoing, debugging, presenting etc. This particular tool is part of the Windows Mobile Power Toys toolkit.

Although this toolkit just worked pre WM5, it doesn't work on WM5 and later out of the box.

There is a relatively easy workaround in order to get the ActiveSync remote display tool to work on WM5 and WM6.0, WM6.1. You need to simpy copy the files: cerdisp2.exe and KillProc.exe from (similar) C:\Program Files (x86)\Windows Mobile Developer Power Toys\ActiveSync_Remote_Display\devices\wce400\armv4 to \Windows of your device.

Then simply run client application: C:\Program Files (x86)\Windows Mobile Developer Power Toys\ActiveSync_Remote_Display\ASRDisp.exe

I have used this workaround on all devices mentioned, WM5, Wm6.0 and WM 6.1 without issues.

Memory management on Windows Mobile <= 6.1

Recently in the communities a question was asked: "Why should I bother finalizing my objects, shouldn't I let the GC do it for me?". It's true we have the wonderful Garbage Collector (GC) in managed code and luckily it is supported on the Compact Framework. Here's a statement: *never* rely on the GC, in fact don't call it, ever. OK, there may be some scenarios you might want to call it in extreme cases, but for the most part, please don't, think of your users ;) instead write better code!
This article is was written to show an example of how Windows Mobile 6.1 and earlier handles memory management and the various events you can hook into to develop more robust applications.

I'm running the following code on a HTC P3300 with 64meg RAM not much by todays standards but the device is 18 months old now so in mobility terms ready for the scrap heap. I happen to have just 22meg free after running this application:




You'll see from the screenshot above I have written out the physical free memory available to applications on my device. Although there is no managed function to achieve this even in CF 3.5, it can be done by P/Invoking function GlobalMemoryStatus. The code to do this is fairly simple ie:
public struct MemoryStatus
{
internal uint dwLength;
public int MemoryLoad;
public int TotalPhysical;
public int AvailablePhysical;
public int TotalPageFile;
public int AvailablePageFile;
public int TotalVirtual;
public int AvailableVirtual;
}

//Declaration.
[DllImport("coredll", SetLastError = false)]
internal static extern void GlobalMemoryStatus(out MemoryStatus status);

This app has been developed using CF 3.5 and VS 2008 Team Suite. I have created a simple form with one button labled "Eat memory". The code looks like the following:
 public partial class Form1 : Form
{
public struct MemoryStatus
{
internal uint dwLength;
public int MemoryLoad;
public int TotalPhysical;
public int AvailablePhysical;
public int TotalPageFile;
public int AvailablePageFile;
public int TotalVirtual;
public int AvailableVirtual;
}

[DllImport("coredll", SetLastError = false)]
internal static extern void GlobalMemoryStatus(out MemoryStatus status);

private List bytelist = new List();

public Form1()
{
InitializeComponent();
}

private void Form1_Closed(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine(string.Format("{0} : Form1_Closed called",
DateTime.Now.ToLongTimeString()));
}

private void CurrentDomain_UnhandledException(object sender,
UnhandledExceptionEventArgs e)
{
System.Diagnostics.Debug.WriteLine(string.Format("{0} :
CurrentDomain_UnhandledException called",
DateTime.Now.ToLongTimeString()));
}

private void MobileDevice_Hibernate(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine(string.Format("{0} : MobileDevice_Hibernate
called",
DateTime.Now.ToLongTimeString()));
Clear();
}

private void Clear()
{
if (bytelist != null)
{
System.Diagnostics.Debug.WriteLine(string.Format("{0} : Clear called
(cleanup unused memory objects)",
DateTime.Now.ToLongTimeString()));
bytelist.Clear();
}
}

private void eatMemory_Click(object sender, EventArgs e)
{
MemoryStatus status = GetMemoryStatus();

for (int i = 0; i < 21; i++)
{
bytelist.Add(new MyClass());
}

status = GetMemoryStatus();
memoryAfter.Text = "Memory after: " +
status.AvailablePhysical.ToString();

}

private MemoryStatus GetMemoryStatus()
{
MemoryStatus status = new MemoryStatus();
GlobalMemoryStatus(out status);
return status;
}

private void resetTest_Click(object sender, EventArgs e)
{
Clear();
MemoryStatus status = GetMemoryStatus();
memoryBefore.Text = "Memory before: " +
status.AvailablePhysical.ToString();
memoryAfter.Text = "Memory after: -";
}

private void Form1_Load(object sender, EventArgs e)
{
Microsoft.WindowsCE.Forms.MobileDevice.Hibernate += new
EventHandler(MobileDevice_Hibernate);
AppDomain.CurrentDomain.UnhandledException += new
UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Closed += new EventHandler(Form1_Closed);
Closing += new CancelEventHandler(Form1_Closing);
resetTest_Click(this, EventArgs.Empty);
}

private void Form1_Closing(object sender, CancelEventArgs e)
{
System.Diagnostics.Debug.WriteLine(string.Format("{0} : Form1_Closing called",
DateTime.Now.ToLongTimeString()));
}
}

public class MyClass
{
private byte[] mybyte = new byte[1000000];
}
The code is very simple. We have created a class named MyClass which allocates a 1mb byte array. We simply have hardcoded a loop to iterate through 21 times whwn the "Eat memory" button is pressed so we get less than 2mb free RAM. You'll probebly thinking, why didn't I just calculate the amount of free RAM before the test and allocate a buffer slightly smaller than the free physical memory limit. This didn't seem to work because of the way the OS allocates and frees memory. I found simply hardcoding it worked better for this test. I needed to get between 1 and 2 free mb which was actually harder than it seems! We create a generic array list to hold references to each of the 1mb MyClass objects. We store this array list object at class level so the GC won't try to clean it up when times get tough.

Just to be clear, usally you wouldn't handle all these events under the UI layer, it would be abstracted out usally to the DeviceManagement level (business). We have added hooks to the following events:

Microsoft.WindowsCE.Forms.MobileDevice.Hibernate
System.Windows.Form.Closing
System.Windows.Form.Closed
System.AppDomain.CurrentDomain.UnhandledException

The OS will send the WM_HIBERNATE message to all applications when the amount of free RAM falls below its minimum limit and will stop when it has enough reserve resources to do whatever it needs to do. In .NET, the event that traps the WM_HIBERNATE message is Microsoft.WindowsCE.Forms.MobileDevice.Hibernate. The objective of handling this message is to give your app a chance to redeem itself and clean up any unused memory it may be hogging. Note: You have to explicitly add the Microsoft.WindowsCE.Forms.dll assembly to your project, VS doesn't do this for free.

WM_CLOSE will be sent if the WM_HIBERNATE made little difference sometime after WM_HIBERNATE was sent (I'll show an example of this architecture later). System.Windows.Form.Closing event will be called when the OS sends the WM_CLOSE message if the app is hogging memory which the OS needs then finally, System.Windows.Form.Closed will be called which terminates the app.

System.AppDomain.CurrentDomain.UnhandledException this traps unhandled exceptions in the current app domain which allow us to clean up our code and write a file somewhere, so next time our app loads it sends debugging information to the back office which can be used to fix the error in future builds. I'll show an example of this later.

Remember we said my HTC 3300 device had 22 meg of free RAM before pressing the "Eat memory" button in the above code, well after pressing the "Eat memory" button we now have 1.6 meg of free RAM. Not alot at all, navigating the device is very very slow as one would imagine.


Free RAM after running "Eat memory".

So what should happen now? well, the OS should firstly send the WM_HIBERBATE message to each application running in hand until it has enough memory as the device is in an unstable state and needs more memory to handle things like phone calls, radio etc. I think this freshhold is somewhere above 2mb.

Guess what, WM_HIBERNATE is called which should give our application a chance to cleanup that horrible 21 meg array that we're not using. But in the code documented earlier, did you notice in the MobileDevice_Hibernate event, we commented out the call to the Clear() method.
private void MobileDevice_Hibernate(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine(string.Format("{0} : MobileDevice_Hibernate
called",
DateTime.Now.ToLongTimeString()));
//Clear();
}
This of course was deliberate to demonstrate what happens in this scenario which emulates what could happen if you do not code for these scenarios. Well as I mentioned earlier the WM_CLOSE will be sent soon after if there are still not sufficient free resources after initiating a WM_HIBERNATE message. We are using the Dianostics.Debug.WriteLine() method to let us know what is happening.

So as we haven't cleaned anything up, we should expect a WM_CLOSE pretty soon.....guess what, we do. See the following output window:


Output when memory limit hits the threshold and WM_HIBERNATE and WM_CLOSE messages are received.

You can see how long it takes for the WM_CLOSE event to occur after the WM_HIBERNATE because we have timestamped the events, here it is 14 seconds. One thing to note after the WM_HIBERNATE message is received a full implicit GC.Collect() is executed. But because we didn't in the above code example clear the array list which is holding onto 21 meg of valuable resource, the GC did little good, so a WM_CLOSE message was sent. Often if the WM_CLOSE has little effect then the OS will kill the app!

So what happens if we uncomment the Clear() method in the MobileDevice_Hibernate event? well lets see.....


Output when memory limit hits the threshold and WM_HIBERNATE message is received

Now this is a different story from before. We can clearly see the hibernate message has been received from the output above and this was received because we reached less then 2meg of free physical memory on the device after pressing the "Eat memory" button. We then called the Clear() method which clears the array of 21 meg MyClass objects. Remember the GC would then be called as soon as the WM_HIBERNATE message returns. So lets examine the available memory in control panel:


Memory applet after cleaning up unused objects.

Wow, this is amazing, now look, we are back to where we were in terms of free physical memory before clicking the dreadful badly written "Eat memory" button!

This demonstrates the fact that you should never rely on the GC even though we are coding in a memory managed evironment today. Native development principles in terms of memory management still apply today as they ever did. This also backs up the fact that sometimes the GC won't help you ;)

There are scenarios where you could blow the free amount of RAM in almost one hit. In these senarios, the OS simply doesn't have a chance to try and get the app to behave itself as clearlyit's not and there is no time to try and get it to behave itself . Usually in these cases an OutOfMemory exception is generated. Here we can then trap these exceptions by registering the AppDomain.CurrentDomain.UnhandledException message. Of course there is no way to rescue your app in these situations, so the only thing you can really do is close everything down safely and write a debug file and transmit it to your back office for debugging information purposes. It is also nice to applogize to the end user once the app loads again and maybe display some info as to what happened. This can be done by simply writing a log file somewhere than the app checks on load.

Saturday, May 17, 2008

The first ever .NET CF Code Profiler

You can get this profiler from here: http://www.eqatec.com/tools/profiler it is free.

I've not installed this yet, so I am not sure how well it works. I will post back my experiences.

Thursday, May 15, 2008

Sending an ICMP packet to a remote server on the CF (Ping)

This question has been coming up a lot recently in the community "How do I ping a remote server from a device?".

I thought I'd write this quick article with some code in how to do this.

Even if you are using the latest version of the Compact Framework which is 3.5, there is no support for this without getting your hands dirty with native code.

A ping is essentially an Internet Control Message Protocol (ICMP) message and can be achieved from managed code by P/Invoking IcmpSendEcho2. This function can be found in Iphlpapi.dll and this call is supported on Windows CE 4.1 and later. For previous versions you'll need to link in library Icmplib.lib.

NOTE: If the machine is sitting behind a router (usual case), some routers give the option for blocking ICMP protocol, if the call doesn't work, you might want to check your router first.

Now this is the good part, instead of having to code the call to the IcmpSendEcho2 function yourself, OpenNETCF has kindly already provided a managed wrapper for us. The OpenNETCF Ping class can be found in the OpenNETCF.Net.NetworkingInformation namespace. Something like the following should work:

public static PingReply Ping(string ipAddress)
{
Cursor.Current = Cursors.WaitCursor;
Ping ping = new Ping();
PingReply reply = null;
try
{
reply = ping.Send(ipAddress, 10000);
switch (reply.Status)
{
case IPStatus.Success:
Cursor.Current = Cursors.Default;
if (ipAddress.IndexOf(".") > -1)
{
MessageBox.Show(string.Format(Properties.Resources.PingServerOK,
ipAddress,
reply.RoundTripTime),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Asterisk,
MessageBoxDefaultButton.Button1);
}
else
{
MessageBox.Show(string.Format(Properties.Resources.PingServerOK2,
ipAddress,
reply.RoundTripTime,
reply.Address.ToString()),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Asterisk,
MessageBoxDefaultButton.Button1);
}
break;
case IPStatus.BadDestination:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingBadDestination,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.BadOption:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingBadOption,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.BadRoute:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingBadRoute,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.DestinationHostUnreachable:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingDestinationHostUnreachable,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.DestinationNetworkUnreachable:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingDestinationNetworkUnreachable,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.DestinationPortUnreachable:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingDestinationPortUnreachable,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.DestinationProhibited:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingDestinationProhibited,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;

case IPStatus.DestinationScopeMismatch:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingDestinationScopeMismatch,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.DestinationUnreachable:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingPingDestinationUnreachable,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.HardwareError:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingHardwareError,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.IcmpError:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingIcmpError,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.NoResources:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingNoResources,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.PacketTooBig:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingPacketTooBig,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.ParameterProblem:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingParameterProblem,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.SourceQuench:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingDestinationScopeMismatch,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.TimedOut:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingTimeout,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.TimeExceeded:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingTimeExceeded,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.TtlExpired:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingTtlExpired,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.TtlReassemblyTimeExceeded:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingTtlReassemblyTimeExceeded,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.Unknown:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingUnknown,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
case IPStatus.UnrecognizedNextHeader:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingUnrecognizedNextHeader,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
default:
Cursor.Current = Cursors.Default;
MessageBox.Show(string.Format(Properties.Resources.PingUnknown,
ipAddress),
Properties.Resources.Ping,
MessageBoxButtons.OK,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
break;
}
}

catch (PingException ex)
{
Cursor.Current = Cursors.Default;
DialogResult result = MessageBox.Show(ex.Message,
Properties.Resources.Ping,
MessageBoxButtons.RetryCancel,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
if (result == DialogResult.Retry)
Ping(ipAddress);
}
catch (Exception ex)
{
DialogResult result = MessageBox.Show(ex.Message,
Properties.Resources.Ping,
MessageBoxButtons.RetryCancel,
MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1);
if (result == DialogResult.Retry)
Ping(ipAddress);
}
finally
{
Cursor.Current = Cursors.Default;
}
return reply;
}
That's all there is too it!

Dropping Defaults (Column Constraint) in SQL Compact 3.5

This interesting post talks about the DEFAULT SQL statement is no longer a database constraint in SQL Compact 3.5. You merely use just DEFAULT.

To remove a default simply use SQL syntax: ALTER TABLE table ALTER COLUMN col DROP DEFAULT. This has always been the recomended way to remove defaults.

See here for more info: http://blogs.msdn.com/sqlservercompact/archive/2008/04/03/dropping-defaults.aspx

Tuesday, May 13, 2008

Visual Studio 2008 and .NET Framework 3.5 Service Pack 1 Beta

UPDATE: ADO.NET team blog talk about what changes to expect from this BSP. One major thing is support for SQL Server 2008 via Server Explorer. So now you can use SQL Server 2008 with the Entity Framework (I've not tried this so do not know how well it works). Read the post here. A little information on this subject can be found over at LINQinAction here.

Visual Studio 2008 and .NET 3.5 SP1 beta is now available and been officially released.

VS 2008 SP1: http://download.microsoft.com/download/7/3/8/7382EA08-4DD6-4134-9B92-8585A5B07973/VS90sp1-KB945140-ENU.exe

.NET 3.5 SP1: http://download.microsoft.com/download/8/f/c/8fc1fe13-55de-4bf5-b43e-375daf01452e/dotNetFx35setup.exe

Express with SP1:
  1. http://download.microsoft.com/download/F/E/7/FE754BA4-140B-413C-933F-8D35FB150F12/vbsetup.exe
  2. http://download.microsoft.com/download/F/E/7/FE754BA4-140B-413C-933F-8D35FB150F12/vcsetup.exe
  3. http://download.microsoft.com/download/F/E/7/FE754BA4-140B-413C-933F-8D35FB150F12/vcssetup.exe
  4. http://download.microsoft.com/download/F/E/7/FE754BA4-140B-413C-933F-8D35FB150F12/vnssetup.exe

TFS 2008 SP1: http://download.microsoft.com/download/a/e/2/ae2eb0ff-e687-4221-9c3e-9165a942bc1c/TFS90sp1-KB949786.exe

Note: This release does *not* include SP1 for .NET CF 3.5. No announcements have been made when this will occur.

Scott Guthrie talks about some interesting features/fixes with the advent of this SP.

Wednesday, May 07, 2008

Every Developer, Now a Mobile Developer!

The Mobile Developer Group at Microsoft has just recently created this blog: http://blogs.msdn.com/mobiledev/default.aspx

There is not much on there as yet, but book mark or subscribe via RSS as I'm sure the group will release some interesting stuff to read.

Sunday, May 04, 2008

Bring back XP Alt-Tab in Windows Vista!

This post hits the nail on the head with regards to the problem with the new Alt-Tab in Windows Vista. Win-Tab is quite cool and useful in which I will eventually move over to. But I want something fast and something that I can quickly find,hmmm much like in XP. In Vista, when pressing Alt-Tab instead of showing the icon of the application as in pre-Vista, this has been replaced with a very small image of the whole window which is too small to identify.

To restore the XP Alt-Tab functionality simply add a DWORD named AltTabSettings to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer and set its value to 1.


Friday, May 02, 2008

Rethrowing Exceptions in .NET

I recently learnt after all this time using .NET that rethrowing an exception such as throw ex in a catch block causes the CLR to alter the original exception meaning you will not get the full trace stack in the exception when it is finally bubbled up the chain.

Consider the following code:
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.Foo(true);
}
}

public class MyClass
{
public void Foo(bool includeTraceStack)
{
if (includeTraceStack)
ThrowExceptionIncTraceStack();
else
ThrowExceptionNotIncTraceStack();
}

public void ThrowExceptionIncTraceStack()
{
int a = 0;
int b = 0;

try
{
Divide(a, b);
}
catch (DivideByZeroException)
{
throw;
}
}

public void ThrowExceptionNotIncTraceStack()
{
int a = 0;
int b = 0;
try
{
Divide(a, b);
}
catch (DivideByZeroException ex)
{
throw ex;
}
}

public decimal Divide(int a, int b)
{
return a / b;
}
}
Calling MyClass.Foo(true) gives you the following trace stack:
 at ConsoleApplication1.MyClass.Divide(Int32 a, Int32 b) in C:\Develop\ConsoleApplication1\ConsoleApplication1\Program.cs:line 58
at ConsoleApplication1.MyClass.ThrowExceptionIncTraceStack() in C:\Develop\ConsoleApplication1\ConsoleApplication1\Program.cs:line 38
at ConsoleApplication1.MyClass.Foo(Boolean includeTraceStack) in C:\Develop\ConsoleApplication1\ConsoleApplication1\Program.cs:line 22
at ConsoleApplication1.Program.Main(String[] args) in C:\Develop\ConsoleApplication1\ConsoleApplication1\Program.cs:line 13
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Calling MyClass.Foo(false) gives you the following trace stack:
at ConsoleApplication1.MyClass.ThrowExceptionNotIncTraceStack() in C:\Develop\ConsoleApplication1\ConsoleApplication1\Program.cs:line 52
at ConsoleApplication1.MyClass.Foo(Boolean includeTraceStack) in C:\Develop\ConsoleApplication1\ConsoleApplication1\Program.cs:line 24
at ConsoleApplication1.Program.Main(String[] args) in C:\Develop\ConsoleApplication1\ConsoleApplication1\Program.cs:line 13
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Notice when throwing the original exception by qualifying the exception ie: throw ex as opposed to just throw clears the trace stack lower down in the calling chain. Just using throw gives us more information in the trace stack.

I must say I have always coded thow ex for clarity, not anymore!

Thursday, May 01, 2008

Mobile Client Software Factory

You might remember, last year I wrote a couple articles regarding the Mobile Software Client Factory Data Access Application Block (which I still use today) and how to use them etc These articles can be found here: Part1 and Part2.

It turns out Microsoft is no longer developing the MCSF but it can be found on codeplex here.