I just downloaded the ASP.NET MVC Preview 5 bits from Codeplex and started on my first experiment.

One of the first things I did was to modify the default AccountController to use the new Form Posting and Form Validation features of the Preview 5, somebody probably overlooked updating those :)

If anyone else wants the reworked code, feel free to copy paste.

Note this was something done during lunch break in a hurry, it seems to all work logically, but it's possible I'll have to tune it a bit later on.

Controller:

 
[HandleError]
[OutputCache(Location = OutputCacheLocation.None)]
public class AccountController : Controller
{
    public AccountController()
        : this(null, null)
    {
    }

    public AccountController(IFormsAuthentication formsAuth, MembershipProvider provider)
    {
        FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();
        Provider = provider ?? Membership.Provider;
    }

    public IFormsAuthentication FormsAuth
    {
        get;
        private set;
    }

    public MembershipProvider Provider
    {
        get;
        private set;
    }

    [Authorize]
    [AcceptVerbs("GET")]
    public ActionResult ChangePassword()
    {
        ViewData["Title"] = "Change Password";
        ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;

        return View();
    }

    [Authorize]
    [AcceptVerbs("POST")]
    public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
    {
        // Basic parameter validation
        if (String.IsNullOrEmpty(currentPassword))
        {
            ViewData.ModelState.AddModelError("currentPassword", currentPassword, "You must specify a current password.");
        }
        if (newPassword == null || newPassword.Length < Provider.MinRequiredPasswordLength)
        {
            ViewData.ModelState.AddModelError("newPassword", newPassword, String.Format(CultureInfo.InvariantCulture,
                     "You must specify a new password of {0} or more characters.",
                     Provider.MinRequiredPasswordLength));
        }
        if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
        {
            ViewData.ModelState.AddModelError("newPassword", newPassword, "The new password and confirmation password do not match.");
        }

        if (ViewData.ModelState.IsValid)
        {
            // Attempt to change password
            MembershipUser currentUser = Provider.GetUser(User.Identity.Name, true /* userIsOnline */);
            bool changeSuccessful = false;
            try
            {
                changeSuccessful = currentUser.ChangePassword(currentPassword, newPassword);
            }
            catch
            {
                // An exception is thrown if the new password does not meet the provider's requirements
            }

            if (changeSuccessful)
            {
                return RedirectToAction("ChangePasswordSuccess");
            }
            else
            {
                ViewData.ModelState.AddModelError("password", currentPassword, "The current password is incorrect or the new password is invalid.");
            }
        }

        // If we got this far, something failed, redisplay form
        ViewData["Title"] = "Change Password";
        ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;

        return View();
    }

    public ActionResult ChangePasswordSuccess()
    {
        ViewData["Title"] = "Change Password";

        return View();
    }

    [AcceptVerbs("GET")]
    public ActionResult Login()
    {
        ViewData["Title"] = "Login";
        ViewData["CurrentPage"] = "login";

        return View();
    }

    [AcceptVerbs("POST")]
    public ActionResult Login(string username, string password, bool? rememberMe)
    {
        // Basic parameter validation
        if (String.IsNullOrEmpty(username))
        {
            ViewData.ModelState.AddModelError("username", username, "You must specify a username.");
        }

        if (ViewData.ModelState.IsValid)
        {
            // Attempt to login
            bool loginSuccessful = Provider.ValidateUser(username, password);

            if (loginSuccessful)
            {
                FormsAuth.SetAuthCookie(username, rememberMe ?? false);
                return RedirectToAction("Index", "Home");
            }
            else
            {
                ViewData.ModelState.AddModelError("*", username, "The username or password provided is incorrect.");
            }
        }

        // If we got this far, something failed, redisplay form
        ViewData["Title"] = "Login";
        ViewData["CurrentPage"] = "login";
        ViewData["username"] = username;

        return View();
    }

    public ActionResult Logout()
    {
        FormsAuth.SignOut();
        return RedirectToAction("Index", "Home");
    }

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.User.Identity is WindowsIdentity)
        {
            throw new InvalidOperationException("Windows authentication is not supported.");
        }
    }

    [AcceptVerbs("GET")]
    public ActionResult Register()
    {
        ViewData["Title"] = "Register";
        ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;

        return View();
    }

    [AcceptVerbs("POST")]
    public ActionResult Register(string username, string email, string password, string confirmPassword)
    {
        // Basic parameter validation
        if (String.IsNullOrEmpty(username))
        {
            ViewData.ModelState.AddModelError("username", username, "You must specify a username.");
        }

        if (String.IsNullOrEmpty(email))
        {
            ViewData.ModelState.AddModelError("email", email, "You must specify an email address.");
        }

        if (password == null || password.Length < Provider.MinRequiredPasswordLength)
        {
            ViewData.ModelState.AddModelError("password", password, String.Format(CultureInfo.InvariantCulture,
                     "You must specify a password of {0} or more characters.",
                     Provider.MinRequiredPasswordLength));
        }

        if (!String.Equals(password, confirmPassword, StringComparison.Ordinal))
        {
            ViewData.ModelState.AddModelError("confirmPassword", confirmPassword, "The password and confirmation do not match.");
        }

        if (ViewData.ModelState.IsValid)
        {

            // Attempt to register the user
            MembershipCreateStatus createStatus;
            MembershipUser newUser = Provider.CreateUser(username, password, email, null, null, true, null, out createStatus);

            if (newUser != null)
            {
                FormsAuth.SetAuthCookie(username, false /* createPersistentCookie */);
                return RedirectToAction("Index", "Home");
            }
            else
            {
                ViewData.ModelState.AddModelError("*", username, ErrorCodeToString(createStatus));
            }
        }

        // If we got this far, something failed, redisplay form
        ViewData["Title"] = "Register";
        ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
        ViewData["username"] = username;
        ViewData["email"] = email;

        return View();
    }

    public static string ErrorCodeToString(MembershipCreateStatus createStatus)
    {
        // See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
        // a full list of status codes.
        switch (createStatus)
        {
            case MembershipCreateStatus.DuplicateUserName:
                return "Username already exists. Please enter a different user name.";

            case MembershipCreateStatus.DuplicateEmail:
                return "A username for that e-mail address already exists. Please enter a different e-mail address.";

            case MembershipCreateStatus.InvalidPassword:
                return "The password provided is invalid. Please enter a valid password value.";

            case MembershipCreateStatus.InvalidEmail:
                return "The e-mail address provided is invalid. Please check the value and try again.";

            case MembershipCreateStatus.InvalidAnswer:
                return "The password retrieval answer provided is invalid. Please check the value and try again.";

            case MembershipCreateStatus.InvalidQuestion:
                return "The password retrieval question provided is invalid. Please check the value and try again.";

            case MembershipCreateStatus.InvalidUserName:
                return "The user name provided is invalid. Please check the value and try again.";

            case MembershipCreateStatus.ProviderError:
                return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

            case MembershipCreateStatus.UserRejected:
                return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

            default:
                return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
        }
    }
}

