benwu's WebLog

  • Managed Spy -- A new way to look at Windows Forms applications

    I just wrote an article for msdn magazine, "Managed Spy: Deliver The Power Of Spy++ To Windows Forms With Our New Tool"

    This tool is a combination of an application (exe) and library (dll).  The application gives you a spy++ view of all managed processes and thier child windows.  Unlike spy++, managed spy allows you to set and get all managed properties such as "BackColor" or "BorderStyle".  You can also view managed events such as "MouseMove" or "ItemSelected".

    The library portion allows programmatic access to controls in another process.  This means you can sync on events and set properties all in your own code.  You could use this to create test harnesses, create a diagnostic tool for your application after its deployed or any number of things.

    Take a look at the article and let me know what you think!

     

  • New VS feature allows VS to crash!

    One of the newer "features" in Visual Studio 8.0 is the ability for us to collect better information about exceptions that are thrown in VS.  When an exception is thrown (and unhandled), VS will generate a Watson report that will send microsoft a minidump where the exception occurred.  Once we get this watson report, its easier now for us to locate the exact cause and issue a fix.

    Of course, a side effect of this is that unhandled exceptions will bring down VS and may cause loss of data if you had not saved your current work.  This can be particularly bad in some scenarios where your control is being used in VS and throws an exception.  We do a lot to try to capture these exceptions.  For example, if the exception is thrown during designer load (when your control is being hosted in the windows forms designer) we will not shutdown VS.  Instead the user will see what we call the WSoD ("white screen of darn").  This indicates the callstack of the exception and disallows further edits to the form.  Also, if the exception is thrown as a result of a window message sent through the designer, we will catch the error and display it to the user.  An example of this is a button click or a mousehover/mouseover event.  These messages are intercepted by the designer before the control in the designer is allowed to process them.

    There are, however, scenarios that we can not catch during design time.  If you throw an exception in a Timer_Tick event, this will not go through a designer and it will therefore bring down VS.  We could not change this behavior in the runtime because doing so would be a compat breaking change (apps that used to show the exception dialog in this case would no longer show it).

    It should be rare that you encounter this error.  However, if you do -- it can be particularly painful.  Especially if it happens whenever a certain form designer appears.  What is worse, is that if a solution has stored that it should show a form immediately when you load the solution, you can end up in a situation where VS crashes every time you open the solution!  One fix here is to delete the .suo file.  This is a solution config file that stores what documents should be opened when the solution is opened.  If there is no .suo file, no documents are opened by default.  This will give you a chance to fix any errors (in code) that might be giving you problems.

     

  • Fixing Window leaks and "Class Already Registered" errors

    Windows Forms makes it very easy to create a rich UI experience.  But it also makes it easy to leak windows or resources.

    For example, say you create a new temporary AppDomain and decide to show some UI in it.  If you close the AppDomain without cleaning up the windows (ie call Dispose on the Controls or close the windows via the UI), then the windows will be orphaned and will not be cleaned up properly.

    It is also easy to create new threads with UI.  However, if you shut down the thread that created the UI, any active windows will again be orphaned.  This is because you cannot destroy a window from a thread other than the thread that created the window.

    WindowCleanup : A utility to help

    Below is a class called "WindowCleanup" along with a Form that displays how it can be used:

    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Data;
    using System.Runtime.InteropServices;
    using System.Threading;

    namespace WindowsApplication1
    {
     /// <summary>
     /// Summary description for Form1.
     /// </summary>
     public class Form1 : System.Windows.Forms.Form
     {
      private System.Windows.Forms.Button button1;
      private System.Windows.Forms.Button button2;
      /// <summary>
      /// Required designer variable.
      /// </summary>
      private System.ComponentModel.Container components = null;

      public Form1()
      {
       //
       // Required for Windows Form Designer support
       //
       InitializeComponent();

       //
       // TODO: Add any constructor code after InitializeComponent call
       //
      }

      /// <summary>
      /// Clean up any resources being used.
      /// </summary>
      protected override void Dispose( bool disposing )
      {
       if( disposing )
       {
        if (components != null)
        {
         components.Dispose();
        }
       }
       base.Dispose( disposing );
      }

      #region Windows Form Designer generated code
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent()
      {
       this.button1 = new System.Windows.Forms.Button();
       this.button2 = new System.Windows.Forms.Button();
       this.SuspendLayout();
       //
       // button1
       //
       this.button1.Location = new System.Drawing.Point(56, 152);
       this.button1.Name = "button1";
       this.button1.TabIndex = 0;
       this.button1.Text = "CleanUp";
       this.button1.Click += new System.EventHandler(this.button1_Click);
       //
       // button2
       //
       this.button2.Location = new System.Drawing.Point(112, 80);
       this.button2.Name = "button2";
       this.button2.TabIndex = 1;
       this.button2.Text = "NewAppDomainWindow";
       this.button2.Click += new System.EventHandler(this.button2_Click);
       //
       // Form1
       //
       this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
       this.ClientSize = new System.Drawing.Size(292, 266);
       this.Controls.Add(this.button2);
       this.Controls.Add(this.button1);
       this.Name = "Form1";
       this.Text = "Form1";
       this.ResumeLayout(false);

      }
      #endregion

      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main()
      {
       Application.Run(new Form1());
      }


      private void button1_Click(object sender, System.EventArgs e)
      {
       WindowCleanup.CloseWindows(false, false);
      }

      AppDomain domain;
      
      private void button2_Click(object sender, System.EventArgs e)
      {
       if (domain != null)
       {
        AppDomain.Unload(domain);
       }
       domain = AppDomain.CreateDomain("tempdomain");
       Form1 domainform = (Form1)domain.CreateInstanceAndUnwrap(typeof(Form1).Assembly.FullName,
        typeof(Form1).FullName);

       domainform.Show();
      }
     }

     class WindowCleanup
     {
      static bool closeNonManaged = false;
      static bool closeOnlyThisThread = false;

      [DllImport("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
      public static extern int GetCurrentProcessId();
       [DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
      public static extern int GetWindowThreadProcessId(HandleRef hWnd, out int lpdwProcessId);
      [DllImport("user32.dll", ExactSpelling=true)]
      public static extern bool EnumChildWindows(HandleRef hwndParent, EnumWindowCallback lpEnumFunc, HandleRef lParam);
      [DllImport("user32.dll", CharSet=CharSet.Auto)]
      public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
      [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
      public static extern bool EnumWindows(EnumWindowCallback callback, IntPtr extraData);
      [DllImport("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
      public static extern int GetCurrentThreadId();

      public delegate bool EnumWindowCallback(IntPtr hwnd, IntPtr lParam);
      public static HandleRef NullHandleRef;
     
      static public void CloseWindows(bool nonManaged, bool onlyThisThread)
      {
       WindowCleanup.closeNonManaged = nonManaged;
       WindowCleanup.closeOnlyThisThread = onlyThisThread;
       EnumWindows(
        new EnumWindowCallback(CloseWindowsCallback), IntPtr.Zero);
      }

      private static bool CloseWindowsCallback(IntPtr handle, IntPtr extraParameter)
      {
       int procid1;
       int procid2 = GetCurrentProcessId();
         
       int threadid1 = GetWindowThreadProcessId(new HandleRef(null, handle), out procid1);
       if (procid1 == procid2)
       {
        if (closeNonManaged || NativeWindow.FromHandle(handle) != null)
        {
         if (!closeOnlyThisThread || GetCurrentThreadId() == threadid1)
         {
          SendMessage(new HandleRef(null, handle), 0x10, 0, 0);
          return true;
         }
        }
        EnumChildWindows(new HandleRef(null, handle),
         new EnumWindowCallback(CloseWindowsCallback), NullHandleRef);
       }
      
       return true;
      }
     }
    }

    WindowCleanup has one main static method:

    WindowCleanup.CloseWindows(nonManaged, onlyThisThread)

    nonManaged:  If this is true, then all windows belonging to the current process will be closed.  If false, then only windows recognized as managed windows in the current appdomain will be closed.

    onlyThisThread:  If this is true, then only windows created on the calling thread will be closed.  Use this to clean up any leftover windows before your temporary UI thread terminates.

    To see the difference this class makes, run the code above by creating a new C# Windows application and copying this code over Form1.cs.  When you run it, you will see 2 buttons (somewhat randomly placed).

    Do this to see an error that occurs when you leak a window:

    1.  Run the application

    2.  Click the "New AppdomainWindow" button on the first form.  A new form will appear and will have focus.

    3.  Go back to the first form and click the button again.  The opened form will disappear and a new form will appear.

    4.  Perform step 3 additional times.  Eventually you will get a "ClassAlreadyRegistered" error which occurs because a window was leaked.

     

    Now, to see how the class fixes the problem:

    1.  Run the application

    2.  Click the "New AppdomainWindow" button on the first form.  A new form will appear and will have focus.

    3.  Go click the "Cleanup" button on the newly opened form.  That opened form will disappear.

    4.  Now click "NewAppdomainForm" again on the first form to show a second form again.

    5.  Perform step 3-4 additional times.  You will not get an error because the utility class is cleaning up open windows in the appdomain that will be shut down.

     

    Let me know if this helps you at all!

     

  • SpyWindowFinalizer and Windows Message Hooking

    One very powerful API that some UI developers may not be aware of is SetWindowHookEx.  This api allows you to intercept all types of window messages before (or after) they are processed.

    You can use this to do a whole range of neat things.  For instance, you can log all messages in your app.  You can use the windows journal record/playback utility to create a test harness.  You can also write code to validate your application -- such as whether your application disposes all of its windows properly.  If you allow a control to finalize (for example, if you remove the control from its container and do not call Dispose() on it), it can hurt the performance of your application or even worse -- cause an AV.

    The sample class I wrote below (SpyWindowFinalizer) allows you to detect controls in your application that are not being properly Dispose()'d and could be leaking and causing performance problems.  It allows you to take snapshots of all the controls currently valid in the application.  You could use this in a UI intensive app to measure where you may have too many temporary controls.  It also is a good starting sample if you'd like to do some window message hooking on your own!

    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Data;
    using System.Runtime.InteropServices;

    namespace WindowSpy {
        /// <summary>
        /// This class uses SetWindowsHookEx to gather statistics and information
        /// on windows created in the application.
        /// It can easily be extended to track detailed information on each control or
        /// modify each control in some manner
        /// </summary>
        class SpyWindowFinalizer {

            /// <summary>
            /// These P/Invokes are necessary to create and manage the hook
            /// </summary>
            public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
            [DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
            public static extern IntPtr CallNextHookEx(HandleRef hhook, int code, IntPtr wparam, IntPtr lparam);
            [DllImport("User32.dll", CharSet = CharSet.Auto)]
            public static extern IntPtr SetWindowsHookEx(int hookid, HookProc pfnhook, HandleRef hinst, int threadid);
            [DllImport("Kernel32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
            public static extern int GetCurrentThreadId();
            [DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
            public static extern bool UnhookWindowsHookEx(HandleRef hhook);

            public const int WH_CALLWNDPROC = 4;    //this tells SetWindowsHookEx to hook WndProc calls
            public const int HC_ACTION = 0;         //when this comes into our callback, we should handle the message
            public const int WM_CREATE = 0x0001;    //this window message is sent when a window is created
            public const int WM_NCDESTROY = 0x0082; //this window message is sent when the nonclient area of a window is destroyed

            static HookProc hookProc;               //this is the delegate to our message hook
            static IntPtr messageHookHandle;        //the handle used for unregistering
            static Hashtable ctorTable = new Hashtable();   //our window table that holds all of the active window information we need
            static ArrayList badWindows = new ArrayList();  //a list of windows that finalized with the window active.  This means the control was not disposed properly

            /// <summary>
            /// Sets up our hook
            /// </summary>
            public static void Initialize() {
                if (messageHookHandle != IntPtr.Zero) {
                    return;
                }
                hookProc = new HookProc(MessageHookProc);

                messageHookHandle = SetWindowsHookEx(WH_CALLWNDPROC,
                    hookProc,
                    new HandleRef(null, IntPtr.Zero),
                    GetCurrentThreadId());
            }

            /// <summary>
            /// Terminates our hook
            /// </summary>
            public static void Terminate() {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                Application.DoEvents();

                if (messageHookHandle != IntPtr.Zero) {
                    UnhookWindowsHookEx(new HandleRef(null, messageHookHandle));
                    hookProc = null;
                    messageHookHandle = IntPtr.Zero;
                }
                foreach (string info in ctorTable.Values) {
                    badWindows.Add("**possible leak**" + info);
                }
                ctorTable.Clear();
            }

            /// <summary>
            /// Returns a list of the windows that were not disposed.
            /// </summary>
            public static IEnumerable WindowsFinalized {
                get {
                    return badWindows;
                }
            }

            /// <summary>
            /// Returns the current list of all windows forms controls
            /// </summary>
            public static IEnumerable WindowsStillAlive {
                get {
                    ArrayList list = new ArrayList();
                    foreach (IntPtr handle in ctorTable.Keys) {
                        Control c = Control.FromHandle(handle);
                        if (c != null) {
                            list.Add(c);
                        }
                    }
                    return list;
                }
            }

            /// <summary>
            /// The message hook itself.
            /// It stores info when windows get created and destroyed.
            /// </summary>
            private static unsafe IntPtr MessageHookProc(int nCode, IntPtr wparam, IntPtr lparam) {
                if (nCode == HC_ACTION) {
                    CWPSTRUCT* msg = (CWPSTRUCT*)lparam;
                    if (msg != null) {
                        if (msg->message == WM_CREATE) {
                            Control c = Control.FromHandle(msg->hwnd);
                            if (c != null) {
                                string controlentry = c.Name + " : " + c.GetType().FullName;
                                controlentry +=  new System.Diagnostics.StackTrace().ToString();
                                ctorTable.Add(msg->hwnd, controlentry);
                            }
                        }
                        else if (msg->message == WM_NCDESTROY) {
                            if (ctorTable.ContainsKey(msg->hwnd)) {
                                NativeWindow w = NativeWindow.FromHandle(msg->hwnd);
                                if (w == null) {
                                    badWindows.Add(ctorTable[msg->hwnd]);
                                }
                                ctorTable.Remove(msg->hwnd);
                            }
                        }
                    }
                }

                return CallNextHookEx(new HandleRef(null, messageHookHandle), nCode, wparam, lparam);
            }

            /// <summary>
            /// parameter type for message hook
            /// </summary>
            [StructLayout(LayoutKind.Sequential)]
                [Serializable]
                public struct CWPSTRUCT {
                public IntPtr lParam;
                public IntPtr wParam;
                public int message;
                public IntPtr hwnd;
            }
        }
    }

    To use this in your app, write this:

      [STAThread]
      static void Main()
      {
                SpyWindowFinalizer.Initialize();

                Application.Run(new Form1());

                SpyWindowFinalizer.Terminate();
                foreach(string s in SpyWindowFinalizer.WindowsFinalized) {
                    System.Windows.Forms.MessageBox.Show(s);
                }
      }

    This will display the control names, types and contructor callstacks for all controls that finalized before being destroyed (meaning they were likely not properly disposed of).

    You can test this by creating a simple windows form application with 2 buttons.  In the button click handlers do the following:

            private void button1_Click(object sender, System.EventArgs e) {
                this.button1.Parent = null;
                this.button1 = null;
            }

            private void button2_Click(object sender, System.EventArgs e) {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                Application.DoEvents();
            }

    Run the application,

    Click button1  (it should disappear)

    Click button2

    Close Form1 and you should see a dialog indicating a potential bad window (being button1 which was never disposed).

     

    For more info, see:

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/setwindowshookex.asp

     

     

  • Control.Invoke hangs under heavy stress

    There have been reports of Control.Invoke hanging after the method completes.  This was reported by a few customers and we have fixed this.  For your reference:

    ID: SRX050308605416

    KB Article: 896665

    The problem stems from some of the errors I describe in my previous post.  IsCompleted will be "true", but the AsyncWaitHandle will never be set.  The fix will be included in our next service pack.

    A few workarounds exist:

    1. Use a timeout in a loop checking IsComplete.  Such as:

    while (!result.IsCompleted) {

       result.AsyncWaitHandle.WaitOne(200, false);

    }

    2. Access AsyncWaitHandle and call Thread.MemoryBarrier() before checking IsComplete

    WaitHandle handle = result.AsyncWaitHandle;

    Thread.MemoryBarrier();

    if (!result.IsCompleted) {

       handle.WaitOne();

    }

    instead of:

    if (!result.IsCompleted) {

       result.AsyncWaitHandle.WaitOne();

    }

    Both of these workarounds should work.  The first has a minor perf implication.

    Props to Jack Cheng at VideoTechnics for helping to correct the second fix.

  • Is this code threadsafe?

    Here is a question for you multithreaded coders out there.

    After executing Thread1 and Thread2, what are the possible values for "i"?

            bool b1 = false;
            bool b2 = false;
            int i = 0;

            private void Thread1() {
                b1 = true;
                if (b2) {
                    i = 1;
                }
            }

            private void Thread2() {
                b2 = true;
                if (b1) {
                    i = 1;
                }
            }

    The answer may surprise you.  In fact, it may be zero on multiprocessor machines do to load/store optimizations made by the CPU.  Try it for yourself.  (note: you will always get i==1 on a single proc machine).

    Pages 19-20 in http://www.microsoft.com/whdc/driver/kernel/MP_issues.mspx explain why.  What is happening is that in about 1 in 1000 cases (if you actually test this on a multi-CPU machine), the CPU for Thread1 will preload the value of b2 at the same time the CPU for Thread2 is preloading the value for b1.  These CPUs then write out the new values at the same time, but lo and behold.  The loaded values for each bool will be false and neither if statement will be executed.  Pretty interesting, yes?

    On the "completely unrelated, but important" front, I have switched teams within Microsoft and I now belong to the .Net Client team (we own System.Windows.Forms, ComponentModel, Designer and a whole bunch of new features for whidbey such as ToolStrips, LayoutPanels, and DataGridView).  After using .Net for five years now, I hardly remember what MFC was like.  I can't imagine calling DoDataExchange or writing custom message handlers for anything.  I'm not sure many MFC coders know not only how easy it is to write UI in .Net, but also how flexible it is.  Only thing is -- I wish you could just override a method and see every window message being sent to the window.  Guess what-  you can do that!  Just override protected WndProc and you have all the power in the world.  Man, imagine having to add message handlers into a table (using defines you must remember beforehand) for each message you wanted and pointing to a new method.  What a hassle.  I think its almost too easy in .Net.  Programmers in .Net have a tendency to be wasteful and create GDI objects (such as Fonts or Brushes) without cleaning them up -- but I digress.

    On the "be careful, don't do that" front, do not call SetWindowLong(GWL_WNDPROC) on a background thread (that is -- a thread other than the one that owns the window handle).  There is a threading issue with DispatchMessage and SetWindowLong(GWL_WNDPROC) that could cause your app to AV.  To change the wndproc or subclass/unsubclass you should do this via a window message so the SetWindowLong call gets executed on the UI thread -- or -- ensure that DispatchMessage is not being called on the window handle you are changing the wndproc.  Support article on this is incoming soon.

    And finally, on the "be careful and DO that" front, please remember to call Dispose on all disposable objects.  If you do not call dispose explicitly, you will allow the object to be garbage collected.  Not only does this decrease performance because your peak workingset is higher, but you are also exposing yourself to potentially nasty threading issues because, yes, the finalizer runs on a background thread.  (see previous 2 issues for examples).  Finalizers also typically have different logic than the explicit dispose and finalizers are difficult to write not only because of the threading issues -- but also because finalization order is not guaranteed.  For example, if A references B and B references C and C references A which one will be finalized first when none of the three are referenced by outside objects?  The answer is that it is not deterministic -- and you need to remember that when writing your finalizer.  More on this later.

    That's it for now -- happy Mother's day!

     

     

     

     

     

  • Deployment help! in VS 2003

    I recieved a request for information and deployment and our deployment expert pointed me to this share.  Let me know if it helps.

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/netcfdeployment.asp

  • Wireless MP3 is HERE!

    I sent this as feedback on another persons blog -- but I've got to plug in some free advertising for Creative Labs.  This setup is incredible.

    http://us.creative.com/products/product.asp?category=119&subcategory=121&product=9192

    It took me about 15 minutes to set up over my existing 802.11 (wireless) network at home.  It would have taken less, but I have security enabled on my network, so I had to plug in the reciever into my computer's USB port to configure it.  The setup consists of 2 peices of hardware:

    1.  The reciever  (just like an 802.11 base station, but instead of an ethernet output, it gives stereo output)

    2.  A RF remote with LCD.  You don't need line of sight with this and you can browse your entire library.  My only complaint with this is that there is no pageUP/DOWN.  But “SmartPlaylists” and favorites makes navigation much easier.

    There is nothing like lying down listeneing to your music and browsing for the next song on your remote.  Truly a new experience that you quickly get used to.  I don't think I will ever have another CD again.  Any CD I get as a gift will get downloaded to an MP3 file and then promptly placed in the trashbin.  Oh wait, my car still can't play MP3's.  Oh well, I guess thats where all my CDs will wind up.

     

  • Having assembly binding problems?

    If you're getting assembly load errors and cannot figure out why, you can use the fusionlog viewer to help figure it out.  The fusion log will show exactly how all managed assemblies were resolved.

    Check out:

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cptools/html/cpgrffusionlogviewerfuslogvwexe.asp

    This includes retargeted assemblies.  For example, if you run your device application on the desktop, compact framework assemblies are “retargeted” to desktop versions -- but only if fusion policy was applied. 

    If you get a cryptic assembly load error when trying to run your app, try using the above log viewer.  It can save you a few hours.

     

  • Custom controls for Devices in VS 7.1

    A workmate and I were talking in the hallway and he mentioned that he saw several newsgroup postings that implied that the lack of device custom control support in VS 7.1 was intentional.  This couldn't be anything further from the truth!

    Although we do support custom controls in VS 7.1, its quite difficult to accomplish.  I've added a link to the left  (“Custom Control Creation for Smart Devices in VS 7.1“ that guides you through the painful process.

    The truth of the matter is that device custom controls are an extremely difficult problem.  The problem is that a control may use P/Invokes which are platform-dependant and will not function correctly on the desktop.  In fact the Compact Framework uses many P/Invoke calls internally (as does the desktop framework).  But coming in MDC (mobility devcon), we will be showcasing some features that some people have already seen at PDC last year.  Custom controls will be much easier than in 7.1.  In fact, most of the time you won't even realize any difference between coding against the desktop framework versus the compact framework.  And the foundation of why is based on retargetting.  More on retargeting next time.

     

  • First Post

    This is my first BLOG entry! I am a developer on the Visual Studio for Smart Devices team. My main areas of expertise are designer infrastructure, custom controls, and a hodgepodge of other areas such as Intellisense and post-compilation verification. We are very busy as MDC is approaching and we want to show off all the great work our whole team has been doing. Hope to see you there!

© 2008 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker