David Connell

Software Developer - Red Gate Software

  • How to add to the list of available SQL Servers

    Posted Monday, March 10, 2008 11:03 AM | 0 Comments

    Have you ever wanted to add to the list of SQL Servers found in Red Gate's SQL Compare, SQL Data Compare, SQL Doc or even SQL Data Generator?

    If so, here goes.

    All you need to do is add the name of the SQL Server instance to the registry. The application looks for servers named 0,1,2,3.... under ‘HKCU\Software\Red Gate\Shared\Servers'. You will also need to add an offset which has a limit of 10. If you are adding more than one server, increment the offset by 1 and add a new key for each subsequent server with the next consecutive number upto a maximum of 9.

    In the following example I will add a new server with a customized connection string to specify a port number over TCP/IP.

    Start up your regedit.exe, and navigate to ‘HKCU\Software\Red Gate\Shared\Servers'. (You may need to create the last part.)

    Add "0" as a string with the server that you wish to add eg:

    Add or modify "offset" (DWORD) to 1

    So now my registry looks like

    Now Refresh your list of SQL Servers and this new item has been added.

    There you go, it was that simple.

  • How to write a Generator for SQL Data Generator 1.0 Beta

    Posted Tuesday, January 15, 2008 11:09 AM | 0 Comments

    In this article we will develop our own generator and understand the basic concepts and ideas behind developing generators for SQL Data Generator. Specifically we will write an ISBN number generator for SQL Data Generator 1.0 Beta, that can generate unique ISBN-10 style strings. You will see that writing and developing your own Generators is simple and easy.

    It is assumed that you are proficient at C# 2.0, have a good understanding of.NET and have access to SQL Data Generator on your machine.

    To test your code you will also need access to either Microsoft SQL Server 2000 or Microsoft SQL Server 2005.

    (Please note that there may be some changes between Beta and final release, as a result any generators developed may need to be modified and rebuilt for the final release.)

    All the code is available in the attached C# project.

    Architecture

    Here is a simple diagram of the architecture of the product.

     

    (insert image)

     

    This discussion is about the plug-in architecture for the generators.

     

    The engine defines a series of interfaces and a generator must implement a series of interfaces in order for the engine to consider it to be a Generator. At start-up a specified directory is scanned for DLLs. Each of these DLLs in turn is loaded up and reflection is used to see if any public classes implement these interfaces. If this is the case then they are considered to be generators and accessible to the rest of the system.

     

    There are a series of interfaces defined by the engine. Each interface is very light-weight. As the developer of the generator, it's up to you to decide how many of these interfaces you wish to implement.

    Basic Interface "IGenerator"

    This interface must be implemented in order to consider that your class is a generator. Here is the definition of the interface:

    namespace RedGate.SQLDataGenerator.Engine.Generators
    
    {
          
    public interface IGenerator IEnumerable
          {

          }
    }

    You must also implement a special constructor that takes a single parameter of type GeneratorParameters. This parameter describes the SQL field in the Table that is being assigned. You are at liberty to throw exceptions etc within your constructor if your code decides for some reason that it is not happy.

    In order for you generator to appear within the UI you must also add a simple Generator attribute to your class.

    You will also need to add the SupportSqlType for non string based generators. (This will probably not be required for the final version.). This attribute can be added multiple times and is used to define the targeted SQL Type. Currently the enumeration is based on the SqlTypes defined in the SQL Compare Engine.

    Here is the code for producing random values for the 8 times table, between 0 and 1024.

    namespace MyNameSpace
    
    {
        [SupportSqlType(SqlType.Integer64)]
        [Generator(typeof(int), "Generic", "8 times table", "8, 0, 16, 256, ...")]
        
    public class SimpleGenerator1 IGenerator
        {
            
    public SimpleGenerator1(GeneratorParameters parameters)
            
    {
            }

            
    public System.Collections.IEnumerator GetEnumerator()
            
    {
                Random r 
    = new Random(0);
                
    while (true)
                
    {
                    yield 
    return r.Next(01024) * 8;
                
    }
            }
        }
    }

    The Generator attribute should only be defined once per class and defines the the type of the .NET result, the Category that the generator is placed in, the name and the description of the generator in the UI.

    Constructor

    The GeneratorParameters gives access to the actual field. This allows the code to verify that lengths and types are consistent. This is likely to change for the final release.

    GetEnumerator

    The easiest way to implement this code is via the yield statement. As a result the code is very straightforward. The example here never runs out of values. However in reality this is not always possible. The engine is designed to cater for a limited number of values from the GetEnumerator. In addition it is fine for the GetEnumerator to throw exceptions in order communicate errors.

    Interface "ISeedableGenerator"

    This interface allows the generator to specify a seed. This allows the generator to generate different random data each time.

     

    Here is the definition of the interface:

           public interface ISeedableGenerator
    
           {
                  
    int Seed { getset}
           }
    A typical implementation would be:
    
            public int Seed
    
            {
                get { 
    return m_Seed}
                set { m_Seed 
    value;  }
            }



    This seed is then used to initialize the Random class, in the GetEnumerator.
     
    The engine will automatically define a seed to the generator at initialization.
    (Each column will have its own seed, hence allowing the same generator being
    assigned multiple times within a table, but each column producing differing results.)
     
    Please see SimpleGenerator2.cs for the full code implementation.

    Interface "IUniqueableGenerator"

     
    This interface allows the generator to specify if the data generated is unique. This allows the
    generator to generate a unique value. 
     
    Here is the definition of the interface:
          public interface IUniqueableGenerator
    
           {
                  
    bool Unique { getset
    }
           }



    A typical implementation would be:

            public bool Unique
    
            {
                get { 
    return m_Unique
    }
                set { m_Unique 
    value
    }
            }


    The GetEnumerator code now becomes more complex as there are two versions.

    The non-unique version, as before, and the new Unique version. For the unique version

    we just iterate in increments of one over the whole range. Please see SimpleGenerator3.cs

    for the full code implementation.

     

    This generator can now be assigned to unique fields. The engine automatically configures

    the Unique flag on when the generator is assigned.

     

    A grid Control is used for the UI . This can be controlled via the standard Microsoft Attributes.

    There are some examples in this code.

     

    Putting this altogther - ISBN Generator

     

    The ISBN number generator is very simple and takes a similar approach to the

    previous examples. However there is one point of interest which is how to

    define unique values.

     

    The engine has a particularly useful set of classes in the namespace

    RedGate.SQLDataGenerator.Engine.Generators.Support, called UniqueValues

    and RandomValues. These classes generate either a unique or non-unique sequence

    from a specified seed from zero up to 1 billion.

     

    This number is then used as the basis of the ISBN. Extra random numbers are added

    the end of the ISBN to make up the required length.

     

    This tack-tick allows a better distribution of random unique values as compared

    to SimpleGenerator3 example.

     

    Please check out ISBNGenerator.cs for a full copy of the source code.

    This example is not complete and for example could be extended to support

    the newer style ISBN-13.

     

    Other Bits And Bobs

    The INullableGenerator interface allows the generator to generate a certain

    percentage of SQL Null values. There is a base abstract class called BaseGenerator

    which does a lot of work for the developer, however it may or may not make it into

    the final release. (See SimpleGenerator4.cs for an example, this code is a fully fledged

    Times Table Generator that allows Nulls etc to be generated.)

     

    Summary to write your own Generator

    1. Start up Visual Studio
    2. Create a Class Library .NET Project
    3. Add references to RedGate.SQLDataGenerator.Engine and RedGate.SQLCompare.Engine
    4. Point the output DLL to %Program Files%\Red Gate\SQL Data Generator 1\Generators\ .

    (Assuming a standard installation)

    1. Create a public class that implements IGenerator
    2. Add the class attributes
    3. Implement the constructor
    4. Implement the method GetEnumerator
    5. Link to download .NET Project


  • SQL Data Generator 1 (Beta) - Adding UK Style Address Generators

    Posted Thursday, January 10, 2008 12:42 PM | 0 Comments

    We are currently running a Beta of SQL Data Generator, and some people have requested UK style data generators. So I have put together a small zip file that generates the following data:-

    UK Post Codes
    UK Counties
    UK Cities
    UK Towns
    In order to install this add in, please carry out the following:

    Stop SQL Data Generator
    Download the following file UKConfig.zip
    Unzip the contents into the folder "%Program Files%\Red Gate\SQL Data Generator 1\Config"
    Restart SQL Data Generator and the new generators are now ready to be used.
    We are thinking about setting up mechanisms to allow users to share generators.

    Regards

    David Connell

  • Performance and Multiple Assigment in C#

    Posted Wednesday, January 25, 2006 5:13 PM | 1 Comments

    I was recently thinking if I really liked to use the Multiple assignment in 'C#' or if it was less readable. In 'C' this syntax was popular as it tended to lead to smaller and quicker code.
    So I wondered in C# if the multiple assigment also lead to quicker code. So I quickly wrote some test cases to see which style of code was quicker to execute...

    I took a rather simplistic model to profile, in order to make the tests more repeatable and easy to follow.

    The .NET code followed the followed the structure:
    [STAThread] static void Main(string[] args) 
                int Count1;
                int  Count2;
                int  Count3;
                int  Count4;
                int  Count5;
                int  Count6;
                int  Count7;
                int  Count8;
    
                Count1 =
                Count2 =
                Count3 =
                Count4 =
                Count5 =
                Count6 =
                Count7 =
                Count8 = 100;
                
    			
                DateTime time = DateTime.Now;
                for (long i = 0; i < 1000000000; i++)
                {
                    int  x = 100;
                    Count1 = 
                    Count2 = 
                    Count3 = 
                    Count4 = 
                    Count5 = 
                    Count6 = 
                    Count7 = 
                    Count8 = x;
                }
    
                TimeSpan span = DateTime.Now - time;
                Console.WriteLine("Time taken{0}", span.TotalMilliseconds / 1000.0);
    } 

    Test .NET 1.1
    Debug Mode
    .NET 1.1
    under the debugger
    .NET 1.1
    Release Mode
    Count1=x;Count2=x;..... 7 7 4.5
    Count1=Count2=Count3 ...=x; 9.5 9.5 4.5
     
    Results are in seconds
    I did test out directly setting the values eg Count1=100; Count2=100; etc as well as using strings. These variations appeared to give similar results. I carried out the same expermiment under .NET 2 and the results were similar just slightly slower in debug mode.
    The machine that carried out the tests was an Intel 3.4Ghz P4 running XP SP2 with 1GB RAM.


    I guess as ever this proves that it is more important to write readable code that requires the minimum of maintanance, rather than trying to outwit the compiler/CLR.
    So would I use the multiple assignment? Probably not as much as I used to, but when it leads to easier to read code then yes.
  • Adding your own .NET exceptions to Visual Studio

    Posted Monday, January 09, 2006 1:01 PM | 0 Comments

    You can easily add your own exceptions into Visual Studio so that you do not need to add them in each time into your project when debugging your code.

    I have only checked this against Visual Studio 2003, but it looks like it would works the same in Visual Studio 2000 and Visual Studio 2005. BR>Start up regedit and go to
    
    My Computer\HKLM\SOFTWARE\Microsoft\VisualStudio\7.1\
    AD7Metrics\Exception\{449EC4CC-30D2-4032-9256-EE18EB41B62B}

    Then

  • add in the new Folder, eg MyCustomExceptions.
  • Add in two DWORD keys, State and Code.
  • Now add in the new exceptions as folders each with their own keys of State and Code.
    Here is an example registry exported file for two exceptions generated by the ANTLR system…

    
    Windows Registry Editor Version 5.00
    
    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AD7Metrics\
    Exception\{449EC4CC-30D2-4032-9256-EE18EB41B62B}\
    Common Language Runtime Exceptions\Antlr] "Code"=dword:00000000 "State"=dword:0000000c [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AD7Metrics\
    Exception\{449EC4CC-30D2-4032-9256-EE18EB41B62B}\
    Common Language Runtime Exceptions\Antlr\antlr.MismatchedTokenException] "Code"=dword:00000000 "State"=dword:0000000c [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AD7Metrics\
    Exception\{449EC4CC-30D2-4032-9256-EE18EB41B62B}\
    Common Language Runtime Exceptions\Antlr\antlr.NoViableAltException] "Code"=dword:00000000 "State"=dword:0000000c
  • Copying File Security Attributes

    Posted Tuesday, December 20, 2005 11:49 AM | 0 Comments

    Last week I was investigating writing out files and what happens if I over-write an existing file.

     

    I remembered back from my MFC days that CDocument makes up a little internal C++ class called CMirrorFile. This is a very clever class that when overwriting an existing file, it copies the original file to a temporary file, writes out the new file and then deletes the temporary file. (However if the serialization of the data fails then the user has an existing copy of their original file. For more information check out doccore.cpp in the MFC source code.)

     

    When developing a .NET version of this code I found that the original MFC code did not work as expected.

     

    The MFC code made two calls to GetFileSecurity. The first to find out how much space is needed to be allocated in order to get the security information. The second call then gets the security information.

     

    Unfortunately the call to GetFileSecurity returned back 0 (Last Error Code was set to 0x7a - The data area passed to a system call is too small). However the resultant length was set correctly.

     

    Below is .NET version of how to copy file securities. So far I have only tested this code on XP.

     

    using System;

    using System.Runtime.InteropServices;

     

     

    namespace Test

    {

      using DWORD = System.UInt32;

      using PSECURITY_DESCRIPTOR = System.IntPtr;

      public class Security

      {

        enum SECURITY_INFORMATION  : int

        {

          DACL_SECURITY_INFORMATION = 4,

        }

     

        [DllImport("advapi32.dll", SetLastError=true)]

          [return: MarshalAs(UnmanagedType.Bool)]

         static extern bool GetFileSecurity(

           string lpszUsername,

           SECURITY_INFORMATION info,

           PSECURITY_DESCRIPTOR descriptor,

           DWORD val2,

           out uint length);

     

        [DllImport("advapi32.dll", SetLastError=true)]

          [return: MarshalAs(UnmanagedType.Bool)]

        static extern bool SetFileSecurity(string lpszUsername,

          SECURITY_INFORMATION info, PSECURITY_DESCRIPTOR descriptor);

     

        static public void CopyFileSecurity(string fileName,
    string targetFileName)

        {

          uint dwLength = 0;               

          bool result = GetFileSecurity(fileName,
          SECURITY_INFORMATION.DACL_SECURITY_INFORMATION,
          IntPtr.Zero, 0, out dwLength);

          int lastError = Marshal.GetLastWin32Error() ;

     

          //Console.WriteLine(string.Format("Last Error = {0}", lastError));

     

          if ( ( result || lastError == 0x7a) && dwLength > 0)

          {

            PSECURITY_DESCRIPTOR pSecurityDescriptor =
            System.Runtime.InteropServices.Marshal.
    AllocHGlobal((int) dwLength);

            if (GetFileSecurity(fileName,
              SECURITY_INFORMATION.DACL_SECURITY_INFORMATION,
              pSecurityDescriptor, dwLength, out dwLength))

            {

              SetFileSecurity(targetFileName,
                SECURITY_INFORMATION.DACL_SECURITY_INFORMATION,
                pSecurityDescriptor);

            }

            System.Runtime.InteropServices.Marshal.FreeHGlobal
       (pSecurityDescriptor);

          }

        }

      }

    }

    For some more information check out Microsoft Article Q163253 and Paul DiLascia’s article.
  • Mapping of .NET Assembly attributes to File Version Information Attributes.

    Posted Wednesday, November 30, 2005 12:43 PM | 0 Comments

    .NET automatically generates a FileInformation resource when building the assembly. Here is a mapping of the .NET attribute to the corresponding named element in VersionInformation and the name that Windows Explorer puts on its UI.

    Assembly Attribute VersionInfo Windows Explore name
    AssemblyTitle FileDescription Description
    AssemblyDescription Comments Comments
    AssemblyConfiguration
    AssemblyCompany CompanyName Company
    AssemblyCopyright LegalCopyright Copyright
    AssemblyTrademark LegalTrademarks Legal Trademarks
    AssemblyCulture Makes up Block header.(Typically Language Neutral)
    AssemblyVersion Assembly Version
    FILEVERSION
    PRODUCTVERSION
    FileVersion File version/File Version
    ProductVerion Product Version
    AssemblyProduct ProductName Product Name
    Project/OutputFile OriginalFilename InternalName
  • Visual Studio & Zoomin.....

    Posted Thursday, November 24, 2005 11:26 AM | 0 Comments

    I used to like the old Zoomin utility that came with Visual Studio 6.... Anyway looks like this little applet is no longer shipped in Visual Studio 2003 or Visual Studio 2005. I have recently come across a replacement utility at code project - better zoomin.
    It does allow you to zoomin a specific part of the screen, and in my opinion is easier to use than the accessibility magnifier.
  • Wild card registering COM dlls

    Posted Wednesday, November 23, 2005 1:04 PM | 0 Comments

    When developing a COM based application sometimes it’s sometimes useful to register all the DLLs/OCX at once.
    From a command prompt
    C:\develep\project\release\>for %i in (*.dll,*.ocx) do regsvr32.exe %i /s
    This will try to register all dll’s even if they are not COM dlls. This is fine as the registration will fail silently.You can change the above command to register COM servers eg
    C:\develep\project\release\>for %i in (*.exe) do %i -RegServer 
    
  • Debugging Arbitrary thread stacks in .NET

    Posted Wednesday, November 23, 2005 9:21 AM | 0 Comments

    Check out John Robbins November 05 Bugslayer article.. He has ported his excellent SUPERASSRT code from native C++ into C#.
    Part of his solution uses CDB.(a command line version of WinDBG)
    I have previously found producing minidump files very useful in tracking down native code problems, so I will be trying out his new C# implementation very soon.
  • Explorer and Environment variables

    Posted Wednesday, November 23, 2005 9:05 AM | 0 Comments

    You can type environment variables into the the Explorer Address bar and it is expanded into its full path.
    Typical examples include:

     

    Environment Variable  Meaning Example
    %TEMP% Temp directory C:\TEMP
    %TMP% Temp directory C:\TEMP
    %USERPROFILE% Current Profile C:\Documents and Settings\David Connell
    %WINDIR% Windows Directory  C:\Windows

    You may have some company specific enviroment variables, check it out by going to a command prompt and typing in "SET"
    These variables can also be used in a command prompt eg

    C:\>MD %USERPROFILE%\TEST
    But be warned when using environment variables on a command line with the TAB key the system always seemed to scan the temporary directory!
    Thanks to Dan A for showing me that.
  • Always set Warnings as Errors

    Posted Friday, November 18, 2005 4:08 PM | 0 Comments

    How many of us have developed some code only to find the compiler outputs some warnings such as unused parameter, or unreachable code and just thought “yup, I’ll fix it tomorrow”. Unfortunately tomorrow never comes and the warning just get left in. Before you know it when you build a project there are hundreds of warnings and the new ones just get missed.

    As a result I have found that the only way forward is to set warnings as errors and crank the warning level as high as possible.

    It’s a bit radical however developers must then either fix the warnings or suppress them.(Developers will only suppress warnings when there is no way round and do not suppress warnings unnecessarily.)


    It maybe a painful road but in the end the builds are always better and easier to understand because there are never any warnings.

  • Testing for empty String

    Posted Friday, November 18, 2005 1:14 PM | 0 Comments

    I was speaking with James a few days ago about strings and testing for them being empty, and as usual we had an interesting discussion. At the end of it I thought I better go off and write some test code to find out some real numbers. I investigated three environments. .NET 1.1 .NET 2.0 and MFC7.1.
    I took a rather simplistic model to profile, in order to make the tests more repeatable and easy to follow.

    The .NET code followed the followed the structure:
    [STAThread] static void Main(string[] args) 
    { 
        DateTime time = DateTime.Now; 
        string stringVal = ""; 
        for (int nIndex = 0; nIndex < 1000000000; nIndex++) 
        { 
            if (stringVal == string.Empty) 
            { 
            } 
        } 
        double TimeTaken = (DateTime.Now-time).TotalMilliseconds; 
        Console.WriteLine(string.Format("Taken {0}", TimeTaken)); 
    } 

    Yes that’s right the loop is 1 billion.
    The results were as follows rounded to the nearest second. (Nb. An empty loop took around 0.5 second). The C++ code was very similar bar using things like CString etc....

    Test .NET 1.1 .NET 2.0
    stringVal.Length == 0 7 4
    stringVal == “” 11 11
    stringVal == String.Empty 11 17
    string.IsNullOrEmpty(stringVal) 13.5* 9
     
    Test Native C++/MFC & CString
    stringVal.IsEmpty() 17
    string.GetLength==0 13
    stringVal[0] == ‘\0’ 25
     
    Test Native C++/MFC & TCHAR
    strVal[0] == ‘\0’ 2

    (Nb. (*) For the .NET 1.1 IsNullOrEmpty test, I wrote a small class to simulate this function)
    The machine that carried out the tests was an Intel 3.4Ghz P4 running XP SP2 with 1GB RAM.


    This showed me a few things
  • NET performs exceedingly well in comparison to native code
  • Writing easy to read and maintainable code is better than trying to write optimum performing code
  • I would use IsNullOrEmpty(stringVal) because it is the most readable and explicit.

  • For additional information check out www.gotdotnet.com.
  • XP SP1/SP2 system DLL's

    Posted Wednesday, November 09, 2005 9:44 AM | 0 Comments

    Last week I found out that sometimes XP SP1 dlls are labelled as xpsp2.

    A client had DLLs that were labelled xpsp2 for example

    comctl32.dll  6.0 (xpsp2.050831-1533) c:\windows\winsxs\
    x86_microsoft.windows
    .common-controls_6595b64144ccf1df_6.0.2600.
    1740_x-ww_7cb8ab44\comctl32.dll 

    However just because the DLLs are labelled xpsp2 this does not mean that they are Service Packl 2.Infact they are late XP Service Pack 1 DLLs!

  • Common Controls and "invalid window class name"

    Posted Wednesday, November 09, 2005 12:09 PM | 0 Comments

    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 different color.)
    Load default DLLs (eg ole32.dll, msvcrt.dll, mscoree.dll, kernel32.dll …) 
    LoadLibraryExW(
    C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll,0,8) LoadLibraryA(SHLWAPI.dll) LoadLibraryExW(comctl32.dll,0,0) Register Common Control Window classes RegisterClassW(12e58c) Style 4003, menu class msctls_progress32 LoadLibraryExW(comctl32.dll,0,0) (loaded many times but fine as it
    return back Handler to the original)
    Lots of stuff……
    LoadLibraryExW(C:\WINDOWS\System32\SQLSRV32.dll,0,8) Load loads of DLLs LoadLibraryA(C:\WINDOWS\System32\COMCTL32.DLL) LoadLibraryExA(C:\WINDOWS\System32\COMCTL32.DLL,0,0) LoadLibraryExW(C:\WINDOWS\System32\COMCTL32.DLL,0,0) Gets the windows class of the common controls…. GetClassInfoW(77340000,msctls_progress32,12c9b0) Does some more stuff FreeLibrary (C:\WINDOWS\System32\SQLSRV32.dll) Free all allocated libs FreeLibrary(c:\Windows\system32\comctl32.dll)
    (Unregisters all common controls) GetClassInfoW(77340000,msctls_progress32,12e730) GetClassInfoW(,,) -> c04f Error = 0 UnregisterClassW(msctls_progress32,77340000) Program then continued until attempted to display the progress bar GetClassInfoW(0,msctls_progress32,1074de0) GetClassInfoW(,,) -> 0 Error = 1411 (class not registered)

    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.
More Posts Next page »

















<July 2008>
SuMoTuWeThFrSa
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789
DML Trigger Status Alerts
 When databases suddenly stop working, it can be for a number of different reasons. Human error plays a... Read more...

Dr Richard Hipp, Geek of the Week
 Simple-Talk's Geek of the Week is Dr Richard Hipp. His code is probably running on your PC, and running... Read more...

Message Hygiene in Exchange Server 2007
 Around four out of every five email messages are spam. Now that the nuisance threatens to engulf what... Read more...

Optimizing the Exchange Environment - Send us your Tips
 We are running a monthly Exchange top tips competition where you can win a $50 Amazon voucher. Read more...

How to Track Down Deadlocks Using SQL Server 2005 Profiler
 It is irritating, sometimes alarming, for the user to be confronted by the 'deadlock message' when a... Read more...