« Stages Of A New BloggerTestament : Akedah »

More Delphi .NET Migration

10/24/06

  04:21:08 am, by Nimble   , 855 words  
Categories: Thoughts, Programming

More Delphi .NET Migration

Delphi (now available in free 'Turbo Delphi Explorer' editions for old-style Windows coding and new-style .NET coding) has a pretty good migration path from regular Windows to .NET.

Unlike Managed C++, which is one of the very few things that can mix .NET and regular Windows freely (though at a price), it's a relatively all-or-nothing deal. If you've been doing plain point-and-click programming, you may have to do practically nothing in the conversion. If you've been doing parsers and cryptographic algorithms, you probably have some work ahead of you. This is all related to the way .NET managed code works.

One option that playing around with Delphi has given me is converting old Delphi code to .NET form. Converting a scripting language engine is certainly a large challenge.

One of the bigger challenges you may face is the use of PChars, the equivalent of char* in C/C++. They are popular for parsing and scanning because of their simplicity and speed. Managed code prevents you from having true pointers to the middle of anything, part of the effort to ensure buffers stay safe and garbage collection stays working. Gone are functions like StrScan and the like.

One of the approaches to use on these is to use indexes instead of PChars. Inside a parser, you can often get away with it, since the text string is available to all pieces. Alternatively, you can make your own StrScan alternate that takes the string and your index. String is a class in .NET, and you have the functions available to you with code completion by simply typing in your variable name and hitting '.'. "StrScan" can be implemented like this:

type
  TParseIndex = Integer;

function TMyParser.StrScan(AIndex: TParseIndex;
  ACharacter: Char): TParseIndex;
begin
  Result := FParseText.IndexOf(ACharacter,AIndex);
end;

That can help you skip quite a bit of translation if you turn your character indices into 'TParseIndex' instead of PChar.

One point of compatibility contention is that Delphi strings start at 1, based on the way that old Delphi strings used to have a length byte at index 0. So MyString[1] will contain the first character, as you would expect.

If you want to work more closely with the .NET methods, though, it's best to start thinking starting from zero. You can do this with the MyString.Chars[x], which is zero-based.

All strings in .NET are Unicode, which helps for internationalization, but which may not help some of the older techniques you may have, or in cases where you really want plain old 8-byte ASCII. For this, you will want to investigate the System.Text.Encoding property. To get a plain old 8-bit byte array out of a string, you would request it something like this:

var
  MyBytes : array of Byte;
begin
  MyBytes := System.Text.Encoding.ASCII.GetBytes(MyText);
  ...

The reverse of GetBytes is GetString, so you can go back and forth between between the two formats with aplomb, and without having to write a loop to do it yourself.

Delphi for .NET cleverly adds variant support to .NET, which is very helpful for migration purposes. That said, they do it in a somewhat more .NET manner, which is to say that they give you boxed values (e.g. an Integer object) behind the scenes.

This means that any "clever" code that hard-casts a Variant with TVarData in order to get at the bits and pieces inside is going to fail miserably without some modification. If you have been using the variant functions, e.g. VarType, you will be fine. Scripting engines tend to get fancy with this sort of feature, though.

Late-bound COM support changes quite a bit, if you ever use it generically with functions like GetIDsOfNames and IDispatch::Invoke.

Actually, .NET does a neat trick here, compared to the standard Windows way, and it requires far fewer shenanigans than you might expect. .NET has run-time type information (almost always abbreviated as RTTI in polite conversation). This information is included in the Reflection system of .NET, similar to the way Java operates. When you issue the commands to grab a COM object, the same methods that you can use to call things and find out about regular classes can now be used from a COM object.

So, you can poke and prod Excel in a 'generic' manner this way:

var
  AppType : &Type; // type is a reserved word
  ExcelObject : TObject;
  Workbooks : TObject;
  Workbook : TObject;
begin
  AppType := &Type.GetTypeFromProgID('Excel.Application');
  ExcelObject := Activator.CreateInstance(AppType);
  // Yes, the ExcelObject is used as a parameter as well as the
  // main object
  Workbooks := ExcelObject.GetType.InvokeMember('Workbooks',
    BindingFlags.GetProperty,nil,ExcelObject,nil);
  Workbook := Workbooks.GetType.InvokeMember('Add',
    BindingFlags.InvokeMethod,nil,Workbooks,nil);
  //...

Scripting languages that have COM support almost always use the late-bound COM access, so this technique can be handy.

There are more lessons to be learned, but I will pass them along as I find cogent examples.

No feedback yet