// The FormsAuthentication type is sealed and contains static members, so it is difficult to
// unit test code that calls its members. The interface and helper class below demonstrate
// how to create an abstract wrapper around such a type in order to make the AccountController
// code unit testable.

public interface IFormsAuthentication
{
    void SetAuthCookie(string userName, bool createPersistentCookie);
    void SignOut();
}

public class FormsAuthenticationWrapper : IFormsAuthentication
{
    public void SetAuthCookie(string userName, bool createPersistentCookie)
    {
        FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
    }
    public void SignOut()
    {
        FormsAuthentication.SignOut();
    }
}

Login View:

 
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="GuildSite.Views.Account.Login" %>


    

Login

Please enter your username and password below. If you don't have an account, please <%= Html.ActionLink("register", "Register") %>.

<%= Html.ValidationSummary()%>
">
Username: <%= Html.TextBox("username") %>
Password: <%= Html.Password("password") %>
Remember me?

Register View:

 
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Register.aspx.cs" Inherits="GuildSite.Views.Account.Register" %>


    

Account Creation

Use the form below to create a new account.

Passwords are required to be a minimum of <%=Html.Encode(ViewData["PasswordLength"])%> characters in length.

<%= Html.ValidationSummary()%>
">
Username: <%= Html.TextBox("username") %>
Email: <%= Html.TextBox("email") %>
Password: <%= Html.Password("password") %>
Confirm password: <%= Html.Password("confirmPassword") %>
 

Apparently I'm not the only one having problems with the categories after a Wordpress 2.6 upgrade.

So, time to give something back to the Wordpress community, some screenshots on how I fixed it.

First of all, I upgraded from 2.2.3 to 2.6, it's possible this solution will work for you, but I don't make any promises.

I started by going to phpMyAdmin and having a look at the wptermtaxonomy table, and noticed all the descriptions where missing.

When I took a look at the backup file I made, more specifically, the wp_categories piece, I noticed the counts and ids matched up.

Empty Descriptions

I then manually edited each record, just hit the pencil icon, and filled in all my descriptions again. If you know some SQL you could do this faster, but anyway, everyone can do it manually :)

Corrected Descriptions

After I've done this, I visited my admin section and noticed the Descriptions were filled in again. But there was no Name, and all my posts still had empty categories linked to them.

To fix this, visit http://your-site/wp-admin/categories.php?action=edit&cat_ID=1 where 1 is the category id to edit. Fill in a Name and a Slug, the url name for your category, and save it.

Repeat this process for all your categories, until they all have a Name again.

Edit Names and Slugs

And that's it! Categories fixed, posts linked, category urls working again.

All Fixed

Good luck!

 

A moment ago, I decided to upgrade to the latest Wordpress version, I was running a bit behind on 2.2.3 :)

The upgrade went quite smooth, all my plugins still work. But suddenly all my categories became empty strings. And the /tags/ urls didn't work anymore either.

After looking around a bit, I noticed my categories were stored in the wptermtaxonomy table, with empty descriptions. Thankfully I had a backup and manually entered the descriptions for all of them.

At this point, their names showed up in the admin section again, but the post weren't linked to them yet, neither did the /tags/ work.

A little more looking around, and I figured out to access categories.php?action=edit&cat_ID=20 for each category, where I had to enter the category name and category slug.

For some reason, each category's slug was suddenly named -2-2-2-2-2-2-2-2-2-2-2-2-2, with increasing -2 per category.

After doing all that, my /tags/ work again, and my posts seem to be linked again :)

If you notice anything broken, please comment to let me know!

 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
Today I was made aware by Filip, a colleague of mine, about the importance of columns used in a where clause with a compound index. I decided to investigate this a bit more in detail, with proper profiling and comparisons on a large data set.

I created a test table, DemoTable, which looks as follows:

[sql]
ColumnA varchar(6)
ColumnB int
ColumnC int
ColumnD varchar(3)
..30 more columns..
[/sql]

The primary key consists out of ColumnA, ColumnB and ColumnC. Which implicitly means it acts as a compound index.

There are about 6.5 million records in the table.

Full Primary Key Lookup

The most obvious query to get data from this table is a simple select, using all values of the primary key, as follows:

[sql]
SELECT *
FROM DemoTable
WHERE ColumnA = 'DCU'
AND ColumnB = 1
AND ColumnC = 1
[/sql]

Taking a look at the query plan, this does exactly what most people expect it to do, it uses a very efficient Index Seek to pick out the matching record.

A, B, C

Partial Primary Key Lookup

A variation to the above query, is the following one, using only part of the primary key.

[sql]
SELECT *
FROM DemoTable
WHERE ColumnA = 'DCU'
AND ColumnB = 1
[/sql]

The query plan shows us that this query is also very efficient, since it can make full use of the index to get the results back.

A, B

Out of curiosity and to be thorough, I also checked the results of the following query:

[sql]
SELECT *
FROM DemoTable
WHERE ColumnA = 'DCU'
[/sql]

Same story, our index is ordered on ColumnA, ColumnB and ColumnC, so it can easily use a binary search on it and give back all results for ColumnA.

A

The fun stuff begins when we don't just remove columns from the where clause in the same order the index is created, for example this query:

[sql]
SELECT *
FROM DemoTable
WHERE ColumnA = 'DCU'
AND ColumnC = 1
[/sql]

SQL Server will first use an efficient index seek to get all results for the correct ColumnA, since the index is ordered by ColumnA first, and will then search in the remaining results for the correct ColumnC.

In the query plan, you can identify this as the Predicate, which displays ColumnC. In the end, this is still an efficient query, but if the majority of your queries would be filtering on ColumnA and ColumnC, it would be a good idea to change your column order to ColumnA, ColumnC, ColumnB in the index. This way, the overall efficiency of your index would increase.

A, C

Another variation of a partial key lookup is the following query:

[sql]
SELECT *
FROM DemoTable
WHERE ColumnB = 1
AND ColumnC = 1
[/sql]

And it's at this point you can say goodbye to performance. To get the requested results, SQL Server will do a full scan on every value in your index to find matches. Making it go over 6.5 million pieces of data in my example.

If lots of your queries would be like this, I'd suggest to consider changing the column order, or add an index on ColumnB and ColumnC. Keep in mind that adding indexes has a drawback on performance of INSERTs and DELETEs, it's up to each project to define this delicate balance.

B, C

Partial Primary Key Lookup Sorted

Another case I wanted to check, was how SQL Server handles ORDER BY clauses with regards to columns in an index.

I started simple by doing:

[sql]
SELECT *
FROM DemoTable
WHERE ColumnA = 'DCU'
AND ColumnB = 1
ORDER BY ColumnC
[/sql]

As expected, SQL Server simply does an index scan for ColumnA and ColumnB and returns the results, since they are already ordered by ColumnC in the index. As efficient as possible.

A, B Order C

A small variation to this query leaves out the first column of the index, resulting in:

[sql]
SELECT *
FROM DemoTable
WHERE ColumnB = 1
ORDER BY ColumnC
[/sql]

Again, since our WHERE clause is not using the columns in the order of the index, we are invoking a full index scan for the value of ColumnB. Afterwards SQL Server calls Sort on the results to get them in the correct order. As inefficient as possible :)

B, Order C

Conclusions

It should be clear that the order of your columns in an index are very important. I've made up some guidelines for myself regarding this:


  • Place the most limiting columns first in an index.
    By this, I mean to first determine which queries I will be running against the database, determining their frequency and then ordering the columns according to the most frequently run queries.
     


  • Place an additional index if needed
    After I've ordered my columns to be as efficient as possible and see that another set of queries is run almost as frequently that they need optimization, possibly add another index on a subset of the columns in the first index. Keeping in mind for which purpose the table should be optimised (eg: SELECT vs INSERT/DELETE).



I'll be more than happy to receive some comments on possible additional tests to analyze and make my conclusions better.
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
Over the years, plenty has been written about string performance, lots of comparisons between String.Concat and StringBuilder. Today I decided to do some of my own research into the subject and contribute to the knowledge already out there. More specifically, I'll be taking a look at the memory usage for various concatenation methods and compiler optimizations used to generate the IL.

The test scenario I defined consists out of several methods, each returning the same string. The string I created is supposed to resemble a real-life scenario. I identified five different ways of concatenating strings for my test. I will be taking a look at the numbers when calling each method once and inside a very small loop of 50 calls, which is another real-life number in my case.

Single line concatenation.

The easiest way of concatenating strings together, by simply putting a plus sign between them.

[csharp]
public string GetPlussedString()
{
string myString = "SELECT column1,"
+ " column2,"
+ " column3,"
+ " column4,"
+ " column5,"
+ " column6,"
+ " FROM table1 t1"
+ " JOIN table2 t2"
+ " ON t1.column1 = t2.column1";
return myString;
}
[/csharp]

Although it seems like we are creating 9 string instances, the compiler optimizes this into the following IL:

[code]
.method public hidebysig instance string GetPlussedString() cil managed
{
.maxstack 1
.locals init (
[0] string myString)
L_0000: ldstr "SELECT column1, column2, column3, column4, column5, column6, FROM table1 t1 JOIN table2 t2 ON t1.column1 = t2.column1"
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ret
}
[/code]

In reality, we created one string instance and returned it, which is about the most efficient way we can achieve.

When profiling the test application, I couldn't even find a call to GetPlussedString in the profiler, which makes me believe the runtime even optimized this.

GetPlussedString Single Call

In total, our application created 113 string instances and barely used any memory.

Running this in the loop gives the following result:

GetPlussedString Multiple Calls

Important to note is the fact that we still have 113 string instances. This is because .NET used String Interning on my string and simply returns a reference to that instance over and over.

Variable concatenation.

Another frequently used way of concatenating strings is by appending a variable with the += operator for each line.

[csharp]
public string GetPlussedVarString()
{
string myString = "SELECT column1,";
myString += " column2,";
myString += " column3,";
myString += " column4,";
myString += " column5,";
myString += " column6,";
myString += " FROM table1 t1";
myString += " JOIN table2 t2";
myString += " ON t1.column1 = t2.column1";
return myString;
}
[/csharp]

Things become messy here, take a look at the generated IL for this code:

[code]
.method public hidebysig instance string GetPlussedVarString() cil managed
{
.maxstack 2
.locals init (
[0] string myString)
L_0000: ldstr "SELECT column1,"
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldstr " column2,"
L_000c: call string [mscorlib]System.String::Concat(string, string)
L_0011: stloc.0
L_0012: ldloc.0
L_0013: ldstr " column3,"
L_0018: call string [mscorlib]System.String::Concat(string, string)
L_001d: stloc.0
L_001e: ldloc.0
L_001f: ldstr " column4,"
L_0024: call string [mscorlib]System.String::Concat(string, string)
L_0029: stloc.0
L_002a: ldloc.0
L_002b: ldstr " column5,"
L_0030: call string [mscorlib]System.String::Concat(string, string)
L_0035: stloc.0
L_0036: ldloc.0
L_0037: ldstr " column6,"
L_003c: call string [mscorlib]System.String::Concat(string, string)
L_0041: stloc.0
L_0042: ldloc.0
L_0043: ldstr " FROM table1 t1"
L_0048: call string [mscorlib]System.String::Concat(string, string)
L_004d: stloc.0
L_004e: ldloc.0
L_004f: ldstr " JOIN table2 t2"
L_0054: call string [mscorlib]System.String::Concat(string, string)
L_0059: stloc.0
L_005a: ldloc.0
L_005b: ldstr " ON t1.column1 = t2.column1"
L_0060: call string [mscorlib]System.String::Concat(string, string)
L_0065: stloc.0
L_0066: ldloc.0
L_0067: ret
}
[/code]

Every += operation translates into a call to String.Concat() creating a new temporary string.

Looking at the profiler we end up with 129 string instances, which is 16 more than the our comparison base. These strings can be split up into 8 coming from the 8 calls to String.Concat and from having 8 more strings declared in code.

GetPlussedVarString Single Call

Calling this 50 times quickly shows the downside of this method. We end up with 408 additional string instances, 400 coming from 50*8 calls to String.Concat and our original 8 extra strings, which got Interned by the way.

