Thursday, July 09, 2009

More on string.Concat vs the + operator in C#

A post of mine from 3 years ago about the performance differences between using string.Concat and the string class’s + operator got its first comment yesterday so I thought I’d flesh out what I said there to clarify what happens. First, here’s a little test program to show different ways to concatenate strings.

  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("a" + "b" + "c" + "d");
      Console.WriteLine(string.Concat("a", "b", "c", "d"));

      string a = "a";
      string b = "b";
      string c = "c";
      string d = "d";
      Console.WriteLine(string.Concat(a, b, c, d));
      Console.WriteLine(a + b + c + d);
    }
  }

So now lets look at the IL generated from that, using our old friend Reflector.

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 4
    .locals init (
        [0] string a,
        [1] string b,
        [2] string c,
        [3] string d)
    L_0000: nop 
    L_0001: ldstr "abcd"
    L_0006: call void [mscorlib]System.Console::WriteLine(string)
    L_000b: nop 
    L_000c: ldstr "a"
    L_0011: ldstr "b"
    L_0016: ldstr "c"
    L_001b: ldstr "d"
    L_0020: call string [mscorlib]System.String::Concat(string, string, string, string)
    L_0025: call void [mscorlib]System.Console::WriteLine(string)
    L_002a: nop 
    L_002b: ldstr "a"
    L_0030: stloc.0 
    L_0031: ldstr "b"
    L_0036: stloc.1 
    L_0037: ldstr "c"
    L_003c: stloc.2 
    L_003d: ldstr "d"
    L_0042: stloc.3 
    L_0043: ldloc.0 
    L_0044: ldloc.1 
    L_0045: ldloc.2 
    L_0046: ldloc.3 
    L_0047: call string [mscorlib]System.String::Concat(string, string, string, string)
    L_004c: call void [mscorlib]System.Console::WriteLine(string)
    L_0051: nop 
    L_0052: ldloc.0 
    L_0053: ldloc.1 
    L_0054: ldloc.2 
    L_0055: ldloc.3 
    L_0056: call string [mscorlib]System.String::Concat(string, string, string, string)
    L_005b: call void [mscorlib]System.Console::WriteLine(string)
    L_0060: nop 
    L_0061: ret 
}

OK, so looking at the first method where we concatenate string literals using the + operator and we can see the compiler helps us out by concatenating the strings at compile time, which is going to be as optimal as possible. The second example shows that the compiler doesn’t do this magic when we use string.Concat so string.Concat is actually slower in this scenario.

Now if we look at the next examples where we concatenate string variables, the generated IL is exactly the same! So the performance characteristics are likely to be somewhat similar to say the least. Things get more interesting when you get beyond 4 strings since there is no version of string.Concat that takes more than 4 parameters, so they have to be pushed into an array but the result is the same, the + operator generates the exact same code as string.Concat.

So I can’t see a scenario where you’d want to use string.Concat (unless you’re particularly fond of it) and if string concatenation performance is an issue, you probably should be using the StringBuilder class.

Wednesday, July 08, 2009

Encryption in Metastorm BPM

A question came up on the Metastorm forums about encrypting sensitive data contained in custom variables so I thought I’d see what I could come up with. I took this C# code and translated it to JScript.NET, which looks something like this

import System;
import System.IO;
import System.Security.Cryptography;
import System.Text;
import eWork.Engine.ScriptObject;

package Encrypt.Encrypt
{
    public class Encryption
    {
        private static const password : String = "password";
        public static function Encrypt( ework: SyncProcessData, args: Object[] ) : Object
        {
            // args[0] - string to encrypt
            // returns the encrypted string

            var encrypt : Encryption = new Encryption(password);
            return encrypt.Encrypt(args[0]);
        }

        public static function Decrypt( ework: SyncProcessData, args: Object[] ) : Object
        {
            // args[0] - string to decrypt
            // returns the decrypted string
            if (args[0] == "")
                return "";

            var encrypt : Encryption = new Encryption(password);
            return encrypt.Decrypt(args[0]);
        }

        function Encryption(password : String)
        {
            GenerateKey(password);
        }

        private var Key : byte[];
        private var Vector : byte[];

        private function GenerateKey(password : String)
        {
            var sha : SHA384Managed  = new SHA384Managed();
            var b : byte[] = sha.ComputeHash(new ASCIIEncoding().GetBytes(password));

            Key = new byte[32];
            Vector = new byte[16];

            System.Array.Copy(b, 0, Key, 0, 32);
            System.Array.Copy(b, 32, Vector, 0, 16);
        }

        public function Encrypt(plainText : String) : String
        {
            var data : byte[] = new ASCIIEncoding().GetBytes(plainText);

            var crypto : RijndaelManaged = new RijndaelManaged();
            var encryptor : ICryptoTransform = crypto.CreateEncryptor(Key, Vector);

            var memoryStream : MemoryStream = new MemoryStream();
            var crptoStream : CryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);

            crptoStream.Write(data, 0, data.Length);
            crptoStream.FlushFinalBlock();

            crptoStream.Close();
            memoryStream.Close();

            return Convert.ToBase64String(memoryStream.ToArray());
        }

        public function Decrypt(encryptedText : String) : String
        {
            var cipher : byte[] = Convert.FromBase64String(encryptedText);

            var crypto : RijndaelManaged = new RijndaelManaged();
            var encryptor : ICryptoTransform = crypto.CreateDecryptor(Key, Vector);

            var memoryStream : MemoryStream = new MemoryStream(cipher);
            var crptoStream : CryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Read);

            var data : byte[] = new byte[cipher.Length];
            var dataLength : int = crptoStream.Read(data, 0, data.Length);

            memoryStream.Close();
            crptoStream.Close();

            return (new ASCIIEncoding()).GetString(data, 0, dataLength);
        }
    }
}

Then all that is required is to decrypt the string when the form is loaded and encrypt it when the form is saved, like so

%sensitive:=%ScriptEval(JScript.NET,,%Procedure.Name,%MapName,"Encrypt.Encrypt.Encryption.Decrypt",%sensitive )

%sensitive:=%ScriptEval(JScript.NET,,%Procedure.Name,%MapName,"Encrypt.Encrypt.Encryption.Encrypt",%sensitive)

Now the user should see the unencrypted text and the encrypted version will be stored in the database. You will also need to decrypt the data anywhere else you need to use it.

One thing to realise at this point is that the system is still not secure. Although it will stop casual viewers who just run a query against the custom variable table, it won’t stop a more professional hacker. The script text is also stored in the database, so a hacker can have a look at that and find the password used to encrypt/decrypt the data. A more secure implementation would store the password in a location that only the engine account has access to, assuming the engine account is also locked down.

Download the demo procedure

Saturday, July 04, 2009

MooZoom with image maps

MooZoom is a nice piece of JavaScript that adds zoom and pan functionality to images, built on top of MooTools. It wasn’t exactly what I needed. I didn’t want the zoom/pan to be constrained to the original size, I wanted the ability to zoom out beyond the original size and I wanted image maps to be handled correctly. I’m quite pleased with the results and you can download the source here.

To use it, in your image simply set

class="moozoom"

Friday, July 03, 2009

Day 21 – where I get offered a job

3 weeks in and I get a job offer, pretty good going I think. I have the weekend to think about it. And given that a bird in the hand is worth two in the bush (even if the birds in the bush have really nice plumage) I will probably accept it.

For any other IT job hunters in the current climate, here’s my advice.

  • Throw your CV onto every job website out there.
  • Make sure everyone you know is aware you are looking for work, have no shame!
  • If you have a website or blog, make it obvious you are looking for work. Play the percentages game, every person who sees you are looking for work may be a potential employer.
  • Don’t demand to get paid as much as you were paid before. Assuming you’re out of work like me, your current income is zero or thereabouts, so your previous salary is pretty much irrelevant.
  • Accept all interviews. Even if the job isn’t a perfect fit, it’s good to get back into the interviewing groove, which will help when a better role turns up. And who knows, a job that doesn’t appear perfect on paper may turn out to be better than expected.
  • When you have an interview and you get asked about something you have no knowledge of, go off and investigate it. OK, it’s too late for that particular interview but it might come up again.
  • Learn about other technologies which may have passed you by in the past. Today the guy I spoke to said he was impressed with my use of the JavaScriptSerializer class, which I’d only started playing with the day before my technical test.

Wednesday, July 01, 2009

Resizing an image map when zooming an image

There are some cool libraries out there for zooming images in a HTML document, but none that I’ve seen handle resizing an image map attached to the image. My research may be incomplete so I might be recreating the wheel here, but this simple solution seems to fix the problem. Now I need to integrate with one of those libraries.

First up my HTML looks like this

      <map id="map" name="map">
        <area coords="15,92,568,247" alt="Blah" href="javascript:alert('hello');" />
        <area coords="18,259,546,432" alt="Blah" href="javascript:alert('hello 2');" />
      </map>
      <input type="button" value="+" onclick="javascript:ZoomIn();"/>
      <input type="button" value="-" onclick="javascript:ZoomOut();"/><br />
      <img src="Highlight cells.png" usemap="#map" id="image" />

And then there is some JavaScript to do the resizing, that looks like this

  <script type="text/javascript">
    function ZoomIn() {
      Zoom(1.1);
    }

    function ZoomOut() {
      Zoom(0.9);
    }

    function Zoom(amount) {
      // resize image
      var image = document.getElementById('image');
      image.height = image.height * amount;

      // resize image map
      var map = document.getElementById('map');
      for (var i = 0; i < map.areas.length; i++) {
        var area = map.areas[i];
        var coords = area.coords.split(',');
        for (var j = 0; j < coords.length; j++) {
          coords[j] = coords[j] * amount;
        }
        area.coords = coords[0] + ',' + coords[1] + ',' + coords[2] + ',' + coords[3];
      }
    }
  </script>

Day 19 – More calls

Quite a few calls from agents today, including some jobs that sound very interesting. What I’ve noticed is that I never hear anything back about the roles that sounds particularly interesting, just the run of the mill stuff. I guess the interesting jobs get absolutely deluged with CVs so can pick and choose people who look to be perfect. My first call about Metastorm work as well, so there is some work out there on that side of my skills. And I will return to Croydon for yet another interview on Friday.