Programming for the Windows Tablet Foundation
http://www.windojitsu.com/blog/bugstory.html
Discuss...
The .locals declaration appears not to be followed by an 'init' for some reason. Is the C# compiler finally exploiting the fact that the language requires explicit initialization of locals, and is no longer generating the init marker on local variables? And there's no reason to suppose that an interop call will necessarily obey any particular rules for initializing out parameters - there are many conventions around in C and C++... But I have a sneaking suspicion that I'm on entirely the wrong track...
Bingo! Yes, that's correct -- the 'init' flag is missing from the IL method header. AFAIK the C# compiler still emits the 'init' flag in all cases... I'm guessing that System.Drawing.dll was probably compiled with MC++, which doesn't? I consider that to be a bug -- or at least, a lapse in defensive programming style. I guess I consider the real bug to be the Win32 EnumPrinters function, which apparently leaves those [out] arguments uninitialized in some exceptional cases (like, when the spooler service is stopped). The scariest part of this bug is thinking about all the garbage-sized byte arrays that I've been allocating -- the ones that were small enough to *not* throw OutOfMemory, but big enough to make my app gring to a halt for several seconds! Indeed, this was another bug lurking on my list... I think some testers were seeing that happening! Here's hoping it goes away, and is never heard from again. :)
Is it possible that the first call to SafeNativeMethods.EnumPrinters actually succeeded because 0 bytes wasn't too small?
...because if the first call succeeded, and num3 was set to zero, then maybe there is no need to allocate any PRINTER_INFO_* structures? Would people typically call EnumPrinters with the 5th and 6th value set to 'a value' and 'a pointer to same value' as per the second call in your code snippet? If so, perhaps EnumPrinters just assumes that's how it will be called, so isn't prepared for a call with a 'const' and 'pointer to some value' like the first call and therefore hasn't bothered to initialise it's output parameter, assuming that it already points to a zero value (as per the 5th param)..?
Ian asks: Is the C# compiler finally exploiting the fact that the language requires explicit initialization of locals, and is no longer generating the init marker on local variables? No, it can't. Verifiable code needs the "init" and therein lies the answer to the riddle. The code was compiled with the /unsafe option, and in that case the C# compiler leaves off the initlocals flag.
After much debate it is the finding of this commitee that the bug is in get_InstalledPrinters. get_InstalledPrinters does not need to initialise num2 to zero for the purposes of the first call to SafeNativeMethods.EnumPrinters, however, regardless of whether it does or does not initialise num2 before that first call it should not be making assumptions as to the its value without first ensuring that EnumPrinters has either succeeded or has failed with ERROR_INSUFFICIENT_BUFFER. If EnumPrinters fails with any other error then the value of num2 should be assumed 'undefined' and if the EnumPrinters succeeds at the first call then the value of num2 is irrelevant, although in that case you could assumed it to be 0. get_InstalledPrinters has a bug. It should probably be implemented something like this (not that I'm confortable with the notion of throwing exceptions from property getters): // ... int num2; int num3; int num1 = SafeNativeMethods.EnumPrinters( 6, null, num4, IntPtr.Zero, 0, out num2, out num3 ); if ( num1 != 0 ) { // first call succeeded (must have found no printers). Debug.Assert( num3 == 0, "There are no printers." ); Debug.Assert( num2 == 0, "No buffer necessary." ); return new string[ 0 ]; } if ( Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER ) { // first call failed for reasons other than insufficient buffer. // ( I'm not sure what exception gets thrown here, a check for // RPC_S_SERVER_UNAVAILABLE may be in order. ) throw new Win32Exception(); } IntPtr ptr1 = Marshal.AllocCoTaskMem( num2 ); num1 = SafeNativeMethods.EnumPrinters( 6, null, num4, ptr1, num2, out num2, out num3 ); if ( num1 == 0 ) { // second call failed for some reason. Marshal.FreeCoTaskMem( ptr1 ); throw new Win32Exception(); } textArray1 = new string[ num3 ]; // ...
Sorry for all the typos. There was just one too many, so I'm forced to post again or else I'll lose sleep. :P After much debate it is the finding of this committee that the bug is in get_InstalledPrinters. get_InstalledPrinters does not need to initialise num2 to zero for the purposes of the first call to SafeNativeMethods.EnumPrinters. However, regardless of whether num2 is or is not initialised before the first call to EnumPrinters, get_InstalledPrinters should not be making assumptions as to num2's value without first ensuring that EnumPrinters has either succeeded or has failed with ERROR_INSUFFICIENT_BUFFER. If EnumPrinters fails with any other error then the value of num2 should be assumed 'undefined', and if EnumPrinters succeeds at the first call then the value of num2 is irrelevant, although in that case you could assume it to be zero. get_InstalledPrinters should probably be implemented something like this (not that I'm comfortable with the notion of throwing exceptions from property getters): // ... int num2; int num3; int num1 = SafeNativeMethods.EnumPrinters( 6, null, num4, IntPtr.Zero, 0, out num2, out num3 ); if ( num1 != 0 ) { // first call succeeded (must have found no printers). Debug.Assert( num3 == 0, "There are no printers." ); Debug.Assert( num2 == 0, "No buffer necessary." ); return new string[ 0 ]; } // first call failed. if ( Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER ) { // first call failed for reasons other than insufficient buffer. // ( I'm not sure what exception gets thrown here, a check for // RPC_S_SERVER_UNAVAILABLE may be in order. ) throw new Win32Exception(); } // first call failed because of insufficient buffer. IntPtr ptr1 = Marshal.AllocCoTaskMem( num2 ); num1 = SafeNativeMethods.EnumPrinters( 6, null, num4, ptr1, num2, out num2, out num3 ); if ( num1 == 0 ) { // second call failed for some reason. Marshal.FreeCoTaskMem( ptr1 ); throw new Win32Exception(); } // EnumPrinters succeeded. textArray1 = new string[ num3 ]; // ...
^_^,Pretty Good!