GetPlussedVarString Multiple Calls

Note the explosion in memory size used for this simple example, 73kB vs 16kB.

I strongly discourage the use of the += operator for string concatenation in these scenarios.

String.Concat(array) concatenation.

A less used way of concatenating strings is by using one of the String.Concat overloads which accept a string array.

[csharp]
public string GetConcatedString()
{
string[] pieces = new string[] {
"SELECT column1,",
" column2,",
" column3,",
" column4,",
" column5,",
" column6,",
" FROM table1 t1",
" JOIN table2 t2",
" ON t1.column1 = t2.column1"
};
return String.Concat(pieces);
}
[/csharp]

This is a more efficient variation of String.Concat by using it explicitly with a string array, as can be seen in the following IL:

[code]
.method public hidebysig instance string GetConcatedString() cil managed
{
.maxstack 3
.locals init (
[0] string[] pieces,
[1] string[] CS$0$0000)
L_0000: ldc.i4.s 9
L_0002: newarr string
L_0007: stloc.1
L_0008: ldloc.1
L_0009: ldc.i4.0
L_000a: ldstr "SELECT column1,"
L_000f: stelem.ref
L_0010: ldloc.1
L_0011: ldc.i4.1
L_0012: ldstr " column2,"
L_0017: stelem.ref
L_0018: ldloc.1
L_0019: ldc.i4.2
L_001a: ldstr " column3,"
L_001f: stelem.ref
L_0020: ldloc.1
L_0021: ldc.i4.3
L_0022: ldstr " column4,"
L_0027: stelem.ref
L_0028: ldloc.1
L_0029: ldc.i4.4
L_002a: ldstr " column5,"
L_002f: stelem.ref
L_0030: ldloc.1
L_0031: ldc.i4.5
L_0032: ldstr " column6,"
L_0037: stelem.ref
L_0038: ldloc.1
L_0039: ldc.i4.6
L_003a: ldstr " FROM table1 t1"
L_003f: stelem.ref
L_0040: ldloc.1
L_0041: ldc.i4.7
L_0042: ldstr " JOIN table2 t2"
L_0047: stelem.ref
L_0048: ldloc.1
L_0049: ldc.i4.8
L_004a: ldstr " ON t1.column1 = t2.column1"
L_004f: stelem.ref
L_0050: ldloc.1
L_0051: stloc.0
L_0052: ldloc.0
L_0053: call string [mscorlib]System.String::Concat(string[])
L_0058: ret
}
[/code]

This method uses 9 more string instances than our base, which is already better than using += resulting in 16.

These 9 come from the 8 additional strings defined in the code and 1 coming from the single call to String.Concat().

GetConcatedString Single Call

Calling this 50 times will result in 58 additional strings compared to our base, coming from 50 calls to String.Concat() and our 8 additional strings in code (again, Interned @ work).

GetConcatedString Multiple Calls

Internally the array overload for String.Concat() will first count the needed length for the result and then create a temporary string variable of the correct length, where as the previous method could not use this optimization since it were 8 separate calls.

StringBuilder.Append() concatenation.

Method number four uses a StringBuilder to create a string, as demonstrated in plenty of tutorials.

[csharp]
public string GetBuildString()
{
StringBuilder builder = new StringBuilder();
builder.Append("SELECT column1,");
builder.Append(" column2,");
builder.Append(" column3,");
builder.Append(" column4,");
builder.Append(" column5,");
builder.Append(" column6,");
builder.Append(" FROM table1 t1");
builder.Append(" JOIN table2 t2");
builder.Append(" ON t1.column1 = t2.column1");
return builder.ToString();
}
[/csharp]

The not so interesting IL for this method simply shows the creation of the object and several method calls.

[code]
.method public hidebysig instance string GetBuildString() cil managed
{
.maxstack 2
.locals init (
[0] class [mscorlib]System.Text.StringBuilder builder)
L_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldstr "SELECT column1,"
L_000c: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0011: pop
L_0012: ldloc.0
L_0013: ldstr " column2,"
L_0018: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_001d: pop
L_001e: ldloc.0
L_001f: ldstr " column3,"
L_0024: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0029: pop
L_002a: ldloc.0
L_002b: ldstr " column4,"
L_0030: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0035: pop
L_0036: ldloc.0
L_0037: ldstr " column5,"
L_003c: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0041: pop
L_0042: ldloc.0
L_0043: ldstr " column6,"
L_0048: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_004d: pop
L_004e: ldloc.0
L_004f: ldstr " FROM table1 t1"
L_0054: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0059: pop
L_005a: ldloc.0
L_005b: ldstr " JOIN table2 t2"
L_0060: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0065: pop
L_0066: ldloc.0
L_0067: ldstr " ON t1.column1 = t2.column1"
L_006c: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0071: pop
L_0072: ldloc.0
L_0073: callvirt instance string [mscorlib]System.Object::ToString()
L_0078: ret
}
[/code]

Note I am using a default StringBuilder, which defaults to a size of 16 characters.

GetBuildString Single Call

From the profiler we can see this approach created 13 more string instances than our base, from which 8 are again the extra strings in code, one is coming from the final ToString() call and 4 are coming from the internals of the StringBuilder, since it had to increase its capacity 4 times (At 16 characters, 32, 64 and 128).

Interesting to note here is the fact that the usage of a StringBuilder already uses less memory than concatenating with += when using 9 strings. Choosing a good estimate of the target size upon constructing the StringBuilder would have made the difference even bigger.

This becomes even more obvious when comparing the results from the loop:

GetBuildString Multiple Calls

Using 258 more than our base, 8 Interned strings, 50 ToString() calls and 200 increases inside StringBuilder, we can clearly see the StringBuilder being more efficient than += even taking StringBuilder object creation into account. It is however not as efficient than the String.Concat(array) method.

StringBuilder.AppendFormat() concatenation.

And lastly, for my own personal curiosity, I wanted to see the effect of using AppendFormat() versus Append().

[csharp]
public string GetBuildFormatString()
{
// AppendFormat will first parse your string to find {x} instances
// and then fill them in. Afterwards it calls .Append
// Better to simply call .Append several times.
StringBuilder builder = new StringBuilder();
builder.AppendFormat("SELECT {0},", "column1");
builder.AppendFormat(" {0},", "column2");
builder.AppendFormat(" {0},", "column3");
builder.AppendFormat(" {0},", "column4");
builder.AppendFormat(" {0},", "column5");
builder.AppendFormat(" {0},", "column6");
builder.AppendFormat(" FROM {0} t1", "table1");
builder.AppendFormat(" JOIN {0} t2", "table2");
builder.Append(" ON t1.column1 = t2.column1");
return builder.ToString();
}
[/csharp]

