<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/atom.xsl" media="screen"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><title type="html">The High Altitude Developer</title><subtitle type="html" /><id>http://blogs.msdn.com/geoffda/atom.xml</id><link rel="alternate" type="text/html" href="http://blogs.msdn.com/geoffda/default.aspx" /><link rel="self" type="application/atom+xml" href="http://blogs.msdn.com/geoffda/atom.xml" /><generator uri="http://communityserver.org" version="2.1.61025.2">Community Server</generator><updated>2007-08-31T15:00:00Z</updated><entry><title>Beware of Auto-Reset Events (They Don't Behave the Way You Think They Do)</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/geoffda/archive/2008/08/18/beware-of-auto-reset-events.aspx" /><id>http://blogs.msdn.com/geoffda/archive/2008/08/18/beware-of-auto-reset-events.aspx</id><published>2008-08-19T02:53:00Z</published><updated>2008-08-19T02:53:00Z</updated><content type="html">&lt;P&gt;So I just spent&amp;nbsp;several days&amp;nbsp;debugging an intermittent crash in some test code.&amp;nbsp; Part of the reason it took so long was because whoever wrote this code introduced a dependency on a legacy test component that was apparently so old that sources and symbols had disappeared.&amp;nbsp; So the first part of the diagnosis involved a bit of reverse engineering, which is always a bit time consuming.&amp;nbsp; Another reason it took so&amp;nbsp;long was because the crash couldn't reliably be reproduced in the debugger, so I had to start with post-mortem analysis.&amp;nbsp; Fortunately, windbg is a great tool for doing that sort of thing.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;So I reproduce the crash, attach windbg and get started (interestingly enough, Dr. Watson couldn't grab a crash dump...):&lt;/P&gt;
&lt;P&gt;&amp;nbsp;*** wait with pending attach&lt;BR&gt;Symbol search path is: *******************&lt;BR&gt;Executable search path is: &lt;BR&gt;ModLoad: 09320000 0932b000&amp;nbsp;&amp;nbsp; c:\dd\OfficeClient_2\binaries\x86chk\SuiteBin\sdmquery.dll&lt;BR&gt;eax=00000000 ebx=00000001 ecx=00000000 edx=00000000 esi=00000000 edi=00000000&lt;BR&gt;eip=77170bc5 esp=0046e770 ebp=0046e808 iopl=0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; nv up ei pl nz na pe nc&lt;BR&gt;cs=0023&amp;nbsp; ss=002b&amp;nbsp; ds=002b&amp;nbsp; es=002b&amp;nbsp; fs=0053&amp;nbsp; gs=002b&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; efl=00000206&lt;BR&gt;ntdll!ZwWaitForMultipleObjects+0x15:&lt;BR&gt;77170bc5 c21400&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ret&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 14h&lt;/P&gt;
&lt;P&gt;The first thing to do is dump the stacks of all threads so we can figure out which one crashed.&lt;BR&gt;0:000&amp;gt; ~*k&lt;/P&gt;
&lt;P&gt;.&amp;nbsp; 0&amp;nbsp; Id: c80.ef0 Suspend: 1 Teb: 7efdd000 Unfrozen&lt;BR&gt;ChildEBP RetAddr&amp;nbsp; &lt;BR&gt;0046e76c 753ddcea ntdll!ZwWaitForMultipleObjects+0x15&lt;BR&gt;0046e808 712b549e KERNEL32!WaitForMultipleObjectsEx+0x11d&lt;BR&gt;0046e86c 712b5529 mscorwks!WaitForMultipleObjectsEx_SO_TOLERANT+0x56 &lt;BR&gt;0046e88c 712b5645 mscorwks!Thread::DoAppropriateAptStateWait+0x4d &lt;BR&gt;0046e918 712b51ed mscorwks!Thread::DoAppropriateWaitWorker+0x14d &lt;BR&gt;0046e984 714337cd mscorwks!Thread::DoAppropriateWait+0x60 &lt;BR&gt;*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.10722_32\mscorlib\d3ebaf905742c816f27f122da299ef5f\mscorlib.ni.dll&lt;BR&gt;0046ea80 10699cef mscorwks!WaitHandleNative::CorWaitOneNative+0x14f&lt;BR&gt;0046ea98 10699bc2 mscorlib_ni!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)+0x1f &lt;BR&gt;0046eab4 05c280c0 mscorlib_ni!System.Threading.WaitHandle.WaitOne(Int32, Boolean)+0x2a &lt;BR&gt;0046eb7c 05c27a40 Motif!MS.Internal.Motif.Runtime.TestElementWorkerThread.Join()+0x30 &lt;BR&gt;0046eb7c 026fdd9a Motif!MS.Internal.Motif.Runtime.WorkerThreadInvokeHandler.ElementInvokeHandler(System.Object, MS.Internal.Motif.Runtime.InvokedEventArgs)+0x1f8&lt;BR&gt;0046eb7c 026fdb3e Motif!MS.Internal.Motif.Runtime.TestElement.OnInvoked(MS.Internal.Motif.Runtime.InvokedEventArgs)+0x1d2 &lt;BR&gt;0046ecc8 05c2c472 Motif!MS.Internal.Motif.Runtime.TestElement.Invoke()+0x17e &lt;BR&gt;0046ecc8 002312d4 Motif!MS.Internal.Motif.Runtime.TestMethodAction.Execute()+0x212 &lt;BR&gt;*** ERROR: Module load completed but symbols could not be loaded for c:\dd\OfficeClient_2\binaries\x86chk\SuiteBin\sth_x86.exe&lt;BR&gt;0046f064 713c2ee9 sth_x86!MS.Internal.Motif.TestHarness.StandaloneTestHarness.Main(System.String[])+0x10d4&lt;BR&gt;0046f074 71256056 mscorwks!CallDescrWorker+0x33&lt;BR&gt;0046f0f0 7125721e mscorwks!CallDescrWorkerWithHandler+0x8e&lt;BR&gt;0046f22c 71257253 mscorwks!MethodDesc::CallDescr+0x19e &lt;BR&gt;0046f248 712572ac mscorwks!MethodDesc::CallTargetWorker+0x21 &lt;BR&gt;0046f260 71305db5 mscorwks!MethodDescCallSite::Call_RetArgSlot+0x1c&lt;/P&gt;
&lt;P&gt;...&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 8&amp;nbsp; Id: c80.98c Suspend: 1 Teb: 7ef40000 Unfrozen&lt;BR&gt;ChildEBP RetAddr&amp;nbsp; &lt;BR&gt;06b3bc34 753d1270 ntdll!NtWaitForSingleObject+0x15 &lt;BR&gt;06b3bca4 753d11d8 KERNEL32!WaitForSingleObjectEx+0xbe&lt;BR&gt;06b3bcb8 713acb41 KERNEL32!WaitForSingleObject+0x12 &lt;BR&gt;06b3bce8 713ad1ed mscorwks!ClrWaitForSingleObject+0x24&lt;BR&gt;06b3c1a4 713afbcd mscorwks!RunWatson+0x1e3&lt;BR&gt;06b3c8f4 713b01d1 mscorwks!DoFaultReportWorker+0xa46&lt;BR&gt;06b3c934 714130a8 mscorwks!DoFaultReport+0x40&lt;BR&gt;06b3c958 71411f04 mscorwks!WatsonLastChance+0x42 &lt;BR&gt;06b3d5f4 71411f7c mscorwks!EEPolicy::LogFatalError+0x420 &lt;BR&gt;06b3d60c 71413758 mscorwks!EEPolicy::HandleFatalError+0x38&lt;BR&gt;06b3d630 71413c44 mscorwks!CLRVectoredExceptionHandlerPhase3+0xa6&lt;BR&gt;06b3d664 714145a3 mscorwks!CLRVectoredExceptionHandlerPhase2+0x20&lt;BR&gt;06b3d690 7141466c mscorwks!CLRVectoredExceptionHandler+0xad&lt;BR&gt;06b3d6bc 771aa357 mscorwks!CLRVectoredExceptionHandlerShim+0x6d &lt;BR&gt;06b3d6e8 7718c6c2 ntdll!RtlpCallVectoredHandlers+0x7a &lt;BR&gt;06b3d6fc 7718c754 ntdll!RtlCallVectoredExceptionHandlers+0x12 &lt;BR&gt;06b3d774 77172eff ntdll!RtlDispatchException+0x19&lt;BR&gt;06b3d774 712a0364 ntdll!KiUserExceptionDispatcher+0xf&lt;BR&gt;06b3dad4 712a46e5 mscorwks!WKS::CFinalize::ScanForFinalization+0xc8&lt;BR&gt;06b3db14 712a371f mscorwks!WKS::gc_heap::mark_phase+0x2c3&lt;/P&gt;
&lt;P&gt;...&lt;/P&gt;
&lt;P&gt;&amp;nbsp; 17&amp;nbsp; Id: c80.980 Suspend: 1 Teb: 7ef22000 Unfrozen&lt;BR&gt;ChildEBP RetAddr&amp;nbsp; &lt;BR&gt;0e24fb2c 77217489 ntdll!ZwWaitForMultipleObjects+0x15&lt;BR&gt;0e24fcc8 7544e3f3 ntdll!TppWaiterpThread+0x294 &lt;BR&gt;0e24fcd4 771ccfed KERNEL32!BaseThreadInitThunk+0xe&lt;BR&gt;0e24fd14 771cd1ff ntdll!__RtlUserThreadStart+0x23&lt;BR&gt;0e24fd2c 00000000 ntdll!_RtlUserThreadStart+0x1b&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;Thread #8 is clearly the problem, so we switch the debugger to that thread.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;0:000&amp;gt; ~8s&lt;BR&gt;eax=00000000 ebx=753d1661 ecx=0cd965ac edx=00400000 esi=06b3bc7c edi=00000000&lt;BR&gt;eip=7717039d esp=06b3bc38 ebp=06b3bca4 iopl=0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; nv up ei pl zr na pe nc&lt;BR&gt;cs=0023&amp;nbsp; ss=002b&amp;nbsp; ds=002b&amp;nbsp; es=002b&amp;nbsp; fs=0053&amp;nbsp; gs=002b&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; efl=00010246&lt;BR&gt;ntdll!NtWaitForSingleObject+0x15:&lt;BR&gt;7717039d c20c00&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ret&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0Ch&lt;/P&gt;
&lt;P&gt;Now we dump the full mixed stack so we can get the full picture of what is going on.&lt;/P&gt;
&lt;P&gt;0:008&amp;gt; !dumpstack&lt;BR&gt;OS Thread Id: 0x98c (8)&lt;BR&gt;Current frame: ntdll!NtWaitForSingleObject+0x15 &lt;BR&gt;ChildEBP RetAddr&amp;nbsp; Caller,Callee&lt;BR&gt;06b3bc34 753d1270 KERNEL32!WaitForSingleObjectEx+0xbe &lt;BR&gt;...&lt;BR&gt;06b3df6c 0905e3fa (MethodDesc 0x127fe4 +0x52 MS.Internal.Mita.Logging.Log.Fail(System.Object, System.String, System.String))&lt;BR&gt;06b3df94 0905e38d (MethodDesc 0x127fdc +0x2d MS.Internal.Mita.Logging.Log.Fail(System.Object, System.String)), calling (MethodDesc 0x127fe4 +0 MS.Internal.Mita.Logging.Log.Fail(System.Object, System.String, System.String))&lt;BR&gt;06b3dfa4 0905e346 (MethodDesc 0x5cf2c78 +0x6 Microsoft.TrinityQA.Logging.MitaGlobalLog.Fail(System.String)), calling (MethodDesc 0x127fdc +0 MS.Internal.Mita.Logging.Log.Fail(System.Object, System.String))&lt;BR&gt;06b3dfa8 0905e31b (MethodDesc 0x5cf46c0 +0x1b Microsoft.TrinityQA.Logging.SharedLog.Fail(System.String)), calling 09180582&lt;BR&gt;06b3dfb8 0905e29e (MethodDesc 0x5cf30f0 +0x3e Microsoft.TrinityQA.Logging.TrinityLog.Fail(System.String)), calling 09180572&lt;BR&gt;06b3dfec 0905e245 (MethodDesc 0x5cf30f8 +0x2d Microsoft.TrinityQA.Logging.TrinityLog.Fail(System.Exception)), calling (MethodDesc 0x5cf30f0 +0 Microsoft.TrinityQA.Logging.TrinityLog.Fail(System.String))&lt;/P&gt;
&lt;P&gt;...&lt;BR&gt;06b3e87c 77173099 ntdll!ExecuteHandler2+0x26&lt;BR&gt;06b3e8a0 7717306b ntdll!ExecuteHandler+0x24 calling ntdll!ExecuteHandler2 &lt;BR&gt;06b3e8c4 7718c7ba ntdll!RtlDispatchException+0x124 , calling ntdll!RtlpExecuteHandlerForException &lt;BR&gt;06b3e950 77172eff ntdll!KiUserExceptionDispatcher+0xf, calling ntdll!RtlDispatchException &lt;BR&gt;06b3ec84 09321260 *** WARNING: Unable to verify checksum for c:\dd\OfficeClient_2\binaries\x86chk\SuiteBin\sdmquery.dll&lt;BR&gt;*** ERROR: Symbol file could not be found.&amp;nbsp; Defaulted to export symbols for c:\dd\OfficeClient_2\binaries\x86chk\SuiteBin\sdmquery.dll - &lt;BR&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: #c00000; LINE-HEIGHT: 115%; FONT-FAMILY: 'Arial','sans-serif'"&gt;sdmquery!SDMCALL+0x1c0 ====&amp;gt; Exception Code 255cc2d &lt;A href="mailto:cxr@6b3e9b8" mce_href="mailto:cxr@6b3e9b8"&gt;&lt;SPAN style="COLOR: #c00000"&gt;cxr@6b3e9b8&lt;/SPAN&gt;&lt;/A&gt; &lt;A href="mailto:exr@b3e968" mce_href="mailto:exr@b3e968"&gt;&lt;SPAN style="COLOR: #c00000"&gt;exr@b3e968&lt;/SPAN&gt;&lt;/A&gt;&lt;/SPAN&gt;&lt;/B&gt;&lt;BR&gt;06b3ecb8 76cf7deb USER32!IsWindow+0x54 calling USER32!__SEH_epilog4 &lt;BR&gt;06b3ecbc 0011aa60 0011aa60, calling KERNEL32!GetLastError &lt;BR&gt;06b3ecc8 0029874f 0029874f&lt;BR&gt;06b3ecf0 0905e0ef (MethodDesc 0x6f3bf7c +0x1df Maui.Core.SdmWindow.GetDialogInfo()), calling 0034832c&lt;BR&gt;06b3ed10 0905e0ef (MethodDesc 0x6f3bf7c +0x1df Maui.Core.SdmWindow.GetDialogInfo()), calling 0034832c&lt;BR&gt;06b3ed80 0905dead (MethodDesc 0x6f3bf04 +0x35 Maui.Core.SdmWindow.Init()), calling 003482b8&lt;BR&gt;06b3ed94 0905de5d (MethodDesc 0x6f3beec +0x25 Maui.Core.SdmWindow..ctor(System.String, Maui.Core.Utilities.StringMatchSyntax, System.String, Maui.Core.Utilities.StringMatchSyntax, Maui.Core.App, Int32)), calling 00348274&lt;BR&gt;06b3eda0 0905de18 (MethodDesc 0x6f3c2d0 +0x30 Maui.VisualStudio.Current.Vsto.Office12.Dialogs.SdmAddNamedRangeDialog..ctor(Maui.Core.App)), calling 00348264&lt;BR&gt;06b3edc4 0905ddc7 (MethodDesc 0x6f38bf0 +0x27 Maui.VisualStudio.Current.Vsto.Office12.DesignerControls.Excel.NamedRange.OnComponentInitialize()), calling 00348244&lt;BR&gt;06b3edd4 09059799 (MethodDesc 0x92c4a60 +0x2a9 Maui.VisualStudio.Current.Vsto.Designers.VstoDesigner.AddToolBoxComponent(Maui.VisualStudio.Current.Ide.Designers.IToolBoxComponent, Maui.VisualStudio.Current.Ide.Designers.AddMethod, Int32, Int32, Int32, Int32)), calling 09180432&lt;BR&gt;06b3ee08 0905945d (MethodDesc 0x92c4cbc +0x2d Maui.VisualStudio.Current.Vsto.Office12.Designers.WorksheetDesigner.AddToolBoxComponent(Maui.VisualStudio.Current.Ide.Designers.IToolBoxComponent, Maui.VisualStudio.Current.Ide.Designers.AddMethod, Int32, Int32, Int32, Int32)), calling (MethodDesc 0x92c4a60 +0 Maui.VisualStudio.Current.Vsto.Designers.VstoDesigner.AddToolBoxComponent(Maui.VisualStudio.Current.Ide.Designers.IToolBoxComponent, Maui.VisualStudio.Current.Ide.Designers.AddMethod, Int32, Int32, Int32, Int32))&lt;BR&gt;06b3ee2c 09059361 (MethodDesc 0x92c4a50 +0xa9 Maui.VisualStudio.Current.Vsto.Designers.VstoDesigner.AddToolBoxComponent(Maui.VisualStudio.Current.Ide.Designers.IToolBoxComponent, Maui.VisualStudio.Current.Ide.Designers.AddMethod))&lt;BR&gt;06b3ee54 0905927b (MethodDesc 0x6f38ba8 +0x43 Maui.VisualStudio.Current.Vsto.Office12.DesignerControls.Excel.NamedRange..ctor(Maui.VisualStudio.Current.Vsto.Office12.Designers.WorksheetDesigner, Maui.VisualStudio.Current.Ide.Designers.AddMethod))&lt;BR&gt;06b3ee68 09059206 (MethodDesc 0x92c4784 +0x1e Microsoft.TrinityQA.Vsto.Helpers.Project.Excel12DesignerHelper.AddNamedRange(Maui.VisualStudio.Current.Ide.Designers.AddMethod)), calling 0034767c&lt;BR&gt;06b3ee7c 09053dbd (MethodDesc 0x5cfba34 +0x5d Microsoft.TrinityQA.FeatureTesters.Vsto.ExcelDocProjectTester.BF_Debug_Windows()), calling 09180182&lt;BR&gt;06b3ee84 06807dc0 (MethodDesc 0x5cfaedc +0xb0 Microsoft.TrinityQA.FeatureTesters.Vsto.DocProjectTester.RunTest(Microsoft.TrinityQA.FeatureTesters.TestSession.TestCase))&lt;BR&gt;06b3eea4 06807dc0 (MethodDesc 0x5cfaedc +0xb0 Microsoft.TrinityQA.FeatureTesters.Vsto.DocProjectTester.RunTest(Microsoft.TrinityQA.FeatureTesters.TestSession.TestCase))&lt;BR&gt;06b3eedc 06807cf7 (MethodDesc 0x48ed118 +0x4f Microsoft.TrinityQA.FeatureTesters.Vsto.DocProjectEntryPoints.BF_Debug_Windows()), calling (MethodDesc 0x5cfaedc +0 Microsoft.TrinityQA.FeatureTesters.Vsto.DocProjectTester.RunTest(Microsoft.TrinityQA.FeatureTesters.TestSession.TestCase))&lt;BR&gt;06b3eef4 713c2ee9 mscorwks!CallDescrWorker+0x33 &lt;BR&gt;06b3ef04 71256056 mscorwks!CallDescrWorkerWithHandler+0x8e calling mscorwks!CallDescrWorker &lt;BR&gt;...&lt;/P&gt;
&lt;P&gt;06b3ff40 771cd1ff ntdll!_RtlUserThreadStart+0x1b, calling ntdll!__RtlUserThreadStart &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;From the stack, we can see that sdmquery!SDMCALL is throwing and there is an exception record (exr).&amp;nbsp; However, the exception code isn't something I recognize.&amp;nbsp; Luckily, there is a managed exception on the stack, so if we dump that, we should get a string that explains the problem.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;0:008&amp;gt; !dso&lt;BR&gt;OS Thread Id: 0x98c (8)&lt;BR&gt;ESP/REG&amp;nbsp; Object&amp;nbsp;&amp;nbsp; Name&lt;BR&gt;06b3c890 028310b4 System.ExecutionEngineException&lt;BR&gt;06b3c8dc 028310b4 System.ExecutionEngineException&lt;BR&gt;06b3db90 0cda8628 System.String&amp;nbsp;&amp;nbsp;&amp;nbsp; cat="INFO/TEST/FAIL"&lt;BR&gt;06b3dc24 0cf8467c System.String&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&lt;BR&gt;06b3dc40 0cf83ba8 System.String&amp;nbsp;&amp;nbsp;&amp;nbsp; System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.&lt;BR&gt;&amp;nbsp;&amp;nbsp; at Maui.Core.NativeMethods.BldSdmCall(IntPtr HWnd, Int32 wMsg, Int32 wp, CTRLDEF&amp;amp;amp; lp)&lt;/P&gt;
&lt;P&gt;...&lt;/P&gt;
&lt;P&gt;06b3f824 029042ac System.Threading.ThreadStart&lt;/P&gt;
&lt;P&gt;Ok, now we can see we're looking at an access violation.&amp;nbsp; WinDbg has a very useful dot command that allows us to switch the context to reflect system state at the time of the exception.&amp;nbsp; So we'll use the .exr command to do that and see what our state was when we crashed.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;0:008&amp;gt; .cxr 6b3e9b8&lt;BR&gt;eax=09530000 ebx=000000a0 ecx=00000249 edx=00001900 esi=09531000 edi=0cd97460&lt;BR&gt;eip=09321260 esp=06b3ec84 ebp=06b3ecc8 iopl=0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; nv up ei pl nz na po nc&lt;BR&gt;cs=0023&amp;nbsp; ss=002b&amp;nbsp; ds=002b&amp;nbsp; es=002b&amp;nbsp; fs=0053&amp;nbsp; gs=002b&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; efl=00010202&lt;BR&gt;sdmquery!SDMCALL+0x1c0:&lt;BR&gt;09321260 f3a5&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rep movs dword ptr es:[edi],dword ptr [esi]&lt;/P&gt;
&lt;P&gt;Now we're getting somewhere.&amp;nbsp; The access violation occured during a memory copy of some sort.&amp;nbsp; We can dissasamble from here to get the address of the next instruction.&amp;nbsp; We'll use that to disassemble backwards so we can see what the code was doing prior to the memory copy.&lt;/P&gt;
&lt;P&gt;0:008&amp;gt; u 09321260&lt;BR&gt;sdmquery!SDMCALL+0x1c0:&lt;BR&gt;09321260 f3a5&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rep movs dword ptr es:[edi],dword ptr [esi]&lt;BR&gt;09321262 8bca&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ecx,edx&lt;BR&gt;09321264 83e103&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; and&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ecx,3&lt;BR&gt;09321267 f3a4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rep movs byte ptr es:[edi],byte ptr [esi]&lt;BR&gt;09321269 eb4d&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; jmp&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sdmquery!SDMCALL+0x218 (093212b8)&lt;BR&gt;0932126b 81f9fd7f0000&amp;nbsp;&amp;nbsp;&amp;nbsp; cmp&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ecx,7FFDh&lt;BR&gt;09321271 7408&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; je&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sdmquery!SDMCALL+0x1db (0932127b)&lt;BR&gt;09321273 81f9fc7f0000&amp;nbsp;&amp;nbsp;&amp;nbsp; cmp&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ecx,7FFCh&lt;/P&gt;
&lt;P&gt;Now that we have the next instruction, we can disassemble backwards to see what the function does.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;0:008&amp;gt; ub 09321262 l 100&lt;BR&gt;sdmquery+0xf78:&lt;BR&gt;09320f78 0000&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; add&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; byte ptr [eax],al&lt;BR&gt;...&lt;BR&gt;0932123c a174873209&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; eax,dword ptr [sdmquery!SDMCALL+0x76d4 (09328774)]&lt;BR&gt;09321241 8b4d0c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ecx,dword ptr [ebp+0Ch]&lt;BR&gt;09321244 81f9f77f0000&amp;nbsp;&amp;nbsp;&amp;nbsp; cmp&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ecx,7FF7h&lt;BR&gt;0932124a 8b5804&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ebx,dword ptr [eax+4]&lt;BR&gt;0932124d 751c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; jne&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sdmquery!SDMCALL+0x1cb (0932126b)&lt;BR&gt;0932124f 8b7d14&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; edi,dword ptr [ebp+14h]&lt;BR&gt;09321252 8d0c9b&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lea&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ecx,[ebx+ebx*4]&lt;BR&gt;09321255 c1e103&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; shl&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ecx,3&lt;BR&gt;09321258 8bd1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; edx,ecx&lt;BR&gt;0932125a 8d7024&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lea&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; esi,[eax+24h]&lt;BR&gt;0932125d c1e902&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; shr&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ecx,2&lt;BR&gt;09321260 f3a5&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rep movs dword ptr es:[edi],dword ptr [esi]&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;Ok, we have an idea of what the function is doing, so the next step is to figure out what kind of an acess violation we're getting.&amp;nbsp; We'll start be checking the address that edi points to.&amp;nbsp; If we get garbage there, we're looking at a buffer overrun.&amp;nbsp; However, it looks like edi is pointing to a valid address.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;0:008&amp;gt; dd edi&lt;BR&gt;0cd97460&amp;nbsp; 0074006e 0032005f 0073005c 00630072&lt;BR&gt;0cd97470&amp;nbsp; 0073005c 00690075 00650074 00720073&lt;BR&gt;0cd97480&amp;nbsp; 005c0063 00720074 006e0069 00740069&lt;BR&gt;0cd97490&amp;nbsp; 005c0079 00750061 006f0074 0061006d&lt;BR&gt;0cd974a0&amp;nbsp; 00690074 006e006f 0043005c 006d006f&lt;BR&gt;0cd974b0&amp;nbsp; 006f006d 005c006e 0069004c 005c0062&lt;BR&gt;0cd974c0&amp;nbsp; 0061004d 00690075 0056005c 00730069&lt;BR&gt;0cd974d0&amp;nbsp; 00610075 0053006c 00750074 00690064&lt;/P&gt;
&lt;P&gt;Next, we'll dump the source pointer.&amp;nbsp; Clearly, this address is bad.&amp;nbsp;&amp;nbsp;So at a minimum, we've read off the end of our source buffer while doing the memory copy.&amp;nbsp; The 64k question is&amp;nbsp;why.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;0:008&amp;gt; dd esi&lt;BR&gt;09531000&amp;nbsp; ???????? ???????? ???????? ????????&lt;BR&gt;09531010&amp;nbsp; ???????? ???????? ???????? ????????&lt;BR&gt;09531020&amp;nbsp; ???????? ???????? ???????? ????????&lt;BR&gt;09531030&amp;nbsp; ???????? ???????? ???????? ????????&lt;BR&gt;09531040&amp;nbsp; ???????? ???????? ???????? ????????&lt;BR&gt;09531050&amp;nbsp; ???????? ???????? ???????? ????????&lt;BR&gt;09531060&amp;nbsp; ???????? ???????? ???????? ????????&lt;BR&gt;09531070&amp;nbsp; ???????? ???????? ???????? ????????&lt;/P&gt;
&lt;P&gt;Let's refer back to the exception context and look at some registers.&amp;nbsp; ECX is definitely suspect.&amp;nbsp; We're trying to copy at least 585 bytes (and probably more since we don't yet know what ECX started at).&amp;nbsp; In any event, the overrun may be caused by a bug in the size calculation, so lets explore that.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;eax=09530000 ebx=000000a0 ecx=00000249 edx=00001900 esi=09531000 edi=0cd97460&lt;BR&gt;eip=09321260 esp=06b3ec84 ebp=06b3ecc8 iopl=0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; nv up ei pl nz na po nc&lt;BR&gt;cs=0023&amp;nbsp; ss=002b&amp;nbsp; ds=002b&amp;nbsp; es=002b&amp;nbsp; fs=0053&amp;nbsp; gs=002b&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; efl=00010202&lt;BR&gt;sdmquery!SDMCALL+0x1c0:&lt;BR&gt;09321260 f3a5&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rep movs dword ptr es:[edi],dword ptr [esi]&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;Looking&amp;nbsp;at the disassembly, we see that ECX's value is determined by some EBX calculations.&amp;nbsp; EBX contains an offset of an EAX address.&amp;nbsp; Luckily we can see that none of these registers have been overwritten so we should be able to examine them.&amp;nbsp; We'll start with eax+4--which should be the value that ebx got.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;0:008&amp;gt; dd eax+4&lt;BR&gt;09530004&amp;nbsp; 00000004 00120ab2 00007ff7 00000002&lt;BR&gt;09530014&amp;nbsp; 0cd96484 76cf7deb 0011aa60 00000001&lt;BR&gt;09530024&amp;nbsp; 00010002 00060000 00000000 00000086&lt;BR&gt;09530034&amp;nbsp; 00000035 000000d2 0000004d 0000011a&lt;BR&gt;09530044&amp;nbsp; 0000ffff ffffffff 00020002 00070000&lt;BR&gt;09530054&amp;nbsp; 00000000 000000da 00000035 00000126&lt;BR&gt;09530064&amp;nbsp; 0000004d 0000011a 0000ffff ffffffff&lt;BR&gt;09530074&amp;nbsp; 00120001 00400000 00000000 00000006&lt;/P&gt;
&lt;P&gt;Strange; eax+4 is 0x4, yet ebx is 0x0a.&amp;nbsp; The only possible way that could happen is if eax+4 got overwritten with a new value *after* it got read into EBX.&amp;nbsp; This code certainly isn't doing that.&amp;nbsp; It is worth mentioning though that when this bug does not occur, *both* EAX+4 and EBX are set to 0x4.&lt;BR&gt;0:008&amp;gt; ?ebx&lt;BR&gt;Evaluate expression: 160 = 000000a0&lt;/P&gt;
&lt;P&gt;We also see that edx holds the original value that ECX was set to.&amp;nbsp; IOW it contains the original count.&amp;nbsp; If we follow the assembly, we can see it jibes with an EBX value of 0xa0.&amp;nbsp; IOW 0xa0 + (0xa0*4) = 0x320&amp;nbsp; 0x320 * 8 (the shl instruction multiplies by 2^#bits shifted) = 0x1900 which is what we see in edx.&lt;/P&gt;
&lt;P&gt;0:008&amp;gt; ?edx&lt;BR&gt;Evaluate expression: 6400 = 00001900&lt;/P&gt;
&lt;P&gt;And just for grins we can verify that the starting address for the memory copy is valid.&amp;nbsp; ESI was originally set to eax+24, which is still valid since EAX hasn't been overwritten.&amp;nbsp; EAX+24 looks like its valid.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;Luckily, before I had to try to reverse engineer the entire function, a collegue came to the rescue with some old source files for the problem component.&amp;nbsp; With that I was able to figure out the bug--but only after several days of painful debugging.&amp;nbsp; The problem code seemed to make sense.&amp;nbsp; It was a mechanism for sending dialog messages cross-process in a synchronous fashion.&amp;nbsp; To do that, the code would wrap up the message in some shared memory (using a memory mapped file), install a GetMessage hook in the target process, post a custom message to the target process, and then wait for an event to be signalled.&amp;nbsp; Once the event was signalled, the returned data would be read out of the shared memory buffer.&amp;nbsp; On the target side, the hook proc would intercept the custom message, unwrap the real message from shared memory, send it to the dialog, write the return values to the shared memory and then signal the event.&lt;/P&gt;
&lt;P&gt;Since I couldn't see the problem from just looking at the code, I decided to try to build the module from the sources I had and see if it would still reproduce the bug.&amp;nbsp; Fortunately it did, so I started by adding a bunch of asserts and trace statements to try to gather more information.&amp;nbsp; What I could see was that in some cases, the value of the shared memory was changing after the WaitForSingleObject call had returned.&amp;nbsp; The other interesting thing is that 0x0a is a legitmate value returned by the first call into the offending code.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;The first thing that occured to me was that perhaps I was seeing latency in the memory mapped file--i.e. perhaps we were missing a FlushViewOfFile call.&amp;nbsp; However, closer examination of the code revealed that the memory mapped file was backed by the page file, so there could be no latency.&amp;nbsp; My next thought was that perhaps there was some re-entrancy occuring, but eventually I was able to rule that out.&amp;nbsp; Finally, I started looking at the WaitForSingleObject call.&amp;nbsp; What had to be happening was that somehow the event was getting incorrectly signalled.&amp;nbsp; Unfortunately, there isn't a way to determine the state of the event, but from looking at the code, it didn't seem possible for the state to ever be wrong.&amp;nbsp; Finally, in desperation, I decided to change the Event to be a manual reset event.&amp;nbsp; My hope was that by adding explicit state changes, I might be able to pin-point where the state was being incorrectly set.&amp;nbsp; Lo and behold, moving to manual reset events fixed the bug.&lt;/P&gt;
&lt;P&gt;Now I was really scratching my head, so I started a careful review of the SetEvent and ResetEvent documentation.&amp;nbsp; And that was where I found the cause of the bug.&amp;nbsp; According to msdn documentation (&lt;A href="http://msdn.microsoft.com/en-us/library/ms686211.aspx"&gt;http://msdn.microsoft.com/en-us/library/ms686211.aspx&lt;/A&gt;), calling SetEvent has the following behavior:&lt;/P&gt;
&lt;P&gt;"The state of an auto-reset event object remains signaled until a single waiting thread is released, at which time the system automatically sets the state to nonsignaled. &lt;B style="mso-bidi-font-weight: normal"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Arial','sans-serif'"&gt;If no threads are waiting, the event object's state remains signaled&lt;/SPAN&gt;&lt;/B&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Arial','sans-serif'"&gt;."&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Arial','sans-serif'"&gt;Whoa!&amp;nbsp; My understanding of auto-reset events was that the reset occured when the wait was satisified.&amp;nbsp; Clearly this is not the case!&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;Suddenly, the cause of the problem became crystal clear.&amp;nbsp; What was happening when the bug occured was this:&amp;nbsp; When the offending code called PostMessage, the hook proc would grab the message in the target process.&amp;nbsp; In some cases, the timing would be such that that hook proc would finish its work and signal the event (via SetEvent) before the code in the calling process&amp;nbsp;had reached&amp;nbsp;the WaitForSingleObject call.&amp;nbsp; In that case, since no thread was (yet) waiting on the event, the SetEvent call would *not* reset the event.&amp;nbsp; This resulted in the event staying signalled which meant that the next call to WaitForSingleObject would immediately (and incorrectly) return.&amp;nbsp; As a result,&amp;nbsp;the value in the shared memory block would be read before the hook proc had actually performed the update.&amp;nbsp; In the bug case, this value would be sufficiently wrong to produce the read access violation on the memory copy.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;So the moral of the story is that auto-reset events can be very dangerous.&amp;nbsp; I think many of us tend to use auto-reset events by default--with an incorrect understanding of how they actually behave.&amp;nbsp; Because it is virtually impossible to guarantee that the thread to be synchronized is actually waiting when the event is signalled, auto-reset events aren't particularly useful as a synchronization mechanism.&amp;nbsp; Manual-reset events are much safer and are a much better choice for all-around use.&amp;nbsp; Really, now that I understand the behavior of auto-reset events, I can't think of a good reason to use them.&amp;nbsp; What little overhead they save you in terms of code seems hardly worth the risk of having a broken synchronization model.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8877667" width="1" height="1"&gt;</content><author><name>geoffda</name><uri>http://blogs.msdn.com/members/geoffda.aspx</uri></author></entry><entry><title>Why Stepping Through Code Trumps Unit Testing</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/geoffda/archive/2008/08/04/why-stepping-through-code-trumps-unit-testing.aspx" /><id>http://blogs.msdn.com/geoffda/archive/2008/08/04/why-stepping-through-code-trumps-unit-testing.aspx</id><published>2008-08-04T16:51:00Z</published><updated>2008-08-04T16:51:00Z</updated><content type="html">&lt;P&gt;Over the past few years, there has been a big buzz about how writing unit tests (preferably via Test Driven Development) combined with code coverage is the silver bullet for keeping bugs out of the product.&amp;nbsp; While the benefits of unit testing are undeniable, there are quite a few folks out there who advocate unit testing as a replacement for stepping through code.&amp;nbsp; In some cases I have even heard stepping through code described as "old school".&amp;nbsp; I've always been uncomfortable with that position and I finally encountered&amp;nbsp;a bug that exposes&amp;nbsp;it&amp;nbsp;for the utter nonsense that it is.&amp;nbsp; Consider the following code:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; const string RegValue1 = "SomeValue";&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; const string RegValue2 = "SomeOtherValue";&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int bufferSize = 255 * Marshal.SizeOf(char);&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; StringBuilder buffer = new StringBuilder(bufferSize);&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result = NativeMethods.RegQueryValueEx(hkey, RegValue1, IntPtr.Zero, IntPtr.Zero, buffer, ref bufferSize);&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;string value1= buffer.ToString();&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;buffer = new StringBuilder(bufferSize);&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT face=Calibri size=3&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result = NativeMethods.RegQueryValueEx(hkey, RegValue2, IntPtr.Zero, IntPtr.Zero, buffer, ref bufferSize);&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;string value2= buffer.ToString();&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;buffer = new StringBuilder(bufferSize);&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&lt;o:p&gt;&amp;nbsp;&lt;FONT face=Arial color=#000000 size=2&gt;Were you able to spot the bug?&amp;nbsp; I'll give you a hint: it will only occur if the string stored in the second registry value is larger than the first.&amp;nbsp; If you still don't see it, don't worry.&amp;nbsp; This bug is extremely difficult to spot if you are just looking at code.&amp;nbsp; The basic problem stems from the design of the RegQueryValueEx API.&amp;nbsp; The last parameter (lpcbData) ist he problem; it requires you to pass the byte count of the buffer in, but it then overwrites that value with the number of bytes actually written on return.&amp;nbsp; In the code above, the buffer size is reused in multiple calls.&amp;nbsp; Although it starts out as 1020 bytes, the first call to RegQueryValueEx will result in the value of bufferSize being changed to the number of bytes written to the first buffer.&amp;nbsp; So on the second call to RegQueryValueEx, the buffer size being passed in will likely end up being less than the intended size of the buffer.&amp;nbsp; If it happens that the second registry value is larger than the first, then the call will fail with ERROR_MORE_DATA because the passed in buffer size will be too small.&amp;nbsp; &lt;/FONT&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&lt;o:p&gt;&lt;FONT face=Arial color=#000000 size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;&lt;o:p&gt;&lt;FONT face=Arial color=#000000 size=2&gt;As a side note, that is why desgning an API with in/out parameters for anything but buffers is generally a bad design.&amp;nbsp; Buffers are about the only place where the developer mind-set expects the output will be different than the input.&amp;nbsp; In just about any other case, such a design practically guarantees that the caller will introduce this type of bug in their code because they&amp;nbsp;will forget that&amp;nbsp;their value got overwritten.&lt;/FONT&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;Back to topic: the truly scary thing about this bug is that there is no reason to think that the relative sizes of the registry values would have an effect on how this code behaves.&amp;nbsp; As a result, it is highly unlikely that anyone would ever think to test the behavior where the length of&amp;nbsp;the second registry value was greater than the first.&amp;nbsp; Moreover, even if the developer did decide to write that test, they would have to correctly mock the behavior in RegQueryValueEx that causes the problem.&amp;nbsp; Given that the developer mindset was clearly that they didn't care about the bytes written, it is unlikely that they would include that behavior in the mock--which means that their test results would be invalid.&amp;nbsp; Which is why mocking system APIs is generally not a particularly good use of time.&amp;nbsp; It should also be pointed out that if tests were written to exercise this code, we would see 100 percent code coverage even with inputs that would not find this bug.&amp;nbsp; Which is a great reminder as to why code coverage numbers aren't as meaningful as some would make them.&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;The bottom line is that this bug is unlikely to be discovered through intentional testing.&amp;nbsp; The best case scenario would be to find this bug accidentally by simply being lucky in the test values chosen.&amp;nbsp; While accidents can happen, the most likely scenario for a developer relying solely on testing (unit or otherwise) is that this bug ships to customers.&amp;nbsp; &lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;In our case, that is exactly what&amp;nbsp;happened.&amp;nbsp; Fortunately, the bug&amp;nbsp;involved in-house code so we were able to deploy the fix immediately.&amp;nbsp; It should be noted that it took a year for this bug to surface, and&amp;nbsp;when it did, it completely broke us.&amp;nbsp; Which makes this type of bug the ultimate nightmare scenario.&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;So how do we prevent a bug like this from shipping?&amp;nbsp; Stepping through code is the ultimate answer, but before we go there, let's talk about how the compiler is the first line of defense.&amp;nbsp; In this case, bufferSize should have been declared as a constant because it is clearly not intended to be changed.&amp;nbsp; If that had been done, the developer would have seen compiler errors because RegQueryValueEx doesn't accept a constant type for the by reference lpcbData parameter.&amp;nbsp; Assuming the developer paid attention to the error (rather than just removing the const to get their code to compile), they would have quickly realized the problem and the bug would have never been coded in the first place.&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;But we're all human.&amp;nbsp; Forgetting to declare buffSize as const would be a common enough mistake, as would removing it in response to the compiler error.&amp;nbsp; The latter would result from the superficial analysis of "right, of course I can't pass a const by ref" while ignoring the implications of what by ref actually means.&amp;nbsp; So that leaves stepping through code as the best line of defense against a bug like this.&amp;nbsp; The problem with stepping through code is that developers either don't do it, or they don't understand how to do it correctly.&amp;nbsp; Many developers do attempt to step through every line of code that they write, but they do so in a superficial way--whereby they only look at return values.&amp;nbsp; With this bug, that doesn't cut it.&amp;nbsp; So let's talk about how to correctly step through code.&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;The first issue with stepping through code is simply keeping track of where we have been so we can ensure we've stepped through every line.&amp;nbsp; I've found that the best way to do this is to place break points on the first line of every code block.&amp;nbsp; This includes not only functions, but every conditional or flow control statement.&amp;nbsp; As we complete stepping through each block, we remove the breakpoint.&amp;nbsp; When no breakpoints remain, we know we have stepped through each line of code.&amp;nbsp; Unit tests&amp;nbsp;can be helpful&amp;nbsp;in this regard because they can enable us to enter code paths that we might not otherwise be able to enter normally.&amp;nbsp; Alternatively, we can explicitly manipulate the instruction pointer and/or change values in the debugger to direct flow into a code path that we wish to step through.&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;While visiting every line of code is a good start, it is only half the battle.&amp;nbsp; The other half is verifying that what each line actually does matches our original intent.&amp;nbsp; Certainly, checking return values of functions is important, but checking inputs is equally important.&amp;nbsp; If we only look at outputs, we are just guessing that the function behaved correctly.&amp;nbsp; We are guessing because if we don't know what the inputs are, we have no basis for saying that the function did what we expected it to do.&amp;nbsp;&amp;nbsp;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;FONT size=3&gt;&lt;FONT color=#000000 size=2&gt;&lt;o:p&gt;Checking inputs would have resulted in this bug being caught because we would have seen that bufferSize &lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;was not what we expected on the second call to RegQueryValueEx.&amp;nbsp; The important thing to call out here is that proper stepping through code would have caught this bug *regardless* of whether the inputs were sufficient to actually cause the bug.&amp;nbsp; This is the critical advantage with stepping through code--it allows us to find bugs that we didn't even know could exist (and therefore didn't think to test for).&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;As a side note, this is why embedding function calls as parameters makes life harder.&amp;nbsp; When it comes time to step through code, the only way to verify the input is to evaluate the embedded function.&amp;nbsp; This will usually require stepping in or (particularly if no sources are available for the embedded code) stepping through some disassembly.&amp;nbsp; Storing the results of the embedded function in a variable and then passing that as a parameter is much easier to debug.&amp;nbsp; In retail builds the compiler will optimize the generated assembly anyway, so we don't gain anything by attempting to optimize higher level code.&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;In addition to checking inputs it is also important to verify proper loop behavior.&amp;nbsp; At a minimum, we should step through a few iterations of any loop to make sure our variables are behaving properly (such as loop counters).&amp;nbsp; We should also step through code executing at the boundaries of our loop; i.e. step through execution where we are at the maximum possible iteration minus one, the maximum possible iteration, and then verify that the loop exits.&amp;nbsp; &lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;Conditional break-points are very useful here because they allow us to do things like break on the 1000th iteration of a loop.&amp;nbsp; &lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;Conditionals are another place where we have to be extra diligent.&amp;nbsp; It is very important to verify both sides of a conditional.&amp;nbsp; This is something that is easy to miss when "if" conditions lack an "else".&amp;nbsp; In that case, I've found it helpful to add a breakpoint to the line following the conditional block and remove it when stepping through the case where the&amp;nbsp;conditional is not satisfied.&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;The bottom line with stepping through code is that the goal ist to prove that each line behaves as intended.&amp;nbsp; There is more to it than what I have mentioned above, but hopefully this will help get folks in the right mind-set to actually step through code in a meaningful way.&amp;nbsp; When done properly, stepping through code is the best defense against shipping bugs.&amp;nbsp; It is the only way to get the complete picture of how a given block of code actually behaves.&amp;nbsp; Stepping through code is not "old school" and it is not optional.&amp;nbsp;&amp;nbsp; &lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;Unit tests are great, but by themselves they are not sufficient to keep bugs at bay.&amp;nbsp; &lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="COLOR: #1f497d"&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&amp;nbsp;EDIT:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;I got an e-mail from a customer suggesting that the "Why" in the title should be changed to "When".&amp;nbsp; My decision to use "Why"&amp;nbsp;was based on my point that having unit tests doesn't excuse us from stepping through code.&amp;nbsp; However, I think the comment raises an important point.&amp;nbsp; That is, why do we write unit tests?&amp;nbsp; Those who claim that unit tests are a substitute for stepping through code are missing the boat on unit testing.&amp;nbsp; Unit tests are not about finding bugs, they are about preventing them.&amp;nbsp; A unit test is just an active form of an assertion.&amp;nbsp; Assertions verify design assumptions in code and fire an alert when those design assumptions are violated.&amp;nbsp; However, assertions are completely passive; they lie dormant until the code path in which they reside is actually executed.&amp;nbsp; The benefit of assertions is that they are not tied to any specific set of actions, so they will verify design assumptions regardless of how the code path was entered.&amp;nbsp; Assertions are powerful and should be used liberally.&amp;nbsp; However, we can also benefit from a more deterministic approach.&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt" mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;Unit tests fufill the same function of validating design assumptions, but they do so in an active way.&amp;nbsp; Unit tests actually force a code path to execute and verify the outcome.&amp;nbsp; So by incorporating unit tests into our code we are able to define how the code should behave and prove definitively that it behaves that way.&amp;nbsp; If changes are made that violate the design assumptions behind the code, a unit test will fail and alert us to the problem.&amp;nbsp; Because unit tests are active, we can guarantee that our design assumptions are actually validated--rather than hoping we do the right things to exercise our assertions.&amp;nbsp; So the primary reason for writing unit tests is to ensure that future changes to the code don't violate the original design assumptions and introduce new bugs.&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt" mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;One of the benefits of writing unit tests is that because we are thinking about testing, it forces us to consider scenarios that our code must handle that we might have ignored when we were writing it.&amp;nbsp; So unit tests can help us find bugs in our existing code, but that is a secondary function.&amp;nbsp; In fact, unit tests tend to give us a false sense of security--we think "hey, my code passes all of these tests, so it must be good."&amp;nbsp; But what unit tests don't address are the unknowns--those tests that we didn't know we needed.&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt" mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;So I'm going to leave my title the way it is, but also acknowledge that in reality unit testing and stepping through code have fundamentally different (yet complementary) purposes.&amp;nbsp; So in that sense, stepping through code only trumps unit testing when unit testing is being done for the wrong reasons.&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8831162" width="1" height="1"&gt;</content><author><name>geoffda</name><uri>http://blogs.msdn.com/members/geoffda.aspx</uri></author></entry><entry><title>Wither White-Box Testing?</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/geoffda/archive/2008/07/24/wither-white-box-testing.aspx" /><id>http://blogs.msdn.com/geoffda/archive/2008/07/24/wither-white-box-testing.aspx</id><published>2008-07-24T20:17:00Z</published><updated>2008-07-24T20:17:00Z</updated><content type="html">&lt;P&gt;I was recently reading JW's blog post on Prevention vs. Cure: &lt;A href="http://blogs.msdn.com/james_whittaker/archive/2008/07/24/prevention-v-cure-part-1.aspx"&gt;http://blogs.msdn.com/james_whittaker/archive/2008/07/24/prevention-v-cure-part-1.aspx&lt;/A&gt;&amp;nbsp;and it set me off a bit.&amp;nbsp; The blog post talks about "developer testing" which got me thinking about one of my biggest gripes: the "Software Design Engineer In Test" title.&amp;nbsp; I hate this title because it implies that someone who does test development has a different skill-set then someone who does product development.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;So what does this have to do with JW's blog?&amp;nbsp; Well, JW draws a distinction between "developer testing" and "tester testing" where developer testing involves things like design reviews, code reviews, unit testing, and presumably stepping through code.&amp;nbsp; In other words, its what we traditionally have referred to as "white box" or sometimes "glass box" testing.&amp;nbsp; White-box testing is extremely valuable.&amp;nbsp; Getting chest-deep in the code, crawling memory and operating systems primitives, using tools like perfmon and process explorer is a really good way to find nasty bugs.&amp;nbsp; Sometimes its the only way.&amp;nbsp; For example, consider threading bugs.&amp;nbsp; To borrow from a well-known gun slogan, IMO "Testers don't find threading bugs, customers do."&amp;nbsp; The variations in timing between thread execution are infinite and testing all cases is impossible.&amp;nbsp; You can't guarantee you don't have deadlock bugs through black-box testing--you have to analyze the code itself.&lt;/P&gt;
&lt;P&gt;So who is going to do this work?&amp;nbsp; There is no question that the buck stops with the developer who wrote the code.&amp;nbsp; Good developers have their designs and their code reviewed.&amp;nbsp; They write unit tests and step through their code.&amp;nbsp; In effect, they do white-box testing every day.&amp;nbsp; However, there are two problems with this.&amp;nbsp; The first is that developers are psychologically invested in their code.&amp;nbsp; No developer wants to think they write buggy code; it would mean they are a bad developer.&amp;nbsp; Therefore they have a vested interest in not finding bugs.&amp;nbsp; Which isn't to suggest most developers just close their eyes and check in.&amp;nbsp; Far from it--many developers are very dilligent and make every effort to test their code.&amp;nbsp; Still the psychological disadvantage cannot be ignored; its a bit akin to letting a student grade their own papers.&amp;nbsp; At the end of the day, developers have to believe their code is great and at some level, that prevents them from being able to take an unbiased view of their code.&lt;/P&gt;
&lt;P&gt;The second problem is that the degree to which the most well-intentioned developer is able to to test their code tends to be dictated by the schedule.&amp;nbsp; You can "Wide Band Delphi" all you want, but if reliable estimating were possible, my landscaping would have been done last week and the plumber would have been at my house on Tuesday instead of Friday.&amp;nbsp; Good developers try to provide good estimates and leave time for testing, but the reality is that estimating is almost impossible to get right--unless it happens to be something that you have done recently and repetitively (which isn't typically the case in a software project).&amp;nbsp; And even if you get your estimate right, other things interfere like customer issues and special projects which somehow never seem to affect ship dates.&amp;nbsp; So at the end of the day, developers do the level of testing that they have time to do which is&amp;nbsp;often severely curtailed&amp;nbsp;by the schedule.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Schedule pressures come into play when it comes to spec, design, and code reviews.&amp;nbsp; Everybody is under the gun to get their own stuff checked in, so who has time to review anybody else's work?&amp;nbsp; Sure, many orgs require code reviews prior to checking in, but usually what that means is two developers sitting in front of a machine where the author rapidly scrolls through their changes while the review sits and listens to the author explain how great their code is.&amp;nbsp; The whole thing is over in minutes.&amp;nbsp; A good code review should take a significant percentage of the time that it took to write the code in the first place and should be done without the author present, but that never happens because nobody has the time.&lt;/P&gt;
&lt;P&gt;Well, almost nobody.&amp;nbsp; SDETs (who should theoretically have the same skills as developers, or else what is the 'D' doing in their title) are paid to test, so and they should be fully capable of white-box testing.&amp;nbsp; By employing "test developers" in a dedicated capacity as white-box testers, the two core problems that limit the effectiveness of white-box testing are removed.&amp;nbsp; First, the fact that they are dedicated ensures that whatever time is available in the schedule will be solely dedicated to white-box testing.&amp;nbsp; Second, unleashing developers on someone else's code not only removes the psychological barriers to finding bugs, but it ties their ego to doing an extremely thorough review of the code.&amp;nbsp; In addition, when experienced people do white-box testing, it becomes a learning experience for the less experienced people.&lt;/P&gt;
&lt;P&gt;Which brings me back to why I hate the SDET title.&amp;nbsp; Development is development, product or otherwise.&amp;nbsp; Developers benefit from doing white-box testing as well as from having it done on their code.&amp;nbsp; Skilled developers may not wish to be forced to choose between product development or development-level testing.&amp;nbsp; Some developers should be forced to do some of&amp;nbsp;the latter for their own personal growth.&amp;nbsp; But as it stands, the SDET title gets in the way; if you want to be a dedicated tester, you must choose a different career path.&amp;nbsp; IMO, we should just have an SDE title where it is well understood that part of being a developer means spending time being solely dedicated to white-box testing.&lt;/P&gt;
&lt;P&gt;Finally, I should mention that none of this is meant to diminish the importance of black-box testing.&amp;nbsp; Its just that most organizations get black box testing right because it is what we tend to associate with traditional testing.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8769794" width="1" height="1"&gt;</content><author><name>geoffda</name><uri>http://blogs.msdn.com/members/geoffda.aspx</uri></author></entry><entry><title>The Three Laws of Software Development</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/geoffda/archive/2008/03/31/the-three-laws-of-software-development.aspx" /><id>http://blogs.msdn.com/geoffda/archive/2008/03/31/the-three-laws-of-software-development.aspx</id><published>2008-03-31T17:12:00Z</published><updated>2008-03-31T17:12:00Z</updated><content type="html">&lt;P&gt;If you haven't read &lt;U&gt;The Pragmatic Programmer&lt;/U&gt;, by Andrew Hunt and David Thomas, I'd highly recommend it.&amp;nbsp; It puts a fresh coat of paint on many of the concepts discussed in Steve Maguire's classic &lt;U&gt;Writing Solid Code&lt;/U&gt; and Steve McConnell's &lt;U&gt;CodeComplete&lt;/U&gt; (both of which are must reads for any software developer).&amp;nbsp; Anyway, one of the things I really like about Hunt and Thomas' book is that they distill software development into three fundamental laws.&amp;nbsp; A recent code review got me thinking about them again, so I decided that this might be a good time to revisit them in my blog.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;Law #1: Do Not Repeat Yourself (DRY)&lt;/P&gt;
&lt;P&gt;This law is very simple to understand: do not duplicate code, ever.&amp;nbsp; Duplicating code causes two problems.&amp;nbsp; The first is that if there is a bug in the duplicated code, it must be fixed in every copy of the code--which is time consuming.&amp;nbsp; The second problem is that in order to fix the bug in every copy of the code, the developer must be aware that the code has been duplicated in the first place.&amp;nbsp; This latter issue is a fairly serious one.&amp;nbsp; Although good developers will try to evaluate any bug in the context of the system and consider if there are other manifestations of the bug that could occur, there is no way to account for duplicate code.&amp;nbsp; Typically, when a bug is found in a piece of code that has been duplicated, the developer will fix the bug where it was found, but the fix will not be made to the other areas of duplication.&amp;nbsp; This is often the case even when the developer who caused the duplication in the first place is fixing the bug; he or she will often not remember that the duplication exists and needs to be addressed.&amp;nbsp; The end result is that the same fundamental bug may turn up several times more in different contexts--in each case wasting time and effort to investigate the issue and redevelop the same fix.&lt;/P&gt;
&lt;P&gt;The solution to this problem is to simply avoid it.&amp;nbsp; However, in order to do that, we must recognize when we are duplicating in the first place.&amp;nbsp; Some forms of duplication are easier to recognize than others.&amp;nbsp; Hunt and Thomas mention several types of duplication:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Imposed duplication--where duplication occurs intentionally because developers don't think they have a choice.&lt;/LI&gt;
&lt;LI&gt;Inadvertant duplication--where duplication happens without developers realizing it.&lt;/LI&gt;
&lt;LI&gt;Impatient duplication--where developers duplicate code because it is the easiest thing to do.&lt;/LI&gt;
&lt;LI&gt;Interdeveloper duplication--where duplication occurs because two different developers are writing the same code without being aware of what the other is doing.&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;The first three forms of duplication are what I'm&amp;nbsp;interested in.&amp;nbsp; The bottom line with all of them is to quit using copy and paste when you are writing code.&amp;nbsp; Seriously.&amp;nbsp; If you find yourself&amp;nbsp;copying and pasting code, stop and ask yourself what you are doing and&amp;nbsp;whether you are introducing duplication into your code.&amp;nbsp;&amp;nbsp;If&amp;nbsp;it turns out the answer to the question is yes, rewrite your code to remove the duplication.&lt;/P&gt;
&lt;P&gt;As it turns out, some duplication is easier to detect and remove than others.&amp;nbsp; I was recently reviewing some code where the author was generating file names and directory names by appending a number to the base name.&amp;nbsp; The logic was to simply create a name by appending a number and check if the name existed on disk.&amp;nbsp; If it did, the number was incremented and the name tested again.&amp;nbsp; The first available name was used.&amp;nbsp; The problem that the developer faced was that they were dealing with both directory and file names.&amp;nbsp; So they needed to use File.Exists in one case and Directory.Exists in the other.&amp;nbsp; As a result, they created two different methods which differed only by the mechanism used to check the existence of a filename.&amp;nbsp; Although the two methods were not strictly identical, the key problem is that the algorithms were duplicated.&amp;nbsp; If a bug were to turn up in the algorithm, it would need to get fixed in multiple places.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;In this case, the duplication was intentional--the developer was aware of it, but didn't see how to avoid it.&amp;nbsp; As it turns out, the solution is fairly simple.&amp;nbsp; The duplication could be avoided by simply passing the name checking function as a delegate to a single method that implemented the algorithm.&amp;nbsp; If necessary, top level methods could be created to call the implementation method with the correct context.&amp;nbsp; In any event, intentional duplication is almost never acceptable.&amp;nbsp; If you find yourself in a position where you feel you cannot avoid duplication, get help.&amp;nbsp; Keep pulling people in until a solution is reached that doesn't involve duplication.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;The only places where duplication is (in my opinion) at least tolerable is in generated code and in test cases.&amp;nbsp; In the former, duplication is acceptable because generated code is never modified directly.&amp;nbsp; If there is a bug in generated code, it must be fixed in the generator itself.&amp;nbsp; So as long as the generator itself contains no duplication, the bug can be fixed in one place and then the corrected code can be regenerated.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;I believe duplication in test cases is acceptable for a few reasons.&amp;nbsp; First, and foremost, attempting to eliminate duplication in test cases can make it difficult to understand what the test case does.&amp;nbsp; To me, this somewhat trumps maintenance.&amp;nbsp; More importantly, test cases generally are not (and should not be) heavy-weight.&amp;nbsp; They generally do not contain much logic, so typically they will not contain bugs and will generally be easy to refactor.&amp;nbsp; Even if readability weren't an issue, at the end of the day I'm still willing to risk some extra maintenance cost simply so I don't have to spend time trying to figure out how to avoid duplication.&amp;nbsp; To me, rapid development of test cases also trumps maintenance.&amp;nbsp; Again, though, test cases should be extremely light weight.&amp;nbsp; I'm a firm believer in enforcing the law of DRY in the framework that the test cases are built on.&lt;/P&gt;
&lt;P mce_keep="true"&gt;Law #2: Preserve Orthogonality&lt;/P&gt;
&lt;P mce_keep="true"&gt;Two things are orthogonal if they are independent of one another.&amp;nbsp; Essentially, the Hunt and Thomas' concept of orthogonality is just a new-school way of talking about the concepts of cohesion and to a lesser extent, coupling.&amp;nbsp; The bottom line here is that we should always strive to limit methods, classes, and modules to a singular purpose as well as limit the dependencies between pieces.&amp;nbsp; If you need more of a refresher on these topics, Wikipedia has a good overview on cohesion here: &lt;A href="http://en.wikipedia.org/wiki/Cohesion_%28computer_science%29" mce_href="http://en.wikipedia.org/wiki/Cohesion_%28computer_science%29"&gt;http://en.wikipedia.org/wiki/Cohesion_%28computer_science%29&lt;/A&gt;&amp;nbsp;and coupling here: &lt;A href="http://en.wikipedia.org/wiki/Coupling_%28computer_science%29" mce_href="http://en.wikipedia.org/wiki/Coupling_%28computer_science%29"&gt;http://en.wikipedia.org/wiki/Coupling_%28computer_science%29&lt;/A&gt;.&lt;/P&gt;
&lt;P mce_keep="true"&gt;The idea behind preservation of orthogonality is to make maintenance simpler.&amp;nbsp; As long as two methods or classes are orthogonal, there is no chance that a change in one could affect the other.&amp;nbsp; In other words, it is less risky to make changes when orthogonality is preserved since unintended side-effects are less likely.&lt;/P&gt;
&lt;P mce_keep="true"&gt;While Hunt and Thomas address many aspects of preservation of orthogonality, I want to touch on one aspect that they don't.&amp;nbsp; Namely, that if statements are often an indication that orthogonality is breaking down.&amp;nbsp; While there are certainly many valid uses for if statements, I personally tend to treat them with a great deal of skepticism because they generally have the effect of reducing cohesion.&amp;nbsp; One warning sign that an if statement is not a good approach is when you see the same conditional spread throughout a class or method.&amp;nbsp; For example, I was recently looking at a class that was responsible for automating the creation of project items in Visual Studio.&amp;nbsp; One of the methods in the class consisted of a switch statement, which depending on whether the project item was created new or added from an existing object would do the right thing to create the project item.&amp;nbsp; As written, the method would create the project items via automating the user interface.&amp;nbsp; However, a change was being made to allow project items to be created via object model automation. The proposed fix was to simply add an if statement at every level of the switch statement that would use the object model if the caller required it, otherwise it would fall through to the original implementation.&lt;/P&gt;
&lt;P mce_keep="true"&gt;There are a couple of problems with this approach.&amp;nbsp; The first is just the duplication of the if statement.&amp;nbsp; Remember DRY?&amp;nbsp; If the conditional expression needed to change (or there was a bug in it), every instance of the expression would have to get fixed.&amp;nbsp; This problem could be mitigated by abstracting the conditional into its own function; i.e. if (UseObjectModelAutomation).&amp;nbsp; &lt;/P&gt;
&lt;P mce_keep="true"&gt;The second problem is that code can be added both at the top and the bottom of the method outside of the conditional expression.&amp;nbsp; This makes it easy to introduce bugs.&amp;nbsp; If someone isn't aware (or thinking about) the fact that there are two code paths here, they could easily add code that either depends on one or the other, or interferes with one or the other.&amp;nbsp; Its a common scenario; the developer gets a bug that says if I do A then B is broken.&amp;nbsp; The developer fixes the bug (and tests the fix), but never realizes they have broken C.&amp;nbsp; And their response will invariably be, "I didn't realize I had to test C".&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;There are some mitigations here.&amp;nbsp; The first&amp;nbsp;would be to ensure the conditional can't be missed.&amp;nbsp;&amp;nbsp;That means using&amp;nbsp;else clauses and making sure all code within the method is within the if or the else.&amp;nbsp;&amp;nbsp;While doing this&amp;nbsp;can't prevent the above scenario, it will make it a whole lot less likely.&amp;nbsp; In this case though, the problem is the&amp;nbsp;switch statement.&amp;nbsp; The only way to have a clean if-else statement would be to duplicate the switch--which really isn't&amp;nbsp;a good option (that DRY thing again).&lt;/P&gt;
&lt;P mce_keep="true"&gt;As it turns out, there is a better option: use an object-oriented solution.&amp;nbsp; The trick is to&amp;nbsp;refactor the project item class.&amp;nbsp; Start by extracting a class that contains all of the project item creation code&amp;nbsp;from the switch statement (which may require&amp;nbsp;doing an extract method first).&amp;nbsp; &amp;nbsp;This gives you a class that knows how to create project items using UI automation.&amp;nbsp; Then create a second class that contains the implementation for creating project items via object model automation.&amp;nbsp; Depending on what you are trying to accomplish, this class can either derive from the UI Automation project item creator class, or it can derive from a base class that is common to both classes.&amp;nbsp; In any event, the original method implementation would simply spin up the correct project item creator as an instance of the base type.&amp;nbsp; The switch statement would call the appropriate method via the interface defined by the common base class.&amp;nbsp; In other words, the details of which automation mechanism will be used are no longer known to the switch statement.&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;The other thing I look for with if statements, is just the size of the enclosed block.&amp;nbsp; Generally, if it is anything more than a function call, I'll look for an opportunity to do an extract method.&amp;nbsp; This isn't really an orthogonality issue per se, but it helps keep things readable and helps cut down on duplication.&lt;/P&gt;
&lt;P mce_keep="true"&gt;Law #3:&amp;nbsp; All Decisions Must Be Reversible&lt;/P&gt;
&lt;P mce_keep="true"&gt;Reversibility is simply the ability to easily undo any decision you make.&amp;nbsp; What Hunt and Thomas are really talking about here are the larger decisions that comprise architecture.&amp;nbsp; Software must be able to adapt to change.&amp;nbsp; For example, the database system in use today, may be suplanted by a better faster version from a different vendor.&amp;nbsp; Can your system easily handle the switch?&amp;nbsp; If not, it will probably be discarded.&amp;nbsp; &lt;/P&gt;
&lt;P mce_keep="true"&gt;One of the best ways to ensure reversibillity is to build a good abstraction layer around all dependencies.&amp;nbsp; Doing this ensures that not only could you change your dependencies at the drop of a hat, but it also makes it easier to handle the case when your dependencies break you.&amp;nbsp; With an abstraction layer in place, all changes are local to the layer.&amp;nbsp; There is no need to search your code base for all of the places where code uses the dependency.&amp;nbsp; In addition, the layer defines your requirements.&amp;nbsp; The layer's interface tells you exactly what you would need from any potential replacement technology in order to make a switch.&lt;/P&gt;
&lt;P mce_keep="true"&gt;I was guilty of violating this rule recently.&amp;nbsp; I was trying to overhaul our system of running tests which was extremely complicated and and involved several diferent files.&amp;nbsp; I wasn't fond of the test harness we were using, but since I was told we were committed to it, I figured, "why not leverage it?"&amp;nbsp; Since that test harness required a data file of its own, I decided that things could be greatly simplfiied if we just moved all of our test data into that file--which we had to use anyway.&amp;nbsp; That approach certainly streamlined our system, but at the cost of reversibility.&amp;nbsp; Recently, our testing requirements changed and it turned out we need to use a different harness.&amp;nbsp; Unfortunately, my decision to move our test data into the original harness's file left us with no simple way to get our data into the new harness.&amp;nbsp; The ironic thing about this is that I had taken great pains to ensure that our system would allow you to easily swap in whatever test harness you needed.&amp;nbsp; I just got blinded by the other problem I was trying to solve and didn't think about how my solution would impact reversibility.&lt;/P&gt;
&lt;P mce_keep="true"&gt;Well, there you have it--software development distilled into three simple laws.&amp;nbsp; As complex as software development is, I'm a big believer in these laws.&amp;nbsp; Think about them when you are writing and/or reviewing code and they will help make you a better developer.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8345696" width="1" height="1"&gt;</content><author><name>geoffda</name><uri>http://blogs.msdn.com/members/geoffda.aspx</uri></author></entry><entry><title>Using a Custom Proxy For Interception</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/geoffda/archive/2007/11/07/using-a-custom-proxy-for-interception.aspx" /><id>http://blogs.msdn.com/geoffda/archive/2007/11/07/using-a-custom-proxy-for-interception.aspx</id><published>2007-11-08T01:21:00Z</published><updated>2007-11-08T01:21:00Z</updated><content type="html">&lt;P&gt;I've been working on a system that required intercepting the method calls in an object model; basically I was trying to use Aspect Oriented Programming to weave in some logging and verification code.&amp;nbsp; My first attempt was to simply add an object sink to a ContextBoundObject as discussed in the following msdn article: &lt;A href="http://msdn.microsoft.com/msdnmag/issues/03/03/ContextsinNET/default.aspx" mce_href="http://msdn.microsoft.com/msdnmag/issues/03/03/ContextsinNET/default.aspx"&gt;http://msdn.microsoft.com/msdnmag/issues/03/03/ContextsinNET/default.aspx&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;While this was relatively simple to implement, I quickly ran into a problem: I needed to reference the context bound objects from a client in a different context.&amp;nbsp; Unfortunately, the context bound objects contained private fields (which cannot be marshalled across context boundaries).&amp;nbsp; As a result, I would get exceptions in the transparent proxy whenever I tried to reference these objects from a client in a different context.&lt;/P&gt;
&lt;P&gt;The solution to this seemed to be clear.&amp;nbsp; The only reason for proxying objects at all was for interception, so I could solve my problem by handing out the real objects (instead of a proxy) from the interceptor.&amp;nbsp; However, as I started digging into this, it appeared that obtaining the real object might be a problem.&amp;nbsp; While I could get the RealProxy, I couldn't figure out how to get at the underlying object.&amp;nbsp; RealProxy.GetUnwrappedServer was clearly the method I needed to call, but it was protected.&amp;nbsp; So it became clear that the solution would be to implement a custom proxy.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;The problem with that is that there is&amp;nbsp;a dearth of documentation on the subject.&amp;nbsp; Aside from the following short article (&lt;A href="http://msdn2.microsoft.com/en-us/library/scx1w94y(VS.71).aspx" mce_href="http://msdn2.microsoft.com/en-us/library/scx1w94y(VS.71).aspx"&gt;http://msdn2.microsoft.com/en-us/library/scx1w94y(VS.71).aspx&lt;/A&gt;)&amp;nbsp;and a host of samples that don't demonstrate how to actually proxy a call to the real object, MSDN had very little to say on the subject.&amp;nbsp; A few folks have blogged about this but the few examples out there were all explicitly creating the proxy.&amp;nbsp; I wanted my interception to be completely seamless such that object activation would return a proxy.&amp;nbsp; While it was clear that ProxyAttribute would allow this, there were no examples of how use the ProxyAttribute to do anything more than intercept activation.&amp;nbsp; So, in this blog post, I'm going to put the pieces together and demonstrate how to implement a completely transparent (no pun intended) interception system using a custom proxy.&lt;/P&gt;
&lt;P&gt;The first step is to create the custom proxy itself.&amp;nbsp; The custom proxy is simply a class that derives from RealProxy.&amp;nbsp;&amp;nbsp;The class should provide an implementation of the parameterized constructor which does nothing more than delegate to the base.&amp;nbsp; The interesting code is in the Invoke method (which is the only required override).&amp;nbsp; Here is an implementation: &lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;class&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;InterceptorProxy&lt;/SPAN&gt; : &lt;SPAN style="COLOR: #2b91af"&gt;RealProxy&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;private&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;MarshalByRefObject&lt;/SPAN&gt; m_realObject;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; InterceptorProxy()&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; InterceptorProxy(&lt;SPAN style="COLOR: #2b91af"&gt;Type&lt;/SPAN&gt; classToProxy) : &lt;SPAN style="COLOR: blue"&gt;base&lt;/SPAN&gt;(classToProxy)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;override&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;IMessage&lt;/SPAN&gt; Invoke(&lt;SPAN style="COLOR: #2b91af"&gt;IMessage&lt;/SPAN&gt; msg)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: #2b91af"&gt;IMessage&lt;/SPAN&gt; returnMessage = &lt;SPAN style="COLOR: blue"&gt;null&lt;/SPAN&gt;;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;if&lt;/SPAN&gt; (msg &lt;SPAN style="COLOR: blue"&gt;is&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;IConstructionCallMessage&lt;/SPAN&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: #2b91af"&gt;IConstructionCallMessage&lt;/SPAN&gt; ctorCallMessage = msg &lt;SPAN style="COLOR: blue"&gt;as&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;IConstructionCallMessage&lt;/SPAN&gt;;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;returnMessage = InitializeServerObject(ctorCallMessage);&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;m_realObject = GetUnwrappedServer();&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SetStubData(&lt;SPAN style="COLOR: blue"&gt;this&lt;/SPAN&gt;, m_realObject);&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;else&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;if&lt;/SPAN&gt; (msg &lt;SPAN style="COLOR: blue"&gt;is&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;IMethodCallMessage&lt;/SPAN&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: #2b91af"&gt;IMethodCallMessage&lt;/SPAN&gt; methodCallMessage = msg &lt;SPAN style="COLOR: blue"&gt;as&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;IMethodCallMessage&lt;/SPAN&gt;;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Preprocess(methodCallMessage);&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: #2b91af"&gt;IMethodReturnMessage&lt;/SPAN&gt; rawReturnMessage = &lt;SPAN style="COLOR: #2b91af"&gt;RemotingServices&lt;/SPAN&gt;.ExecuteMessage(m_realObject, methodCallMessage);&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;returnMessage = PostProcess(methodCallMessage, rawReturnMessage);&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;else&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;throw&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;new&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;NotSupportedException&lt;/SPAN&gt;();&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;return&lt;/SPAN&gt; returnMessage;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 10pt"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;/SPAN&gt;&lt;/P&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;private&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;void&lt;/SPAN&gt; Preprocess(&lt;SPAN style="COLOR: #2b91af"&gt;IMessage&lt;/SPAN&gt; msg)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: green; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;// Do interception work here.&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;private&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;IMessage&lt;/SPAN&gt; PostProcess(&lt;SPAN style="COLOR: #2b91af"&gt;IMessage&lt;/SPAN&gt; msg, &lt;SPAN style="COLOR: #2b91af"&gt;IMessage&lt;/SPAN&gt; msgReturn)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 10pt"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 10pt"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: green"&gt;// Do interception work here.&lt;/SPAN&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 10pt"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 10pt; TEXT-INDENT: 0.5in"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;}&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=2&gt;There are two pieces to the Invoke implementation.&amp;nbsp; The first is how the IConstructionCall message is handled--we'll go through each call as it happens.&amp;nbsp; The first call is to RealProxy.InitializeServerObject.&amp;nbsp; This call simply instantiates the real object that we are proxying.&amp;nbsp; The next call is to RealProxy.GetUnwrappedServer.&amp;nbsp; This call actually returns a reference to the object that we just created.&amp;nbsp; We need this so we can marshal calls (among other things).&amp;nbsp; Finally, we call RealProxy.SetStubData.&amp;nbsp; This last call performs the magic that ensures that calls to the Transparent Proxy are routed to the RealProxy.&amp;nbsp; If SetStubData is not called, no further messages will be routed to the RealProxy.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=2&gt;The second piece of the Invoke implementation deals with handling IMethodCallMessages.&amp;nbsp; Assuming you hooked up your proxy correctly, you will get IMethodCallMessages whenever a method is invoked on your proxied object.&amp;nbsp; This implementation provides hooks before and after the method invocation to allow whatever pre and postprocessing you might require.&amp;nbsp; In my case I do some logging and verification actions (not shown).&amp;nbsp; The interesting call is RemotingServices.ExecuteMessage.&amp;nbsp; This call performs the execution of a message on&amp;nbsp;the real object.&amp;nbsp; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=2&gt;In this case, we simply delegate what we were passed in since we aren't interested in modifying the message.&amp;nbsp; However, if we needed to, we could provide our own message in lieu of the one we received to do anything we liked.&amp;nbsp; For example, we could create a custom proxy implementation that would provide a thunking layer to allow existing code to call what would otherwise be an incompatible API.&amp;nbsp; Another thing to point out is that Invoke's return IMessage can also be modified.&amp;nbsp; For example, the interceptor can eat exceptions by simply replacing the IMethodReturnMessage it receives from the method call with one that does not contain any exceptions.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=2&gt;&amp;nbsp;Now that we've got our custom proxy created, we need to hook into activation.&amp;nbsp; What we want to accomplish is to ensure that when we "new" up our object we always get a transparent proxy instead.&amp;nbsp; In other words, we want to make sure that our interceptor is installed automatically and without any special code required.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;To do this, we need to create a ProxyAttribute.&amp;nbsp; It looks like this:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[&lt;SPAN style="COLOR: #2b91af"&gt;AttributeUsage&lt;/SPAN&gt;(&lt;SPAN style="COLOR: #2b91af"&gt;AttributeTargets&lt;/SPAN&gt;.Class)]&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[&lt;SPAN style="COLOR: #2b91af"&gt;SecurityPermissionAttribute&lt;/SPAN&gt;(&lt;SPAN style="COLOR: #2b91af"&gt;SecurityAction&lt;/SPAN&gt;.Demand, Flags = &lt;SPAN style="COLOR: #2b91af"&gt;SecurityPermissionFlag&lt;/SPAN&gt;.Infrastructure)]&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;class&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;InterceptorProxyAttribute&lt;/SPAN&gt; : &lt;SPAN style="COLOR: #2b91af"&gt;ProxyAttribute&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; InterceptorProxyAttribute()&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;public&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;override&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;MarshalByRefObject&lt;/SPAN&gt; CreateInstance(&lt;SPAN style="COLOR: #2b91af"&gt;Type&lt;/SPAN&gt; serverType)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;{&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: #2b91af"&gt;RealProxy&lt;/SPAN&gt; proxy = &lt;SPAN style="COLOR: blue"&gt;new&lt;/SPAN&gt; &lt;SPAN style="COLOR: #2b91af"&gt;InterceptorProxy&lt;/SPAN&gt;(serverType);&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: #2b91af"&gt;MarshalByRefObject&lt;/SPAN&gt; transparentProxy = (&lt;SPAN style="COLOR: #2b91af"&gt;MarshalByRefObject&lt;/SPAN&gt;)proxy.GetTransparentProxy();&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;return&lt;/SPAN&gt; transparentProxy;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; LINE-HEIGHT: normal; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 10pt"&gt;&lt;SPAN style="FONT-SIZE: 10pt; LINE-HEIGHT: 115%; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;}&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=2&gt;The interesting method is the only override we need: ProxyAttribute.CreateInstance.&amp;nbsp; CreateInstance gets called during non-remote activation.&amp;nbsp; Normally, in that case you would get the real object back.&amp;nbsp; However, we want to return a TransparentProxy that delegates to our custom interceptor proxy.&amp;nbsp; So instead of activating our real object, we activate our InterceptorProxy.&amp;nbsp; We then call RealProxy.GetTransparentProxy to provide a TransparentProxy that gets returned in lieu of the real object.&amp;nbsp; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=2&gt;Since CreateInstance doesn't actually create the real object, you might wonder where that happens.&amp;nbsp; If you recall, the actual object gets created when RealProxy.Invoke handles the IConstructionCallMessage.&amp;nbsp; RealProxy.InitializeServerObject instantiates the real object that is being backed by the proxy.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=2&gt;That's it.&amp;nbsp; The only remaining step is to apply the InterceptorProxyAttribute to any class which we want to set up interception on.&amp;nbsp; Once this is done, activation of that class will result in our interceptor being automatically installed.&amp;nbsp; Note that in order for this technique to work, the class must derive from ContextBoundObject--otherwise it cannot be proxied.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=2&gt;As a final note, the following blog entry&amp;nbsp;from Chris Brumme provides some good background on proxies and contexts: &lt;A href="http://blogs.msdn.com/cbrumme/archive/2003/07/14/51495.aspx"&gt;http://blogs.msdn.com/cbrumme/archive/2003/07/14/51495.aspx&lt;/A&gt;&lt;/P&gt;&lt;/FONT&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=5971788" width="1" height="1"&gt;</content><author><name>geoffda</name><uri>http://blogs.msdn.com/members/geoffda.aspx</uri></author></entry><entry><title>Why UI Automation Is Not All That And A Bag Of Chips</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/geoffda/archive/2007/09/21/why-ui-automation-is-not-all-that-and-a-bag-of-chips.aspx" /><id>http://blogs.msdn.com/geoffda/archive/2007/09/21/why-ui-automation-is-not-all-that-and-a-bag-of-chips.aspx</id><published>2007-09-22T02:00:00Z</published><updated>2007-09-22T02:00:00Z</updated><content type="html">&lt;P&gt;I spent some time today looking&amp;nbsp;at an issue&amp;nbsp;on a Korean system where a test was searching for a specific item in a combo box.&amp;nbsp; The test would iteratively select each item in the combo box, examine it and continue looping until it found the right entry.&amp;nbsp; The only problem was, this particular test was failing.&amp;nbsp; Oddly enough, when I watched the test run, I could see it selecting the right item in the combo box.&amp;nbsp; Upon debugging, I found that even though the right item was selected, the call to get the selected item was returning the text of the previously selected item.&amp;nbsp; So what was going on?&lt;/P&gt;
&lt;P&gt;It turns out that the test was interacting with the combo box by sending key strokes.&amp;nbsp; At first glance, this might seem like a good approach--after all that is what users are going to do, right?&amp;nbsp; The problem is that there is a fundamental difference between users and test automation.&amp;nbsp; Users can take advantage of visual feedback to know when the system has actually processed their input.&amp;nbsp; Automation cannot do this.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;The authors of the SendKeys code clearly realized this and they attempted to ascertain when the input had actually been processed.&amp;nbsp; Their strategy was to make a SendMessageCallback call immediately after sending the keystrokes.&amp;nbsp; In case you are not familiar with it, the behavior of SendMessageCallback is to call the WndProc of the specified window and then return immediately.&amp;nbsp; After the WndProc handles the message, the specified callback function is called and the results are passed back.&amp;nbsp; At first glance, this seems like a sound strategy.&amp;nbsp; Since the call is cross-process (and therefore cross-thread), the end result of the SendMessageCallback call is that a message is placed in the thread's message queue after the keystroke messages.&amp;nbsp; Since messages are processed serially, in order for the callback to be called, the keystroke processing must have completed, right?&amp;nbsp; Wrong.&amp;nbsp; The oversight here is re-entrancy.&amp;nbsp; If the key stroke handling code itself pumps messages, then the callback message will be completely processed before the key stroke handling has completed.&amp;nbsp; In other words, the callback message gets processed in the middle of the key stroke handling.&amp;nbsp; This is what was happening with the bug I was investigating; the code to get the currently selected item in the combo box was getting called before the combo box had a chance to update the selection.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;The other very serious problem with this mechanism is that the SendKeys implementation uses the Win32 SendInput function.&amp;nbsp; All SendInput does is place input primitives on the system event queue for processing by the Raw Input Thread.&amp;nbsp; Once it has done that, it immediately returns.&amp;nbsp; On single-procecessor machines this will work since the RIT will be running with higher priority and context will switch.&amp;nbsp; On multi-processor machines, however, there is the possibility that the scheduling will work out such that the RIT ends up running concurrently on another processor and no context switch away from the SendKeys thread occurs.&amp;nbsp; In that case, it is possible for the SendMessageCallback to be executed prior to the RIT posting the keyboard message to the application thread's queue.&amp;nbsp; In that case, the result will be the same as the re-entrancy scenario; SendKeys will return prior to the application's keystroke handler actually executing.&lt;/P&gt;
&lt;P&gt;This very issue is the fundamental problem with all test code that attempts to automate user input using keyboard and mouse inputs.&amp;nbsp; While it is often trivial to generate the inputs, it is very difficult to reliably determine when the input has actually been completely processed.&amp;nbsp; It is even more difficult to do this in a generic fashion (such as what SendKeys was attempting).&amp;nbsp; The end result is that ui automation tends to be flaky because it so timing dependent.&amp;nbsp; Inevitably, the inherent timing problems lead to a proliferation of Sleep statements strewn through out test cases and the automation will gradually get slower and slower over time.&lt;/P&gt;
&lt;P&gt;In addition to the timing problems,&amp;nbsp;UI automation turns into a nightmare when localized builds of the product must be tested.&amp;nbsp; The localized versions of all of those UI strings must somehow be located if you want to test a different language.&amp;nbsp; Typically, this will involve grovelling the binaries you are testing for string resources and trying to map resource identifiers to strings.&amp;nbsp; The problem gets more difficult if you have duplicate strings.&amp;nbsp; For example, you might be several "OK" strings that differ only by context.&amp;nbsp; Unfortunately, it turns out that these strings are only duplicated in English.&amp;nbsp; In other languages, the context results in completely different strings.&amp;nbsp; So if you are working with English, you cannot assume that the first OK you find is necessarily the right string.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;So all of these issues beg the question: why are we trying to automate UI at all?&amp;nbsp; The typical answer is "well we have to test what our users will actually do."&amp;nbsp; However, I would argue that such a view is overly simplistic.&amp;nbsp; For example, let's take the case of the combo box.&amp;nbsp; The combo box is a self-contained Win32 control.&amp;nbsp; User input has already been tested by the people that developed the control.&amp;nbsp; The combo box is just a state machine.&amp;nbsp; There is no question what will happen with respect to any of the inputs a user can throw at a combo box.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;The part that has not been tested is how the application using the combo box will react to the state changes.&amp;nbsp; In fact, the code we are responsible for testing is completely independent of the combo box UI.&amp;nbsp; All we need to do to fully test it is put the combo box into a state and/or cause the combo box to generate the events that we might be listening to.&amp;nbsp; We can do this by bypassing the user interface and coding directly against the combo box API.&amp;nbsp; To put it another way, we can theoretically achieve&amp;nbsp;100 percent code coverage without ever automating the UI.&amp;nbsp; Assuming that to be true, how could it be argued that we have not met our burden as testers?&lt;/P&gt;
&lt;P&gt;Certainly, there are those who would cry "but what if the combo box is broken and clicks don't do what they are supposed to do?"&amp;nbsp; While that may be true, the reality is that nobody relies solely on automation to ship a product.&amp;nbsp; The rare bug that is actually truly a UI issue will get discovered through manual testing.&lt;/P&gt;
&lt;P&gt;The other thing to consider is what we are trying to accomplish through automation.&amp;nbsp; We automate tests because we expect to have to execute them many times before the product ships.&amp;nbsp; Why is this?&amp;nbsp; So we can ensure that changes to the product do not errode its quality--that is, we use automation to catch regressions.&amp;nbsp; So in the context of UI, how often do we really expect regressions in UI behavior?&amp;nbsp; The answer is, not often.&amp;nbsp; Moreover, such bugs would generally be simple and low risk fixes; i.e. adding some event hookup code that was incorrectly removed.&amp;nbsp; These are the kind of fixes that can be applied as a patch to a daily build if they do actually happen.&lt;/P&gt;
&lt;P&gt;At the end&amp;nbsp;of the day, testing&amp;nbsp;is about trade-offs.&amp;nbsp;&amp;nbsp;This is particularly true with respect to automation.&amp;nbsp; We&amp;nbsp;simply cannot automate everything, so we must make smart choices about where&amp;nbsp;choose to invest our resources.&amp;nbsp; In my mind, the costs of UI automation are&amp;nbsp;tremendous, yet the benefits&amp;nbsp;are marginal when compared to approaches that enable testing immediately below the UI layer.&amp;nbsp;&amp;nbsp;The amount of time spent developing, maintaining, and most importantly, analyzing failed test cases is far greater than the time it would take just test the UI manually.&amp;nbsp; In the face of such costs and difficulties, it would seem that the only time full UI automation may be appropriate when the team is directly responsible for testing UI primitives that they have developed.&amp;nbsp; Even then, it may still be cheaper to manually test the keyboard and mouse interaction and restrict automation to more direct calls.&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=5042982" width="1" height="1"&gt;</content><author><name>geoffda</name><uri>http://blogs.msdn.com/members/geoffda.aspx</uri></author></entry><entry><title>The Designer Process That Would Not Terminate (Part 2)</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/geoffda/archive/2007/09/07/the-designer-process-that-would-not-terminate-part-2.aspx" /><id>http://blogs.msdn.com/geoffda/archive/2007/09/07/the-designer-process-that-would-not-terminate-part-2.aspx</id><published>2007-09-07T17:43:00Z</published><updated>2007-09-07T17:43:00Z</updated><content type="html">&lt;P&gt;In my previous post (&lt;A href="http://blogs.msdn.com/geoffda/archive/2007/08/31/the-designer-process-that-would-not-terminate.aspx" mce_href="http://blogs.msdn.com/geoffda/archive/2007/08/31/the-designer-process-that-would-not-terminate.aspx"&gt;http://blogs.msdn.com/geoffda/archive/2007/08/31/the-designer-process-that-would-not-terminate.aspx&lt;/A&gt;) I talked about an issue&amp;nbsp;where RCWs (Runtime Callable Wrappers)&amp;nbsp;awaiting garbage collection were holding references to a COM object and preventing it from being deterministically shut down.&amp;nbsp; In this post, we'll continue the discussion--this time focusing on how to use the debugger to track down such a problem.&lt;/P&gt;
&lt;P&gt;To facilitate this blog entry, I started to code up a simple example of the problem.&amp;nbsp; What I discovered is that the insidious nature of RCW leaks can be reproduced in code that is even simpler than I had originally intended to write.&amp;nbsp; Hopefully, this example will hammer home just how careful you have to be when using RCWs.&lt;/P&gt;
&lt;P&gt;using System;&lt;BR&gt;using System.Collections.Generic;&lt;BR&gt;using System.Text;&lt;BR&gt;using Microsoft.Office.Interop.Excel;&lt;BR&gt;using System.Runtime.InteropServices;&lt;/P&gt;
&lt;P&gt;namespace RCWLeak&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; class Program&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; static void Main(string[] args)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ApplicationClass excel = new ApplicationClass();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Workbook workbook = excel.Workbooks.Add(Type.Missing);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Marshal.FinalReleaseComObject(workbook);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; excel.Quit();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Marshal.FinalReleaseComObject(excel);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Console.ReadKey();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;So here is a very simple routine that does nothing more than start Excel, add a workbook, and then shut down.&amp;nbsp; It looks correct right?&amp;nbsp; Unfortunately it is not.&amp;nbsp; When we run this code, execution pauses at the end and waits for input.&amp;nbsp; What we can see by viewing task manager (or tasklist) that Excel is still running despite the fact that we thought we had taken all of the necessary steps to ensure a clean shut down.&lt;/P&gt;
&lt;P mce_keep="true"&gt;Clearly there is an RCW that is still holding a reference to Excel somewhere, but it is non-obvious from looking at the code.&amp;nbsp; To get to the bottom of this we'll need to take a look at the managed heap.&amp;nbsp; Fortunately, we can do this with the SOS ("Son Of Strike") debugging extension.&amp;nbsp; If you have installed WinDBG, you already have SOS.&amp;nbsp; If you haven't installed, WinDBG, you can get it free from Microsoft here: &lt;A href="http://www.microsoft.com/whdc/devtools/debugging/default.mspx" mce_href="http://www.microsoft.com/whdc/devtools/debugging/default.mspx"&gt;http://www.microsoft.com/whdc/devtools/debugging/default.mspx&lt;/A&gt;.&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;If you are familiar with WinDBG, you are probably aware that it has an open interface that allows for the development of custom, pluggable debugging tools to extend the base debugging functionality.&amp;nbsp; As I mentioned, SOS is one such extension.&amp;nbsp; To load SOS in WinDBG,&amp;nbsp;you would&amp;nbsp;type the following in the command window:&lt;/P&gt;
&lt;P&gt;.loadby sos mscorwks&lt;/P&gt;
&lt;P&gt;Once SOS is loaded, you can see what it does by typing "!help".&amp;nbsp; To get help for individual commands, you can simply type "!help &amp;lt;command&amp;gt;".&lt;/P&gt;
&lt;P&gt;What we want to do is figure out what is holding on to Excel.&amp;nbsp; To start with, we can find out all of the objects on the managed heap by using the !dumpheap command.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;0:005&amp;gt; !dumpheap -stat&lt;BR&gt;total 6901 objects&lt;BR&gt;Statistics:&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp; Count&amp;nbsp;&amp;nbsp;&amp;nbsp; TotalSize Class Name&lt;BR&gt;79132f9c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]]&lt;BR&gt;79116b3c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.Security.Permissions.ReflectionPermission&lt;BR&gt;79116a1c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.Security.Permissions.FileDialogPermission&lt;BR&gt;79116998&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.Security.PolicyManager&lt;BR&gt;791135c8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.Resources.FastResourceComparer&lt;BR&gt;7910a9e8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.RuntimeTypeHandle&lt;BR&gt;7910746c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.DBNull&lt;BR&gt;791073ac&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.Empty&lt;BR&gt;7910556c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.__Filters&lt;BR&gt;7910551c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.Reflection.Missing&lt;BR&gt;791045e4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12 System.RuntimeType+TypeCacheQueue&lt;BR&gt;7912ed84&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.SByte[]&lt;BR&gt;7912d8dc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Reflection.CustomAttributeData, mscorlib]]&lt;BR&gt;7912d274&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Reflection.CustomAttributeTypedArgument, mscorlib]]&lt;BR&gt;7911baf8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.Enum+HashEntry&lt;BR&gt;79113ea0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.Security.Permissions.FileIOAccess&lt;BR&gt;79113744&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.Resources.ResourceReader+TypeLimitingDeserializationBinder&lt;BR&gt;79112510&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.Globalization.GlobalizationAssembly&lt;BR&gt;791106e4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.Security.Permissions.UIPermission&lt;BR&gt;79108934&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.Reflection.Cache.InternalCache&lt;BR&gt;790fa4f8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.__ComObject&lt;BR&gt;008cdbfc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 Microsoft.Office.Interop.Excel.WorkbookClass&lt;BR&gt;008c5e84&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 Microsoft.Office.Interop.Excel.ApplicationClass&lt;/P&gt;
&lt;P&gt;...&lt;/P&gt;
&lt;P&gt;790fcb30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2466&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 217980 System.String&lt;BR&gt;Total 6901 objects&lt;/P&gt;
&lt;P&gt;The "stat" flag limits the output to just the objects themselves--very handy.&amp;nbsp; If we put our cursor on the first line of the output, we can use the find window to search for "Microsoft.Office.Interop".&amp;nbsp; This shows us the typed RCWs that are on the heap.&amp;nbsp; However, all we see are:&lt;/P&gt;
&lt;P&gt;008cdbfc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 Microsoft.Office.Interop.Excel.WorkbookClass&lt;BR&gt;008c5e84&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 Microsoft.Office.Interop.Excel.ApplicationClass&lt;/P&gt;
&lt;P&gt;We know from checking our code that we are calling Marshal.FinalReleaseComObject on these, so they can't be the problem.&amp;nbsp; (As a side note, if we wanted to dump these objects, we could call !dumpheap -mt &amp;lt;method table&amp;gt; and it would give us the address that we could then use to call !dumpobj.)&lt;/P&gt;
&lt;P&gt;What else could be going on?&amp;nbsp; Well, we have accounted for all of the typed RCWs, but we haven't checked for System.__ComObject.&amp;nbsp; To look for a specific type, we can make use of the !dumpheap type flag:&lt;/P&gt;
&lt;P&gt;0:005&amp;gt; !dumpheap -type System.__ComObject&lt;BR&gt;&amp;nbsp;Address&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Size&lt;BR&gt;02893294 790fa4f8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;total 1 objects&lt;BR&gt;Statistics:&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp; Count&amp;nbsp;&amp;nbsp;&amp;nbsp; TotalSize Class Name&lt;BR&gt;790fa4f8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16 System.__ComObject&lt;BR&gt;Total 1 objects&lt;BR&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;Aha!&amp;nbsp; This must be the RCW that is causing problems.&amp;nbsp; Now we just need to figure out where it is coming from.&amp;nbsp; Let's start by trying to figure out who owns it.&amp;nbsp; We'll do that by looking for the root using the !gcroot command:&lt;/P&gt;
&lt;P mce_keep="true"&gt;0:005&amp;gt; !gcroot 02893294&lt;BR&gt;Note: Roots found on stacks may be false positives. Run "!help gcroot" for&lt;BR&gt;more info.&lt;BR&gt;Scan Thread 0 OSTHread f48&lt;BR&gt;Scan Thread 2 OSTHread c3c&lt;/P&gt;
&lt;P mce_keep="true"&gt;Uh oh, the object is already eligible for collection.&amp;nbsp; We know this because there are no roots shown.&amp;nbsp; It looks like we need to start debugging earlier when this object is still alive.&amp;nbsp; The problem is that managed source level debugging doesn't work very well in WinDBG at the moment.&lt;/P&gt;
&lt;P mce_keep="true"&gt;What to do?&amp;nbsp; Take advantage of one of the best kept secrets out there:&amp;nbsp;the Visual Studio 2005 debugger can load debugger extensions.&amp;nbsp; To load SOS in the Visual Studio Debugger, enable native debugging in your project, start debugging&amp;nbsp;and then simply go to the immediate window and type ".load sos".&amp;nbsp; Subsequent SOS commands can be entered in the immediate window exactly as they would be entered in the WinDBG command window.&lt;/P&gt;
&lt;P mce_keep="true"&gt;So we'll open our project in Visual Studio, turn on native debugging, hit F10 (single step) and load SOS:&lt;/P&gt;&lt;FONT size=1&gt;
&lt;P&gt;.load sos&lt;/P&gt;
&lt;P&gt;extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded&lt;/P&gt;&lt;/FONT&gt;
&lt;P mce_keep="true"&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;Now that we have SOS loaded, we'll try setting a breakpoint on the first Marshal.FinalReleaseComObject call.&amp;nbsp; After running to that location, we'll take a look at the ComObject again to see if we've managed to catch it while it is still rooted.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;&lt;FONT size=1&gt;
&lt;P&gt;!dumpheap -type System.__ComObject&lt;/P&gt;
&lt;P&gt;Address MT Size&lt;/P&gt;
&lt;P&gt;02a83294 790fa4f8 16 &lt;/P&gt;
&lt;P&gt;02a83494 790fa4f8 16 &lt;/P&gt;
&lt;P&gt;total 2 objects&lt;/P&gt;
&lt;P&gt;Statistics:&lt;/P&gt;
&lt;P&gt;MT Count TotalSize Class Name&lt;/P&gt;
&lt;P&gt;790fa4f8 2 32 System.__ComObject&lt;/P&gt;
&lt;P&gt;Total 2 objects&lt;/P&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/FONT&gt;
&lt;P mce_keep="true"&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;Well, we see two ComObjects now.&amp;nbsp; Lets find out who they belong to.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;FONT size=1&gt;
&lt;P&gt;!gcroot 02a83294&lt;/P&gt;
&lt;P&gt;Note: Roots found on stacks may be false positives. Run "!help gcroot" for&lt;/P&gt;
&lt;P&gt;more info.&lt;/P&gt;
&lt;P&gt;Error during command: Warning. Extension is using a callback which Visual Studio does not implement.&lt;/P&gt;
&lt;P&gt;Scan Thread 5988 OSTHread 1764&lt;/P&gt;
&lt;P&gt;Scan Thread 3964 OSTHread f7c&lt;/P&gt;&lt;FONT size=1&gt;
&lt;P&gt;!gcroot 02a83494&lt;/P&gt;
&lt;P&gt;Note: Roots found on stacks may be false positives. Run "!help gcroot" for&lt;/P&gt;
&lt;P&gt;more info.&lt;/P&gt;
&lt;P&gt;Error during command: Warning. Extension is using a callback which Visual Studio does not implement.&lt;/P&gt;
&lt;P&gt;Scan Thread 5988 OSTHread 1764&lt;/P&gt;
&lt;P&gt;Scan Thread 3964 OSTHread f7c&lt;/P&gt;
&lt;P&gt;DOMAIN(004514F0):HANDLE(Strong):18118c:Root:02a83494(System.__ComObject)&lt;/P&gt;&lt;/FONT&gt;&lt;/FONT&gt;
&lt;P mce_keep="true"&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;The second ComObject is rooted to a GCHandle, which means that it was probably created by some internal implementation and we can ignore it for now.&amp;nbsp; The first ComObject is not rooted--which means that it is probably the one we are looking for, but we still don't know who is allocating it.&amp;nbsp; To find out, we'll try stopping at each line of code and dumping ComObjects.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;What we discover with this approach is that the ComObject does not appear until we step over the following line of code.&amp;nbsp; &lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;FONT color=#2b91af size=2&gt;
&lt;P&gt;Workbook&lt;/FONT&gt;&lt;FONT size=2&gt; workbook = excel.Workbooks.Add(&lt;/FONT&gt;&lt;FONT color=#2b91af size=2&gt;Type&lt;/FONT&gt;&lt;FONT size=2&gt;.Missing);&lt;/P&gt;&lt;/FONT&gt;
&lt;P mce_keep="true"&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;However, it is still unrooted.&amp;nbsp; That can only mean one thing--this line of code is creating an RCW on the heap but not referencing it.&amp;nbsp; Looking carefully at the code, we finally see what we have been missing.&amp;nbsp; The Workbooks accessor (of the ApplicationClass) creates a Workbooks interface instance and returns it.&amp;nbsp; Since we don't assign the return value to anything, there is nothing to reference it beyond the scope of the call.&amp;nbsp; Presumably, the object backing the Workbooks interface does't implement IProvideClassInfo, so the RCW is created as a System.__ComObject.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;We can actually confirm this with a little creative stepping in the dissembly window:&lt;/P&gt;&lt;FONT size=1&gt;
&lt;P&gt;Workbook workbook = excel.Workbooks.Add(Type.Missing);&lt;/P&gt;&lt;/FONT&gt;&lt;FONT color=#808080 size=1&gt;
&lt;P&gt;0000003e mov ecx,esi &lt;/P&gt;
&lt;P&gt;00000040 mov eax,dword ptr [ecx] &lt;/P&gt;
&lt;P&gt;00000042 call dword ptr [eax+000000F8h] &lt;/P&gt;
&lt;P&gt;00000048 mov edi,eax &lt;/P&gt;&lt;/FONT&gt;
&lt;P mce_keep="true"&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;After stepping over the first call (which invokes the Workbooks accessor), we dump the&amp;nbsp;ComObjects again:&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;FONT size=1&gt;
&lt;P&gt;!dumpheap -type System.__ComObject&lt;/P&gt;
&lt;P&gt;Address MT Size&lt;/P&gt;
&lt;P&gt;02763294 790fa4f8 16 &lt;/P&gt;
&lt;P&gt;027632a4 790fa4f8 16 &lt;/P&gt;
&lt;P&gt;027632b4 790fa4f8 16 &lt;/P&gt;
&lt;P&gt;total 3 objects&lt;/P&gt;
&lt;P&gt;Statistics:&lt;/P&gt;
&lt;P&gt;MT Count TotalSize Class Name&lt;/P&gt;
&lt;P&gt;790fa4f8 3 48 System.__ComObject&lt;/P&gt;
&lt;P&gt;Total 3 objects&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;/FONT&gt;The last one&amp;nbsp;proves our hypothesis:&lt;/P&gt;&lt;FONT size=1&gt;
&lt;P&gt;!gcroot 027632b4&lt;/P&gt;
&lt;P&gt;Note: Roots found on stacks may be false positives. Run "!help gcroot" for&lt;/P&gt;
&lt;P&gt;more info.&lt;/P&gt;
&lt;P&gt;Error during command: Warning. Extension is using a callback which Visual Studio does not implement.&lt;/P&gt;
&lt;P&gt;Scan Thread 3924 OSTHread f54&lt;/P&gt;
&lt;P&gt;ESP:47ef64:Root:027632b4(System.__ComObject)&lt;/P&gt;
&lt;P&gt;Scan Thread 4672 OSTHread 1240&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;/FONT&gt;&amp;nbsp;What we see is that the ComObject is currently rooted to the stack.&amp;nbsp; Now&amp;nbsp;after we step over the remainder of the code for that line, we can dump the heap again and we will see that object has lost its root (which is what we expect).&lt;/P&gt;&lt;FONT size=1&gt;
&lt;P&gt;!gcroot 027632b4&lt;/P&gt;
&lt;P&gt;Note: Roots found on stacks may be false positives. Run "!help gcroot" for&lt;/P&gt;
&lt;P&gt;more info.&lt;/P&gt;
&lt;P&gt;Error during command: Warning. Extension is using a callback which Visual Studio does not implement.&lt;/P&gt;
&lt;P&gt;Scan Thread 3924 OSTHread f54&lt;/P&gt;
&lt;P&gt;Scan Thread 4672 OSTHread 1240&lt;/P&gt;&lt;/FONT&gt;
&lt;P mce_keep="true"&gt;&lt;FONT size=1&gt;&lt;FONT size=2&gt;In order to effect a clean shutdown, it would seem that we need to refactor our code so that we can call Marshal.FinalReleaseComObject on the Workbooks object.&amp;nbsp; We'll need to assign the return value of the Workbooks accessor to a variable.&amp;nbsp; Here is the corrected code:&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; static void Main(string[] args)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ApplicationClass excel = new ApplicationClass();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Workbooks workbooks = excel.Workbooks;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Workbook workbook = workbooks.Add(Type.Missing);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Marshal.FinalReleaseComObject(workbook);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Marshal.FinalReleaseComObject(workbooks);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; excel.Quit();&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Marshal.FinalReleaseComObject(excel);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Console.ReadKey();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P mce_keep="true"&gt;Upon running it, we see that we have solved the problem and Excel now shuts down cleanly when we expect it to.&lt;/P&gt;
&lt;P mce_keep="true"&gt;Hopefully, this demonstration has reinforced the perils of RCW usage.&amp;nbsp; When writing interop code, it is&amp;nbsp;important to avoid calls that tunnel into the object model because they will orphan RCWs on the heap that you will not be able to access in order to call Marshal.ReleaseComObject.&amp;nbsp; &lt;FONT size=1&gt;&lt;/P&gt;&lt;/FONT&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=4812155" width="1" height="1"&gt;</content><author><name>geoffda</name><uri>http://blogs.msdn.com/members/geoffda.aspx</uri></author></entry><entry><title>The Designer Process that Would Not Terminate</title><link rel="alternate" type="text/html" href="http://blogs.msdn.com/geoffda/archive/2007/08/31/the-designer-process-that-would-not-terminate.aspx" /><id>http://blogs.msdn.com/geoffda/archive/2007/08/31/the-designer-process-that-would-not-terminate.aspx</id><published>2007-09-01T00:00:00Z</published><updated>2007-09-01T00:00:00Z</updated><content type="html">&lt;P&gt;I recently was asked to take a look at some VSTO test automation that wasn't behaving correctly on lab machines.&amp;nbsp; The test was fairly simple; it created a VSTO Excel project, dirtied the document, closed the designer window without saving, and then reopened the document to verify that it was not dirty.&amp;nbsp; However, it turned out that the Excel process was never terminating as expected after the designer window was closed--though Excel would terminate after the test case had completed.&lt;/P&gt;
&lt;P&gt;My initial assumption was that there was an RCW (Runtime Callable Wrapper)&amp;nbsp;somewhere that was holding a reference to Excel so I started digging into the code.&amp;nbsp; However, it appeared that all test code holding onto RCWs had an IDisposable implementation that was calling Marshal.ReleaseComObject on said RCWs.&amp;nbsp; I stepped through all of the Marshal.ReleaseComObject calls to check the return values by examining EAX.&amp;nbsp; Sure enough in a few cases, Marshal.ReleaseComObject was returning 1.&amp;nbsp; So, I reset EIP in the debugger to make additional ReleaseComObject calls to ensure the referenced IUnknown was fully released.&amp;nbsp; However, that still did not solve the problem.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;So what was going on?&amp;nbsp; I finally started stepping deeper into various methods and realized that a somewhat lower layer of the test automation code was allocating RCW's on the heap and those RCWs were never explicitly released before they were discarded.&amp;nbsp; As a result, the RCW's would hold a reference that would keep Excel open until they were collected--which wasn't happening prior to the designer being closed.&lt;/P&gt;
&lt;P&gt;Since the methods in question weren't caching the RCW's that were being allocated on the heap, there was no possibility for implementing a Dispose/Marshal.ReleaseComObject pattern without doing a major rewrite of the code.&amp;nbsp; Instead, I was able to solve the problem by calling GC.Collect to force a garbage collection to occur.&amp;nbsp; The specific pattern to use (which is documented by Andrew Whitechapel here: &lt;A href="http://msdn2.microsoft.com/en-us/library/aa679805(office.11).aspx" mce_href="http://msdn2.microsoft.com/en-us/library/aa679805(office.11).aspx"&gt;http://msdn2.microsoft.com/en-us/library/aa679805(office.11).aspx&lt;/A&gt;) is as follows:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;FONT face=Calibri size=3&gt;GC.Collect();&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;FONT face=Calibri size=3&gt;GC.WaitForPendingFinalizers();&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;FONT face=Calibri size=3&gt;// Only call if you care about reclaiming RCW memory right now.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;FONT face=Calibri size=3&gt;GC.Collect();&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt" mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;This deserves some additional explanation.&amp;nbsp; What we are really trying to accomplish here is to get the RCW's finalizer executed.&amp;nbsp; The RCW's finalizer is where the actual IUnkown::Release call will occur--which is what we need to happen in order to destroy the underlying COM object.&amp;nbsp; The actual reclamation of the RCW memory is secondary and probably not important).&amp;nbsp; In any event, in the initial GC.Collect call, the garbage collector will see that there are pointers to the unrooted RCWs in the finalizer queue and will duly move them to the freachable queue for processing by the finalizer thread.&amp;nbsp; The subsequent WaitForPending finalizers call blocks the calling thread until all finalizers in the freachable queue have been executed--which ensures that all RCW's will have released their wrapped COM objects.&amp;nbsp; When the finalizer thread executes a finalizer on an object, it removes the pointer to that object from the finalizer queue.&amp;nbsp; At that point the object will be eligible for collection, which means that the memory will not be recovered until the next time a garbage collection occurs.&amp;nbsp; For this reason, if you actually care about recovering the RCW memory, you would need to call GC.Collect a second time.&amp;nbsp; Andrew Whitechapel actually recommends a second call to GC.WaitForPendingFinalizers as well, but I can't see how this would be useful since any unrooted objects would have been finalized as a result of the first set of calls.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;The real issue is that using RCW's is very tricky and if you aren't careful, you can end up putting yourself in a box that you can only escape with the scorched earth approach outlined above.&amp;nbsp; Anyone using RCWs should read Chris Brumme's blog entry that explains how they work here: &lt;A href="http://blogs.msdn.com/cbrumme/archive/2003/04/16/51355.aspx" mce_href="http://blogs.msdn.com/cbrumme/archive/2003/04/16/51355.aspx"&gt;http://blogs.msdn.com/cbrumme/archive/2003/04/16/51355.aspx&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;For certain, utilizing the GC.Collect pattern will guarantee proper shutdown of a COM server (assuming all RCWs are eligible for collection).&amp;nbsp; The advantage of the pattern lies in its simplicity.&amp;nbsp; By forcing a GC, it sidesteps all of the issues associated with prematurely calling Marshal.ReleaseComObject--since only those RCWs that are not rooted will release.&amp;nbsp; The disadvantage to this pattern is that degrades performance.&amp;nbsp; Scott Holden details these issues in his blog entry: &lt;A href="http://blogs.msdn.com/scottholden/archive/2004/12/28/339733.aspx" mce_href="http://blogs.msdn.com/scottholden/archive/2004/12/28/339733.aspx"&gt;http://blogs.msdn.com/scottholden/archive/2004/12/28/339733.aspx&lt;/A&gt;.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;It should also be pointed out that when an AppDomain is torn down, all finalizable objects get finalized regardless of whether they are rooted.&amp;nbsp; So (excepting the rare case where the finalizer thread gets blocked or the process gets summarily terminated) all RCWs will eventually release as part of a normal shutdown.&amp;nbsp; Depending on your Com object usage, it might be reasonable to simply let this happen.&amp;nbsp; The question to be answered will be whether an unacceptable level of memory pressure is created by waiting.&amp;nbsp; The issue is that RCWs are small objects so a great many can be created before the generation 0 heap fills up and a collection is performed by the system.&amp;nbsp; If the underlying Com objects are large, you may end up with a great deal of memory pressure from the native heap.&amp;nbsp; Obviously, the situation to be avoided is where so much memory is used that hard page faults start to occur and system performance degrades substantially.&lt;/P&gt;
&lt;P&gt;For anyone wanting a deeper understanding of garbage collection, I'd recommend the following KB article which provides a great reading list: &lt;A href="http://support.microsoft.com/default.aspx/kb/317866/" mce_href="http://support.microsoft.com/default.aspx/kb/317866/"&gt;http://support.microsoft.com/default.aspx/kb/317866/&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;Once you've read up on garbage collection, try taking Tess Ferrandez's garbage collection quiz: &lt;A href="http://blogs.msdn.com/tess/archive/2007/04/02/net-garbage-collection-popquiz.aspx" mce_href="http://blogs.msdn.com/tess/archive/2007/04/02/net-garbage-collection-popquiz.aspx"&gt;http://blogs.msdn.com/tess/archive/2007/04/02/net-garbage-collection-popquiz.aspx&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;The answers can be found here: &lt;A href="http://blogs.msdn.com/tess/archive/2007/04/10/net-garbage-collector-popquiz-followup.aspx" mce_href="http://blogs.msdn.com/tess/archive/2007/04/10/net-garbage-collector-popquiz-followup.aspx"&gt;http://blogs.msdn.com/tess/archive/2007/04/10/net-garbage-collector-popquiz-followup.aspx&lt;/A&gt;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;In my next blog entry I'll talk about how you can use the debugger to diagnose a problem like this.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=4677952" width="1" height="1"&gt;</content><author><name>geoffda</name><uri>http://blogs.msdn.com/members/geoffda.aspx</uri></author></entry></feed>