Last week came across a weird issue. A .NET 1.1 program was throwing up a weird exception when running on XP SP1, with a manifest. (The problem disappeared if the manifest was removed). We were sent the stack trace
exception name: System.ComponentModel.Win32Exception
description: Invalid window class name.
handler: user interface
------------------------------ STACK TRACE BEGINS
------------------------------
at System.Windows.Forms.WindowClass.RegisterClass()
at System.Windows.Forms.WindowClass.Create(
String className, Int32 classStyle)
at System.Windows.Forms.NativeWindow.CreateHandle(CreateParams cp)
at System.Windows.Forms.Control.CreateHandle()
at System.Windows.Forms.ProgressBar.CreateHandle()
at System.Windows.Forms.Control.CreateControl(
Boolean fIgnoreVisible)
at System.Windows.Forms.Control.CreateControl(
Boolean fIgnoreVisible)
at System.Windows.Forms.Control.CreateControl()
at System.Windows.Forms.Control.OnVisibleChanged(EventArgs e)
at System.Windows.Forms.ScrollableControl.OnVisibleChanged(
EventArgs e)
at System.Windows.Forms.Control.OnParentVisibleChanged(EventArgs e)
at System.Windows.Forms.Control.OnVisibleChanged(EventArgs e)
at System.Windows.Forms.ScrollableControl.OnVisibleChanged(
EventArgs e)
at System.Windows.Forms.Form.OnVisibleChanged(EventArgs e)
at System.Windows.Forms.Control.SetVisibleCore(Boolean value)
at System.Windows.Forms.Form.SetVisibleCore(Boolean value)
at System.Windows.Forms.Control.set_Visible(Boolean value)
at System.Windows.Forms.Form.ShowDialog(IWin32Window owner)
This highlighted several issues. Firstly that native windows are only created in .NET when they are needed and not in the constructor or InitializeComponent. Which seems to makes sense given that Windows are a precious resources and should only be created at the last moment, just surprising. The next issue was that it was apparent from the stack trace that the ProgressBar was failing to be created but why? So we dusted down Lutz Roeder’s excellent reflector tool and found out exactly what was going on in RegisterClass. It is as follows
private void RegisterClass()
{
NativeMethods.WNDCLASS_D wndclass_d1 = new NativeMethods.WNDCLASS_D();
if (NativeWindow.userDefWindowProc == IntPtr.Zero)
{
string text1 = (Marshal.SystemDefaultCharSize == 1) ?
"DefWindowProcA" : "DefWindowProcW";
NativeWindow.userDefWindowProc =
UnsafeNativeMethods.GetProcAddress(
UnsafeNativeMethods.GetModuleHandle("user32.dll"),
text1);
if (NativeWindow.userDefWindowProc == IntPtr.Zero)
{
throw new Win32Exception();
}
}
if (this.className == null)
{
wndclass_d1.hbrBackground =
UnsafeNativeMethods.GetStockObject(5);
wndclass_d1.style = this.classStyle;
this.defWindowProc = NativeWindow.userDefWindowProc;
this.windowClassName = "Window." +
Convert.ToString(this.classStyle, 0x10);
this.hashCode = 0;
}
else
{
NativeMethods.WNDCLASS_I wndclass_i1 =
new NativeMethods.WNDCLASS_I();
bool flag1 = UnsafeNativeMethods.GetClassInfo(
IntPtr.Zero, this.className, wndclass_i1);
int num1 = SafeNativeMethods.GetLastError();
if (!flag1)
{
throw new Win32Exception(num1, SR.GetString("InvalidWndClsName"));
}
wndclass_d1.style = wndclass_i1.style;
wndclass_d1.cbClsExtra = wndclass_i1.cbClsExtra;
wndclass_d1.cbWndExtra = wndclass_i1.cbWndExtra;
wndclass_d1.hIcon = wndclass_i1.hIcon;
wndclass_d1.hCursor = wndclass_i1.hCursor;
wndclass_d1.hbrBackground = wndclass_i1.hbrBackground;
wndclass_d1.lpszMenuName =
Marshal.PtrToStringAuto(wndclass_i1.lpszMenuName);
this.defWindowProc = wndclass_i1.lpfnWndProc;
this.windowClassName = this.className;
this.hashCode = this.className.GetHashCode();
}
string[] textArray1 = new string[5]
{
Application.WindowsFormsVersion,
".",
this.windowClassName,
".app",
Convert.ToString(AppDomain.CurrentDomain.GetHashCode(), 0x10)
} ;
this.windowClassName = string.Concat(textArray1);
this.windowProc = new NativeMethods.WndProc(this.Callback);
wndclass_d1.lpfnWndProc = this.windowProc;
wndclass_d1.hInstance = UnsafeNativeMethods.GetModuleHandle(null);
wndclass_d1.lpszClassName = this.windowClassName;
if (UnsafeNativeMethods.RegisterClass(wndclass_d1) == IntPtr.Zero)
{
this.windowProc = null;
throw new Win32Exception();
}
this.registered = true;
}
After a couple of false starts we realized that the exception was being thrown by the code UnsafeNativeMethods.GetClassInfo. You may be wondering how we were able to get in and find out what was going on?
Well the answer was given by
Dan Archer and he pointed us in the direction of Detours. (Check out
research.microsoft.com/sn/detours/ for more information. There are some example detours projects on code projects.)
The Detours project effectively allows the developer to log any Win32 API call.
So we decided to log the following API calls LoadLibraryExW, LoadLibraryExA, LoadLibraryW, LoadLibraryA, FreeLibrary, RegisterClassW, GetClassInfoW, UnregisterClassW. We then asked the customer to re-run the program but with this logging enabled.
We were then sent back the following log (I have sumerized it for ease of reading. Comments are in .)
LoadLibraryExW(
C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll,0,8)
LoadLibraryA(SHLWAPI.dll)
LoadLibraryExW(comctl32.dll,0,0)
RegisterClassW(12e58c) Style 4003, menu class msctls_progress32
LoadLibraryExW(comctl32.dll,0,0)
LoadLibraryExW(C:\WINDOWS\System32\SQLSRV32.dll,0,8)
LoadLibraryA(C:\WINDOWS\System32\COMCTL32.DLL)
LoadLibraryExA(C:\WINDOWS\System32\COMCTL32.DLL,0,0)
LoadLibraryExW(C:\WINDOWS\System32\COMCTL32.DLL,0,0)
GetClassInfoW(77340000,msctls_progress32,12c9b0)
FreeLibrary (C:\WINDOWS\System32\SQLSRV32.dll)
FreeLibrary(c:\Windows\system32\comctl32.dll)
GetClassInfoW(77340000,msctls_progress32,12e730)
GetClassInfoW(,,) -> c04f Error = 0
UnregisterClassW(msctls_progress32,77340000)
GetClassInfoW(0,msctls_progress32,1074de0)
GetClassInfoW(,,) -> 0 Error = 1411
It was now apparent that SQKSRV32.DLL directly loaded up the old/un-themed common controls dll, which had the nasty side effect of registering and then un-registering the common controls windows classes when the DLL was unloaded. Unfortunately the program had not finished and so it failed when trying to access the common controls for the first time. (This then explained why running without a manifest the program worked correctly.)
We were able to verify that this was the problem by asking the user for their version of MDAC, (which happened to be MDAC 2.8) and then installing it on our test machine. At last we were able to then replicate this user issue. It appears to be XP SP1a/MDAC 2.8 issue.