This method is the most inefficient method of the pack. First a look at the IL:

[code]
.method public hidebysig instance string GetBuildFormatString() cil managed
{
.maxstack 3
.locals init (
[0] class [mscorlib]System.Text.StringBuilder builder)
L_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldstr "SELECT {0},"
L_000c: ldstr "column1"
L_0011: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
L_0016: pop
L_0017: ldloc.0
L_0018: ldstr " {0},"
L_001d: ldstr "column2"
L_0022: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
L_0027: pop
L_0028: ldloc.0
L_0029: ldstr " {0},"
L_002e: ldstr "column3"
L_0033: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
L_0038: pop
L_0039: ldloc.0
L_003a: ldstr " {0},"
L_003f: ldstr "column4"
L_0044: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
L_0049: pop
L_004a: ldloc.0
L_004b: ldstr " {0},"
L_0050: ldstr "column5"
L_0055: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
L_005a: pop
L_005b: ldloc.0
L_005c: ldstr " {0},"
L_0061: ldstr "column6"
L_0066: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
L_006b: pop
L_006c: ldloc.0
L_006d: ldstr " FROM {0} t1"
L_0072: ldstr "table1"
L_0077: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
L_007c: pop
L_007d: ldloc.0
L_007e: ldstr " JOIN {0} t2"
L_0083: ldstr "table2"
L_0088: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
L_008d: pop
L_008e: ldloc.0
L_008f: ldstr " ON t1.column1 = t2.column1"
L_0094: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0099: pop
L_009a: ldloc.0
L_009b: callvirt instance string [mscorlib]System.Object::ToString()
L_00a0: ret
}
[/code]

Inside the AppendFormat() calls is where the ugly stuff happens. The format is converted to a character array, after which it is scanned for occurrences of {x} and in the end it is being passed to StringBuilder.Append() anyway.

I didn't spent much time trying to extract a conclusion out of the results of this test, since it's bound to perform worse than the previous method anyway, since it's the same logic with extra operations.

GetBuildFormatString Single Call

Interesting to note are the results inside the loop, demonstrating it uses even more memory than += concatenating.

GetBuildFormatString Multiple Calls

Conclusion

The conclusions I made for myself and will be following in my future development are as follows:


  • If you can avoid concatenating, do it!
    This is a no brainer, if you don't have to concatenate but want your source code to look nice, use the first method. It will get optimized as if it was a single string.
     


  • Don't use += concatenating ever.
    Too much changes are taking place behind the scene, which aren't obvious from my code in the first place. I advise to rather use String.Concat() explicitly with any overload (2 strings, 3 strings, string array). This will clearly show what your code does without any surprises, while allowing yourself to keep a check on the efficiency.
     


  • Try to estimate the target size of a StringBuilder.
    The more accurate you can estimate the needed size, the less temporary strings the StringBuilder will have to create to increase its internal buffer.
     


  • Do not use any Format() methods when performance is an issue.
    Too much overhead is involved in parsing the format, when you could construct an array out of pieces when all you are using are {x} replaces. Format() is good for readability, but one of the things to go when you are squeezing all possible performance out of your application.



One conclusion I am not 100 percent sure of is the difference between using String.Concat(array) and using a StringBuilder. It seems using an array incurs less memory overhead than using a StringBuilder, unless the cost of array creation is big, which I couldn't determine in my tests. I'd be more than interested to know if someone could provide more detail on this.

The guidelines in Jouni Heikniemi's article seem to be accurate when comparing between String.Concat(string, string) and StringBuilder, and will be the ones I'll be following, until I get a clear picture of the String.Concat(array) implementation.

Once again, I've uploaded the solution I used as a .zip file, with an additional HTML page displaying the results below each other.
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
I've always been a big fan of Reflector, allowing me to have a look in assemblies to learn new things, debug in case of problems and provide useful information when creating bug reports. Combined with the Reflexil Add-in it's now easy to also modify assemblies yourself.

To demonstrate this, I've created a small CrackMe sample program, which consists out of nothing more then a simple password check. This is how it looks when opened in Reflector, with Reflexil displaying the IL code below it.

Reflexil - CrackMe

Ignoring the fact that the password is out there in the open, let's have a look on how we could easily change the behavior of this assembly.

As you can see on the highlighted line in Reflexil, the instruction will jump to line 11 in case the provided password is false. Let's simply change this to jump when true. Right click the line, select 'Edit...' and change the OpCode to brtrue.s to modify the if statement.

Reflexil - Edit

I'm sure this brings back memories for people doing this years ago in assembler, JNE to JE  :)

To save this change, navigate to the .exe node in Reflector and select 'Save as ...' in the Reflexil dialog.

Reflexil - Save

If you open the new executable in Reflector and have a look at the password check, you'll notice the operator has changed, making all passwords valid except the correct one.

Reflexil - Cracked

I've attached the .zip of this project and the modified file as a small example to demonstrate how powerful good tools are.

This post is the first in a series on protecting intellectual property.
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
Do you find Outlook Today too boring? I do! Let's have a look at how we can transform it into something more visually appealing, something like this: (click the image for a full view)

Outlook Today - David Cumps

But just how do you achieve this? Start by creating a fresh HTML page and add the following just below your title tag:

[html]










[/html]

Afterwards, unleash your graphical talents in Photoshop or any other editor and design something beautiful, designate some zones you would like to have, and convert it to HTML.

At the location you'd like to have your Calendar events, add the following snippet:

[html]






 
 

[/html]

The code to see your Tasks is as follows:

[html]







[/html]

Displaying the message count consists out of two actions, first we have to add it to our layout as follows:

[html]




::

[/html]

Afterwards, have a look at the following registry entries:

[code]
[HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Outlook\Today\Folders]
"0"="\\\\Cumps David\\Inbox"
"1"="\\\\CumpsD\\Inbox"
[/code]

As you can see, I have two entries in my Messages section, displaying my Exchange and POP3 Inbox. Entries are simply numbered with their value being the location of the item you want to count. You can also leave this as it is by default if you wish to.

Displaying the Date is a small piece of Javascript:

[html]

[/html]

I also added some New links next to Messages, Calendar and Tasks. The code for this is as follows:

[html]
New Mail
New Appointment
New Task
[/html]

Additionally you can add external links as well. In my example I am calling two PHP scripts, running on my local webserver, which output some Javascript, my system's uptime and an RSS feed. The following code is an example of an external link and an included PHP script:

[html]
David Cumps

[/html]

You might wonder what my picture is doing there. Well, it's a direct link to my own Contact item, since I can't remember my own phone number, I usually copy paste it from my contact details. This way I simply get to those details faster :)

Once you have completed your layout, place it somewhere on your hard disk or personal webspace and add the following registry key:

[code]
[HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Outlook\Today]
"Url"="file://path_to_your_htm"
[/code]

In my case this results in:

[code]
[HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Outlook\Today]
"Url"="file://C:\\OutlookToday\\cumpsd.htm"
[/code]

The next time you open Outlook, it will load your webpage, ready to be used.

I uploaded a zip file containing the files used in my Outlook Today. Included are two PHP scripts, one to parse an rss feed, and one for the Windows uptime. In the cumpsd.htm file you can also view all available css styles which Outlook supports. Have a look at it and feel free to modify it to get a quick start. If you create something, leave a comment showing off your creation please, I'm always interested in seeing what others accomplish.

Enjoy modifying your Outlook Today! Personalize your computer!
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
Visual Studio is a great editor! But when editing other file formats I miss the coloring.

When working with PHP files, it's great to be able to make a solution to organize php sources, and having code open in tabs, but when they all look black and white, I find it requires more focus on my part when coding.

Long ago, I wrote a piece on getting coloring to work in Visual Studio 2002 and 2003. Somehow I managed to skip doing any PHP development during the entire lifetime of Visual Studio 2005, but recently I had to create something small, and I missed my nice colors. I tried to apply my old method in Visual Studio 2008 (Orcas), and this is the result:

Visual Studio 2008 - PHP

Looks pretty appetizing, doesn't it?

To get this in Visual Studio 2008, do the following:

  • Download vs-php.zip and extract it somewhere.

  • Execute the preferred registry file (php_edit2008.reg for Visual Studio 2008 ofcourse).

  • This will create a File Extension association for PHP files, treating them as C++ files.

  • Copy the usertype.dat to your VS.NET directory. (default C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE)

  • Restart Visual Studio if it was opened.

  • Open a .php file and admire the colors!


For this to work, you need to have C++ support installed! Otherwise the usertype.dat file will not work, since this is specific to .cpp files. You don't need to install everything for C++, only the following will already work:

Visual Studio 2008 - Setup C++

When you look in the registry, you will see that the .php extension looks exactly like the .cpp one, you can also try experimenting with applying the .cs or .html filter to a .php file, but I found them both lacking. Using the .cs value, you will get coloring from C#, but it will also give you lots of syntax warnings. When using the .html one, it doesn't always color the entire file.

If you want to color more keywords, open up the usertype.dat file in a text editor, and simply add more words to it.

Enjoy the extra productivity gain!
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
When using the Windows Search functionality, you can search for words inside files. By default this doesn't seem to work for .sql or .php files however.

To enable this, add the following key to the registry:

HKEY_CLASSES_ROOT\.sql\PersistentHandler\(Default) : {5e941d80-bf96-11cd-b579-08002b30bfeb}

This will associate the plain text filter (System32\Query.dll) to the specific file type, making it possible to search the content of the file. The reason not all extensions have this enabled by default, is due to performance considerations from Microsoft's point of view. I personally haven't noticed any visible performance problems from enabling it for SQL and PHP files.

Log off and log on again. From then on you can use the search box to search inside SQL and PHP files. A quick way to make Windows recognize the change, is by killing the explorer.exe process and starting a new one. That way you don't have to close any programs.

At pilif.ch you can find a nice tool to set the PersistentHandler to the plain text filter.

Give it a try and search for 'SELECT'.
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
Lately I hear about a lot of people using VPC that their keyboard suddenly stops working inside VPC.

There are a different number of keyboard issues I've heard of so far:


  • Keyboard stops working all together.


  • Keyboard freaks out. (CTRL key stuck)


  • Keyboard and mouse lock up.



The first issue is the one I hear the most, and which someone managed to perform on one of my VPC's lately.

It's quite hard to reproduce, if able to at all, but apparently it feels to trigger the most from fullscreen/windowed switching.

But, no more chatting, the (very simple) solution:

Lock and unlock the host computer. Problem fixed, keyboard working again.

The first time someone had a locked keyboard again, I tried this, and it worked! :)


A solution for the other issues are apparently:


  • Freaking: A bug in VPC apparently prevents to let go out the CTRL key when you use the AltGr key to simulate CTRL+ALT (when switching to fullscreen?). Which means in practice Windows sees the CTRL key as being kept down. Rumored solutions are to press CTRL inside the VPC, or/and to use your Right CTRL to do special VPC operations instead of AltGr.


  • Keyboard and mouse lock up: Apparently there's a hotfix somewhere out there, or at least one exists but has to be requested. I remember it being mentioned somewhere, the only comfort I can offer is that there is hope there is a fix for you, but you'll have to search a bit.

 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
The Pocket PC I recently received was completely in French, so I figured 'I'll just change this to English'.

It can't be that hard, can it? Apparently it was trickier then I thought.

The Pocket PC has the OS in it's ROM, and it has limited ROM, so no multilanguages in there.

It quickly became obvious to me the ROM had to be flashed with an English version, but where to get it?

I didn't buy the Pocket PC, so asking Dell to give me an English one probably would fail, and from various messageboards I discovered they won't do it anyway.

So, where to get it? From the Dell site I guessed, in the download section there was an English update for Windows Mobile 2003 Second Edition.

Since this update just flashes the ROM and puts the new version in it, I guessed this was ok.

But when trying to flash it, it started complaining about being the wrong language.

Apparently French can only be upgraded to French, and since I don't speak French fluently this wasn't practical :)

So, search engine to the rescue. I found this post on Aximsite, a site dedicated to Dell Axim resources.

It seemed logical, get the English and French ROM, make the updater believe the English ROM is actually a French language, and flash.

The process looked obvious to me, so, let's get started!

I opened up the French ROM and English ROM in a hex editor and located the differences:



Note:
This is different from the forum post! It's not the first 7 lines you have to copy paste.

Pasting the first 7 lines results in an Integrity Check error. It's enough to change everything before the "AXIM30".

After having modified the English ROM with the new header, I saved it to the French updated directory, overwriting the original French ROM update. (So, now you have a filename which indicates it's a French ROM, but it's actually the English ROM with the French header)

I did the same for the other image (there is a C and an N image).

Now I ran the updater, which did not give me an error about Integrity Check anymore, and also not about wrong language.

It successfully updated the ROM and after the Pocket PC restarted, everything was English!

So, now I have an English Pocket PC :)


Of course, the disclaimer on my blog applies especially to this post, as this is not something you should do quickly if you have no technical skills.

So: I (David Cumps) cannot be held responsible for any damage what-so-ever that might come from this post. You do this at your own risk.
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
Right, someone trying to convince me why Firefox is so much better than IE brought up the issue of being able to type "google searchterm" into his Adress bar and immediately being taken to the site.

Well, here's some news: IE can do that to, along with imdb, whois, vandale, php and everything else that uses parameters as query terms.

How? Look at this registry key and see the light:

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\SearchUrl\google]
@="http://www.google.com/search?q=%s"
" "="+"
"%"="%25"
"&"="%26"
"+"="%2B"

%s is where your search term comes, and the name of the key is the prefix.

Another example to get the hang of it:

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\SearchUrl\imdb]
@="http://us.imdb.com/Find?%s"
" "="+"
"%"="%25"
"&;"="%26"
"+"="%2B"

Got it? Sweet, enjoy your new IE power! ;)

I have uploaded a .reg file which contains altavista, astalavista, cd, download, google, googlenl, imdb, php, sub, vandale, vcd, whois and lucky as search terms. Feel free to invent more :)


Update: Google feeling lucky keyword.

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\SearchUrl\lucky]
@="http://www.google.com/search?q=%s&btnI=I%27m+Feeling+Lucky"
" "="+"
"%"="%25"
"&"="%26"
+"="%2B"
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
With my new laptop I got a screen with a 1920x1200 resolution, so I went out looking for wallpapers for the resolution, because my existing ones degraded in quality with that size.

My search led to Deaddreamer from which I have my current wallpaper as well.

But nowadays he got so many good ones, so I picked up 5 1600x1200 wallpapers. And now I want a random wallpaper each time I logon.

Having some spare time (very rare) I created something small myself, I'm sure there are tools out there to do all that.

Originally I wanted to use PHP to get a random number and set the wallpaper, and then call the php script with a bat file and set it as a scheduled task. But that kept popping up a dos prompt each time it set it, and my wallpaper disappeared after boot (some bad registry settings).

So I used KiXtart to set the wallpaper and then link it with a shortcut which would run minimized.

That worked for one paper, but when I tried the RAND function in KiXtart, it didn't go well.. But I had the php script and bat still there so I let that make a random number and then kill the KiX script with that number as argument.

Result:
Shortcut to wall.bat, which calls wall.php, which makes a random number and calls wallpaper.kix with the number, after which my wallpaper is set, and saved in registry.

I have to shortcut Run as minimized and on logon and every hour, and now I got a nice random wallpaper implementation.

Overly complicated? Maybe, If you have php installed, it's a small solution, otherwise you need a 1.3Mb dll and 24k php.exe in the same dir, which isn't really something bad.

I have zipped everything and written the steps to install it out in a Readme.txt for everyone wanting it as well. I also included the php.exe and dll.

Download it here: RandomWall.zip (706Kb).

And now I have sweeeet super-detailed wallpapers auto-changing without any special timer programs running, but just the Scheduled Tasks :)

Here are my wallpapers:


(convert them to .bmp first, not sure if it's required, but whatever, bmp is nicer, no need for desktop to go in Web mode)

 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
One thing I immediately do on a new pc is get it personalized. This includes changing icons and wallpapers, but also resource hacking the run box and hacking the uxtheme.dll to support custom themes.

Today I'll talk about implementing the Watercolor theme I have been using for a year now, and got a lot of mails on where and how to get it.

First you have to modify UXTheme. This used to be something "difficult" but now it's easy. Get the UXTheme Multi-Patcher and run it.

As always, and on the left side of the blog, I'm not responsible if something goes wrong, everything you do is your responsibility.

Normally, according to the page I just linked, this doesn't work on XP SP2 final, but I ran it anyway and it worked. Just make sure you wait for the Windows File Protection box to come up.

Next you get the Watercolor theme, unzip it and run WatercolorLitev211.exe, it'll install the theme and you can select it from the Desktop Configuration control panel. It packs with several colors, I either have it set to Blue or Ergonomic.

Enjoy your sweet new theme :)


A screenshot from a previous posting demonstrating the theme:

 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
Time for a new article, but not about C# this time.

This time it's about Resource Hacking, more particular, we'll customize the Windows Run box so it will look something like this:



If you're interested, take a look at the article: Custom Run Box (Resource Hacking).
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
It's time for a new article, but not about C# this time. I'll tell you how to modify Windows, we'll change the Run box and make it look like this:



First of all the disclaimer: This article is purely informational, you don't have to do anything because I tell so, everything you do is on your own risk and I am not responsible when anything goes wrong.

With that out of the way, grab the tools if you don't have them yet:


We'll begin by copying shell32.dll to a safe place.

Copy it two times, one which we'll be editing, and one which is a backup.

Open it in ResHacker and you'll see a list on the left side showing all available resources.



Now select the 'Dialog' resource, and look at 1003. You'll notice it's the Run box.



We'll start by adding a bitmap to place on our Run box. Go to 'Bitmap' and select 'Action', 'Add a new Resource'.



Here you select a bmp file and give it the name RUNGFX. You can get the bmp I used here: Runbox.bmp.



Press 'Add Resource' and now you can see it's added.



Now we'll go back to the 'Dialog' 1003, 1033 and replace the existing script with this one:

1003 DIALOGEX 0, 0, 188, 83
STYLE DS_FIXEDSYS | DS_MODALFRAME | DS_NOIDLEMSG | DS_CONTEXTHELP | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION ":: run ::"
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
FONT 8, "MS SHELL DLG"
{
CONTROL "", 12298, COMBOBOX, CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP, 3, 53, 181, 198
CONTROL "R", 12306, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 21, 90, 1, 1
CONTROL "Run", 1, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 3, 67, 59, 14 , 0x00020000
CONTROL "Cancel", 2, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 63, 67, 59, 14 , 0x00020000
CONTROL "Find", 12288, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 124, 67, 59, 14 , 0x00020000
CONTROL "RUNGFX", 0, STATIC, SS_BITMAP | WS_CHILD | WS_VISIBLE | WS_GROUP, 3, 3, 181, 48
}


To make your life easier, and to be sure you don't make a mistake due to your browser word wrapping the script, copy paste it from this text file: runscript.txt.

When you have replaced it you press 'Compile Script'.



And now you'll see the preview has changed to our new runbox!



Press CTRL+S to save the modified dll.

The last thing we need to do is to use Replacer to replace our existing shell32.dll in SYSTEM32 with our new file. You can't just copy it in there because Windows File Protection would replace it with a backup. Therefore we use Replacer, just follow the instructions Replacer shows and you'll be fine. Replacer also prompts you to make a backup, make sure you make one, you never know what can go wrong.

When the file is replaced (Replacer replaces the file itself along with the one in dllcache) you have to reboot and when everything went fine, you now have a new Run box! A really good looking one, different from everyone else.

Some notes:
If you move the controls around, make sure to not place any above or behind the bitmap, it could give problems. Also don't include a bitmap bigger then your box. Make sure you leave a bit of spaces from the edges, when you put the bitmap against the border, it won't show up.
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
For everyone who received .NET Magazine #5, there's an article in there at page 53 about cleaning up your code.

Jan Tielens mentions a ConvertToProperties macro, which is really sweet!

But there has been a printing error or something, the link went missing on page 55 in the bottom box. (http://xxx/...)

So, here is it: Create Property Macro for C#.

The version on the site doesn't correspond to the screenshots, it doesn't use mVariable (any more?), but replaces it with _variable (which I prefer).

Be sure to get this macro! It saves you a lot off time ;)
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
A tool I used a lot on my .exe's when creating VB6 applications is UPX (which stands for the Ultimate Packer for eXecutables).

This is a very valuable .exe that you can just drag-drop your .exe's on and it'll compact them (and make it a bit harder for newbies to decompile your program).

Of course there are Unpackers for the people who really want to decompile your application, but you can at least enjoy the compression that UPX does. I used it on my last project file and it went from 208KB to 77KB.

I used the upx 1.24 windows console version.
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
A long time ago I talked about how great NDoc is.

Quick reminder:
NDoc generates class library documentation from .NET assemblies and the XML documentation files generated by the C# compiler (or with an add-on tool for VB.NET).

I love the tool, I use it for all my documentation in .NET projects.

A few days ago, I had to make a Visual Basic 6 project. And one of the requirements was a documentation. Knowing about NDoc, I went looking for something similar for VB6. There are a lot off tools out there, commercial and free.

But VBDOX is the best in my opinion. It generates the same MSDN style documentation, as HTML with the possibility to compile it into a Windows Help file.

It can use an XML style of commenting, and quickly parses it and generates the documentation.

This resulted in a very nice documentation for my project.
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
You all know those open directories with pictures in it.

And you all know how annoying it can be to view them all by clicking them, or downloading them all to your hd and viewing them there.

Well, here's another way:

Add this .url to your favorites. And when you're on such an open dir, just hit the url and it will show all the .jpg, .gif, .png and .bmp's on that page.

All this actually is, is a javascript as url. This has been tested in IE6, I don't garantee it'll work in other browsers, you can try.

Here's the actual script that's in the link:

[javascript]
var sHTML = 'Fotoviewer\n\n\t\n';
for (x = 0; x < document.links.length; x++) {
link = document.links(x).href.toLowerCase();
if ((link.indexOf('.jpg') != -1) || (link.indexOf('.gif') != -1) || (link.indexOf('.png') != -1) || (link.indexOf('.bmp') != -1)) {
sHTML += '\t\t

\n'
}
}
sHTML += "\t
\n";
document.body.innerHTML=sHTML;
[/javascript]

Or in a compact one line version:

javascript:var%20sHTML='<html><head><title>Fotoviewer</title></head>\n<body>\n\t<div%20align="center">\n';for(x=0;x<document.links.length;x++){link=document.links[x].href.toLowerCase();if((link.indexOf('.jpg')!=-1)||(link.indexOf('.gif')!=-1)||(link.indexOf('.png')!=-1)||(link.indexOf('.bmp')!=-1)){sHTML+='\t\t<img%20src="'+document.links[x].href+'"/><br/><br/>\n'}}sHTML+="\t</div>\n</body></html>";document.body.innerHTML=sHTML;

Update: Added the .toLowerCase() to link.

Update #2: Changed links(x) to links[x] to make it work in other browsers ;)
 
Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
Here's a dilemma:

On one side you want to keep your machine up to date with all latest patches, but then there is "Cumulative Security Update for Internet Explorer (832894)", which disables the user:pass@ way of authentication.

Now, do you update and loose this functionality (which can be handy), or don't apply it but have the other security it fixes unpatched?

Here's what I did:

I patched!

…

But I really, really wanted the user:pass back, and it's even in an RFC MS has linked.

3.1. Common Internet Scheme Syntax

While the syntax for the rest of the URL may vary depending on the
particular scheme selected, URL schemes that involve the direct use
of an IP-based protocol to a specified host on the Internet use a
common syntax for the scheme-specific data:

//:@:/

Some or all of the parts ":@", ":",
":", and "/" may be excluded. The scheme specific
data start with a double slash "//" to indicate that it complies with
the common Internet scheme syntax. The different components obey the
following rules:

user
An optional user name. Some schemes (e.g., ftp) allow the
specification of a user name.

password
An optional password. If present, it follows the user
name separated from it by a colon.

The user name (and password), if present, are followed by a
commercial at-sign "@". Within the user and password field, any ":",
"@", or "/" must be encoded.


The solution? Re-enable it!

Start regedit.

Go to:
HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_HTTP_USERNAME_PASSWORD_DISABLE
to re-enable it for the entire machine,

or go to:
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_HTTP_USERNAME_PASSWORD_DISABLE
to re-enable it for the logged in user.

Now create iexplore.exe and explorer.exe DWORD values and set their value data to 0.

Done, you just got the user:pass@ functionality back.


Update:

As Kent Sharkey writes, the RFC I quoted actually did not specifiy the user:pass possibilty for the HTTP protocol. I'm sorry for that, it's a 'feature' I guess :)

This registry tweak does however not undo the patch, it only reactivates this 'feature', the chr(0) exploit remains fixed with this tweak.

Update2:

Here is a .reg file to re-enable it system-wide.