Wednesday, March 07, 2007

Fun with enumerators, part 3 - parameterized enumerators

[Part 1 - Introduction, Part 2 - Additional enumerators.]

In first two installments of this series, we took a look at for..in statement, described Delphi support for custom enumerators and demonstrated one possible way to add additional enumerator to a class that already implements one.

Today we'll implement a parameterized enumerator. Enumerators of this kind can be especially handy when used as filters. For example, an enumerator that takes a class reference as a parameter can be used to filter a TObjectList containing objects of many different classes.

IOW, we would like to write a following construct: for obj in list.Enum(TSomeClass) do. Clearly, Enum cannot be a property anymore, but it can be a function.

We can still use an enumerator factory object stored inside our class (as in the Part 2). Besides that, we must implement enumeration function that will take one or more parameters, preserve them inside the factory object and return the factory object so that Delphi compiler can call its GetEnumerator function.

Sounds complicated but in reality it really is simple.

First we have to add an internal factory object and an enumeration function to the TCustomStringList class.

  TCustomStringList = class(TStringList)
private
FEnumEveryNth: TCSLEnumEveryNthFactory;
public
constructor Create;
destructor Destroy; override;
function SkipEveryNth(skip: integer): TCSLEnumEveryNthFactory;
end;

constructor TCustomStringList.Create;
begin
inherited;
FEnumEveryNth := TCSLEnumEveryNthFactory.Create(Self);
end;

destructor TCustomStringList.Destroy;
begin
FEnumEveryNth.Free;
inherited;
end;

function TCustomStringList.SkipEveryNth(skip: integer): TCSLEnumEveryNthFactory;
begin
FEnumEveryNth.Skip := skip;
Result := FEnumEveryNth;
end;


Enumeration function SkipEveryNth takes one parameter and passes it to the factory object. Then it returns this factory object.



We also need a new factory class. This is an extended version of factory class from Part 2. It must implement storage for all enumerator parameters. In this case, this storage is implemented via property Skip.

  TCSLEnumEveryNthFactory = class
private
FOwner: TCustomStringList;
FSkip: integer;
public
constructor Create(owner: TCustomStringList);
function GetEnumerator: TCSLEnumEveryNth;
property Skip: integer read FSkip write FSkip;
end;

constructor TCSLEnumEveryNthFactory.Create(owner: TCustomStringList);
begin
inherited Create;
FOwner := owner;
FSkip := 1;
end;

function TCSLEnumEveryNthFactory.GetEnumerator: TCSLEnumEveryNth;
begin
Result := TCSLEnumEveryNth.Create(FOwner, FSkip);
end;


As you can see, GetEnumerator was also changed - in addition to passing FOwner to the created enumerator, it also passes current value of the Skip property.



New enumerator is very similar to the one from the Part 2, except that it advances list index by the specified ammount (instead of advancing it by 2).

  TCSLEnumEveryNth = class
private
FOwner: TCustomStringList;
FListIndex: integer;
FSkip: integer;
public
constructor Create(owner: TCustomStringList; skip: integer);
function GetCurrent: string;
function MoveNext: boolean;
property Current: string read GetCurrent;
end;

constructor TCSLEnumEveryNth.Create(owner: TCustomStringList; skip: integer);
begin
FOwner := owner;
FListIndex := -skip;
FSkip := skip;
end;

function TCSLEnumEveryNth.GetCurrent: string;
begin
Result := FOwner[FListIndex];
end;

function TCSLEnumEveryNth.MoveNext: boolean;
begin
Inc(FListIndex, FSkip);
Result := (FListIndex < FOwner.Count);
end;


We can now write some test code.

procedure TfrmFunWithEnumerators.btnSkipEveryThirdClick(Sender: TObject);
var
ln: string;
s : string;
begin
ln := '';
for s in FslTest.SkipEveryNth(3) do
ln := ln + s;
lbLog.Items.Add('Parameterized enumerator: ' + ln);
end;


Delphi compiler will translate this for..in roughly into

enumerator := FslTest.SkipEveryNth(3).GetEnumerator;
while enumerator.MoveNext do
ln := ln + enumerator.Current;
enumerator.Free;


So what is going on here?





  1. FslTest.SkipEveryNth(3) sets  FslTest.FEnumEveryNth.Skip to 3 and returns FslTest.FEnumEveryNth.

  2. Compiler calls FslTest.FEnumEveryNth.GetEnumerator.

  3. FslTest.FEnumEveryNth,GetEnumerator calls TCSLEnumEveryNth.Create(FslTest, 3) and returns newly created object.

  4. Enumerator loops until MoveNext returns false.


Test code result:





Tomorrow we'll do something even more interesting - we'll add new enumerator to existing class without creating a derived class.





Technorati tags: , ,

Delphi 2007 - New Vista dialogs

Delphi 2007 supports new-style Open/Save dialogs on Vista, allows you to configure new features in those dialogs and does it in a way that's compatible with older applications. Sadly, it only does two out of three things at once (admittedly, even that is a lot and more would be a pure magic).

If you know you'll be running on Vista, you can use new TFileOpenDialog (or TFileSaveDialog - everything I'll tell you about Open dialog holds also for Save dialog) which exposes new properties, such as FavoriteLinks and OkButtonLabel, and new options. But if you'll try to open this dialog on pre-Vista Windows, you'll only get an error message. (Still, that app will load fine up to the point when you actually try to display the dialog, so it is entirely possible to code two paths in your application, one using TFileOpenDialog if OS is Vista and another using TOpenDialog in all other cases.)

If you use TOpenDialog, you'll get an old-shool dialog, but it will work on all Windows.

And then there's a third possibility - you can use TOpenDialog while setting the global variable UseLatestCommonDialogs (declared in Dialogs.pas) to True. In that case, TOpenDialog will use new-style dialog on Vista and old-style dialog on older Windows. However, you'll not be able to specify any new Vista-only properties (like the OkButtonLabel I mentioned above).

In visual summary, your options are:

TOpenDialog

TOpenDialog +
UseLatestCommonDialogs

TFileOpenDialog

Vista
Windows XP

Besides file open/save dialogs, Delphi 2007 includes support for task dialogs. TTaskDialog components allows for simple creation of task dialogs, together with icons, expanded section, progress bar etc. Of course, it will only work on Windows Vista.


[I'm blogging about Spacely - the beta version of Delphi 2007 for Win32 - with special permission given from CodeGear.]

Technorati tags: , ,

Tuesday, March 06, 2007

Fun with enumerators, part 2 - multiple enumerators

In the first installment of this series, we just repeated some well-known facts about the for..in language construct and the implementation of enumerators. Today I'll show a way to implement additional enumerators for a class.

To start, let's write some test framework. Instead of creating a new class implementing an enumerator, we'll just inherit from the TStringList. That way, we get existing TStringList enumerator 'for free'.

TCustomStringList = class(TStringList)
end;


To make things simple, an instance of this class is initialized with one-letter strings from 'a' to 'z' in form's OnCreate handler.

procedure TfrmFunWithEnumerators.FormCreate(Sender: TObject);
var
ch: char;
begin
FslTest := TCustomStringList.Create;
for ch := 'a' to 'z' do
FslTest.Add(ch);
end;

procedure TfrmFunWithEnumerators.FormDestroy(Sender: TObject);
begin
FslTest.Free;
end;


To test it, we'll iterate over the list and collect all lines into a single line.

procedure TfrmFunWithEnumerators.btnStandardIteratorClick(Sender: TObject);
var
ln: string;
s : string;
begin
ln := '';
for s in FslTest do
ln := ln + s;
lbLog.Items.Add('Standard enumerator: ' + ln);
end;


And here's a result of this test code:



Standard enumerator



All good, all well, all very trivial. But our mission was not to test existing enumerator, but to add a new one. So let's take a look at the pseudo-code describing the inner working of the for..in implementation.

enumerator := list.GetEnumerator;
while enumerator.MoveNext do
//do something with enumerator.Current;
enumerator.Free;


The important fact is that enumerator is created directly from the parameter that is passed to the for..in construct (list, in our case). After that, this parameter is not needed anymore and for..in only operates on the enumerator.



To get a new behaviour, we have to provide a different enumerator. To do that, we have to pass a different object to the for..in loop. This object has to provide a GetEnumerator function, which the compiler will use to implement for..in.



In short, we need a factory class that will generate new enumerator and a property inside our main class that will provide this factory to the for..in loop.

  TCSLEnumEverySecondFactory = class
private
FOwner: TCustomStringList;
public
constructor Create(owner: TCustomStringList);
function GetEnumerator: TCSLEnumEverySecond;
end;

TCustomStringList = class(TStringList)
private
FEnumEverySecond: TCSLEnumEverySecondFactory;
public
constructor Create;
destructor Destroy; override;
property SkipEverySecond: TCSLEnumEverySecondFactory read FEnumEverySecond;
end;


Now we can write

procedure TfrmFunWithEnumerators.btnSkipEverySecondClick(Sender: TObject);
var
ln: string;
s : string;
begin
ln := '';
for s in FslTest.SkipEverySecond do
ln := ln + s;
lbLog.Items.Add('Additional enumerator: ' + ln);
end;


and Delphi compiler will translate it (approximately) into

enumerator := FslTest.SkipEverySecond.GetEnumerator;
while enumerator.MoveNext do
ln := ln + enumerator.Current;
enumerator.Free;


We have a winner! That's exactly what we wanted to achieve.



The other parts of the puzzle are simple. The SkipEverySecond factory can be created in the TCustomStringList constructor. We have to pass it Self object (for the reason we'll come to in a minute).

constructor TCustomStringList.Create;
begin
inherited;
FEnumEverySecond := TCSLEnumEverySecondFactory.Create(Self);
end;

destructor TCustomStringList.Destroy;
begin
FEnumEverySecond.Free;
inherited;
end;


The factory class is simple. It has to remember the owner (TCustomStringList) that was passed to the constructor so that it can pass it on to the enumerator whenever compiler requests one.

constructor TCSLEnumEverySecondFactory.Create(owner: TCustomStringList);
begin
inherited Create;
FOwner := owner;
end;

function TCSLEnumEverySecondFactory.GetEnumerator: TCSLEnumEverySecond;
begin
Result := TCSLEnumEverySecond.Create(FOwner);
end;


The enumerator is just a variation on a standard string list enumerator - it increments list index by 2 instead of 1 and skips every second item in the list. Yes, there is no rule saying that enumerator must return all entries in the enumerated object. In fact, you can be very creative inside the enumerator. We'll return to it some other day.

constructor TCSLEnumEverySecond.Create(owner: TCustomStringList);
begin
FOwner := owner;
FListIndex := -2;
end;

function TCSLEnumEverySecond.GetCurrent: string;
begin
Result := FOwner[FListIndex];
end;

function TCSLEnumEverySecond.MoveNext: boolean;
begin
Inc(FListIndex, 2);
Result := (FListIndex < FOwner.Count);
end;


And the results are ...





That wasn't so hard, wasn't it? Tomorrow we'll look into an even niftier topic - enumerators with parameters.



Technorati tags: , ,

Monday, March 05, 2007

Fun with enumerators, part 1

Iteration over containers (the 'for ... in' statement) is one of the nice things we Win32 developers got with the BDS 2005. I started using it reluctantly but recently I found out that I'm using and misusing it more and more. I started writing a short article on how to implement multiple enumerators in a class, but somehow it turned into a monster. At the moment I'm up to five parts but actually there may be more. You'll just have to wait and see.

If you don't know anything about for...in statement yet and you're running BDS 2005 or later, open up the help and locate Declarations and Statements topic. Inside the lengthy text (near the end), there's an Iteration Over Containers Using For statements subtopic that explains this theme. BDS 2006 users may simply click this link to open the subtopic in question.

This is not a tutorial on iteration, but still a short demo wouldn't hurt. Following simple program adds three items to a string list and then uses for...in construct to write those three lines out. Then it copies first line to a string and uses another for...in construct to iterate over all characters in the string. Output is shown just below the code.

program ForIteratorDemo;

{$APPTYPE CONSOLE}

uses
SysUtils,
Classes;

var
sl: TStringList;
s : string;
c : char;

begin
sl := TStringList.Create;
sl.Add('Line 1');
sl.Add('Line 2');
sl.Add('Line 3');
for s in sl do
Writeln(s);
s := sl[0];
for c in s do
Write(c, '.');
Writeln;
sl.Free;
Readln;
end.


 



How are enumerators made?



To iterate over something, the target must know how to create an enumerator. Enumerators are the mechanism used by the compiler to implement iterator support for any class, record or interface (in addition to some system types as sets and strings, where iterator support is built into the compiler).



Creating an enumerator is simple.The following instructions are copied directly from Delphi help:





To use the for-in loop construct on a class, the class must implement a prescribed collection pattern. A type that implements the collection pattern must have the following attributes:





  • The class must contain a public instance method called GetEnumerator(). The GetEnumerator() method must return a class, interface, or record type.

  • The class, interface, or record returned by GetEnumerator() must contain a public instance method called MoveNext(). The MoveNext() method must return a Boolean.

  • The class, interface, or record returned by GetEnumerator() must contain a public instance, read-only property called Current. The type of the Current property must be the type contained in the collection.


If the enumerator type returned by GetEnumerator() implements the IDisposable interface, the compiler will call the Dispose method of the type when the loop terminates.



For a practical demonstration, you can open Delphi's Classes.pas or, if you don't have Delphi sources or if you are still runnning pre-2005 Delphi, my own GpLists, which implements two enumerators. The one supporting for...in construct on the TGpIntegerList class is shown below.

type
TGpIntegerList = class;

TGpIntegerListEnumerator = class
private
ileIndex: integer;
ileList : TGpIntegerList;
public
constructor Create(aList: TGpIntegerList);
function GetCurrent: integer;
function MoveNext: boolean;
property Current: integer read GetCurrent;
end;

TGpIntegerList = class
//...
public
function GetEnumerator: TGpIntegerListEnumerator;
//...
end;

{ TGpIntegerListEnumerator }

constructor TGpIntegerListEnumerator.Create(aList: TGpIntegerList);
begin
inherited Create;
ileIndex := -1;
ileList := aList;
end;

function TGpIntegerListEnumerator.GetCurrent: integer;
begin
Result := ileList[ileIndex];
end;

function TGpIntegerListEnumerator.MoveNext: boolean;
begin
Result := ileIndex < (ileList.Count - 1);
if Result then
Inc(ileIndex);
end;

{ TGpIntegerList }

function TGpIntegerList.GetEnumerator: TGpIntegerListEnumerator;
begin
Result := TGpIntegerListEnumerator.Create(Self);
end;


The code is quite simple. TGpIntegerList implements GetEnumerator method, which creates an instance of the TGpIntegerListEnumerator class. That one in turn implements GetCurrent and MoveNext functions and Current property.



The trick when writing an enumerator is to keep in mind that MoveNext is called before the GetCurrent. You can assume that compiler-generated iteration loop looks very similar to this pseudocode:

enumerator := list.GetEnumerator;
while enumerator.MoveNext do
do something with enumerator.Current;
enumerator.Free;


Enough for today. You probably didn't learn anything new but that may change tomorrow when I'll work on a much more interesting task - how to add a second enumerator to a class that already implements one.



 



Technorati tags: , ,

Saturday, March 03, 2007

Delphi 2007 - New Welcome Page

There will be a new Welcome page in the Delphi 2007 and it is based on the 'community' page, created by Daniel "sakura" Wischnewski as a replacement for BDS 2006 Welcome Page. I can only say hurray! to that.

What I liked about Daniel's page (and that holds for the new Welcome page too) is the Favorites list. Welcome page still shows only 5 recent projects (supposedly this is a limitation in Delphi, not in the Welcome page itself), but you can mark any number of projects as favorites and they will be managed in a special list.

New welcome page offsers structured access do 96 feeds (notably missing is www.delphifeeds.org), about 20 resources (from YAPP syntax highlighter to CodeGear Online), and various Help topics, but that's of little interest to me. Favourites, on the other hand, is something that I wouldn't want to live without.


[I'm blogging about Spacely - the beta version of Delphi 2007 for Win32 - with special permission given from CodeGear.]

Technorati tags: , ,

Friday, March 02, 2007

Delphi 2007 - More Tool Palette Changes

Yesterday I wrote few words on Tool Palette changes in Delphi 2007. Besides other, I noticed that

The only thing that's bothering me at the moment (but which may change in the final release) is that Standard components are not on the top of the list. I'll much more frequently need TButton than TSpeedButton.

Today I installed new beta and immediately noticed that this behaviour has been fixed. Tool Palette filtering now works exactly as I want!

[I'm blogging about Spacely - the beta version of Delphi 2007 for Win32 - with special permission given from CodeGear.]

Technorati tags: , ,

Thursday, March 01, 2007

Delphi 2007 - Tool Palette Changes

This is something I really hated in BDS 2005/2006. I'm glad that CodeGear people fixed the behaviour (although I'm sure there will be people claiming that previous implementation was proper and that it's broken now).

I don't know about you, but I'm using Tool Palette filtering a lot. For example, if I have to drop a TListBox on the form, I just press Ctrl-Alt-P to select Tool Palette and type list. Tool Palette will show filtered subset of components - only those starting in list or Tlist (case insensitive, of course). Then I just press Enter to drop the selected component on the form. When I need new TButton, I do the same, except that I have to type bu. But when I need TSpeedButton, I have to type spee, because Tool Palette only matches characters at the beginning of the component's name (give or take that extra T). And when I need some very specialized button, maybe something from the JVCL or Tnt, I browse through the complete component list as I don't always know what prefix the given component uses.

This behaviour will be enhanced in Delphi 2007. Here, Tool Palette filter searches complete component name - when you type bu, for example, it will display all components containing this substring at any position, including TSpeedButton, TCategoryButtons and whole lot of other buttons. Exactly what I need!

The only thing that's bothering me at the moment (but which may change in the final release) is that Standard components are not on the top of the list. I'll much more frequently need TButton than TSpeedButton.

Tool Palette in BSD 2006 and Delphi 2007 beta

Filtered Tool Palette in BDS 2006 (left) and Delphi 2007 Beta (right)

[I'm blogging about Spacely - the beta version of Delphi 2007 for Win32 - with special permission given from CodeGear.]

Technorati tags: , ,

Wednesday, February 28, 2007

UAC in the Oven

I just noticed that Aleksander Oven, a honorable member of Delphi-SI community is blogging.

Just now he finished a great three-part series on Vista UAC and Delphi. Recommended!

Tuesday, February 27, 2007

Building in Delphi 2007

[I'm blogging about Spacely - the beta version of Delphi 2007 for Win32 - with special permission given from CodeGear. Thanks, guys!]


Today I'll focus on a feature that I'm already exploiting a lot - build configurations.

Delphi 2007 for Win32 will use Microsoft's MSBuild engine for project building. In addition to faster IDE compile times, this will bring us great new improvement - build configurations. In short, that will allow you to give a name to a group of of compile settings and then recall them at will. Standard installation already defines Debug and Release configurations, but you are free to add many other.

In IDE, build configurations show as a new drop-down combo, visible on some Project Options pages - namely on Compiler, Compiler Message, Linker, and Directories/Conditionals.

Build configuration - click for larger picture

Another feature of the new build engine are pre- and post-build events. You can attach a list of commands (calls to external programs) that will be executed before and after building. Quite powerful, as you can integrate external steps required for making a release here.

Pre- and post-build events - click for larger picture

That's all for today. Stay tuned as I have more to show ...

Monday, February 26, 2007

Four Ways to Detect Vista

Pop quiz. How do you detect Windows version? Simple, call the GetVersionEx API.

And now for a bonus question. How do you detect Windows version without a doubt? Well, that's not so simple. Do you disagree? Read on.

Compatibility mode

For some time (I think this functionality appeared in Windows XP), you can execute applications under a compatibility layer. Right-click your exe, select Properties and click on the Compatibility tab. Click on the Run this program in compatibility mode for. Now you can select from the list of many operating system versions - from Windows 95 onwards.

When you do this, plenty of things happen. Firstly, Windows starts faking OS version to your program. Secondly, some API functions work more like they did in the selected OS version.

Why would you do this? Maybe the program in question is not well-behaved and needs this kind of help from the OS. Or maybe it just tests for supported OS version too aggressively and you want to circumvent this test.

In all cases, result is the same - what you get from GetVersionEx is not the true answer. So, I have to repeat my bonus question - how could you detect the true OS version?

Four ways to detect OS

I run into that problem few weeks ago. We have an application that is not yet Vista-ready, mostly because of some DirectX incompatibilities. We don't want negative user experience and therefore we don't allow the app to run on Vista at all. Still, some users may be smart enough to enable compatibility mode for this application, which would make OS version test useless (sadly, compatibility mode doesn't help this application to work correctly on Vista).

I found no good solution but luckily my fellow Slovenian Delphi users did. Even more, they found three alternative solutions.

One is to check for system file that was not present in previous OS versions. For example, one can test for presence of %WINDIR%\System32\ndfetw.dll.

One is to check Notepad.exe version. Under Vista, Notepad has version 6.something while it was 5.something in XP.

And the last one (and the best, in my opinion) is to check if specific API is exported. For example, Vista exports GetLocaleInfoEx from kernel32.dll while previous Windowses didn't.

That's it - now you have one way to get simulated OS version (actually, there are two - you can check the HLKM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersion key) and three to get true OS version.

The last approach is so flexible that we (Miha-R from the Delphi-SI forum, who did most of the work, and me) turned it into a function that can detect all 32-bit OS versions simply by checking for exported API functions. After some work, we created a list of OS-version-determining APIs, all exported from kernel32.dll.

  Vista Server 2003 XP SP1 XP 2000 ME NT 4 98 95 OSR 2 NT 3 95
GetLocaleInfoEx  x                    
GetLargePageMinimum  x  x                  
GetDLLDirectory  x  x  x                
GetNativeSystemInfo  x  x  x  x              
ReplaceFile  x  x  x  x  x            
OpenThread  x  x  x  x  x  x          
GetThreadPriorityBoost  x  x  x  x  x    x        
IsDebuggerPresent  x  x  x  x  x  x  x  x      
GetDiskFreeSpaceEx  x  x  x  x  x  x  x  x  x    
ConnectNamedPipe  x  x  x  x  x          x  
Beep  x  x  x  x  x  x    x    x  x

Writing the function - DSiGetTrueWindowsVersion - was quite simple; just proceed from newest OS to oldest and check for exported APIs. You can get this function (and many more) by downloading the freeware DSiWin32 unit (another effort of Slovenian Delphi community).

Checking for compatibility layer

It would be also interesting to detect whether the application is running under the compatibility layer. There is a registry key that contains this info - Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers, key name is full path of the executable - but the information itself is not reliable.

First, you have to check both HKLM and HKCU branches, as the compatibility may be set by current user and enforced by the system administrator. Second, on 64-bit Windows, you must check the (true) HKLM64 branch, not the (32-bit compatibility) HKLM branch (see the code below for more detail). And third, all this may be to no avail.

The problem lies in a fact that child processes inherit compatibility layer from parents. If you set compatibility mode for executable A and then run executable B from it, executable B won't have any compatibility data set in the registry but nevertheless it will run under the compatibility layer. Still, information in those keys may be of some use sometime.

Demo

Try running it as a normal app and under the compatibility layer.

program IsVista;

{$APPTYPE CONSOLE}

uses
Windows,
SysUtils,
GpVersion,
DSiWin32;

var
testFile: string;

begin
Writeln('GetVersionEx API: ', CDSiWindowsVersionStr[DSiGetWindowsVersion]);
Writeln('HKLM CurrentVersion registry value: ', DSiReadRegistry(
'\SOFTWARE\Microsoft\Windows NT\CurrentVersion', 'CurrentVersion', '',
HKEY_LOCAL_MACHINE));
Writeln('HKLM ProductName registry value: ', DSiReadRegistry(
'\SOFTWARE\Microsoft\Windows NT\CurrentVersion', 'ProductName', '',
HKEY_LOCAL_MACHINE));
testFile := IncludeTrailingPathDelimiter(DSiGetSystemFolder) + 'ndfetw.dll';
Writeln('Presence of file ', testFile, ': ', BoolToStr(FileExists(testFile), true));
Writeln('Notepad.exe version: ',
CreateResourceVersionInfo(IncludeTrailingPathDelimiter(DSiGetWindowsFolder) +
'notepad.exe').GetFormattedVersion(verFullDotted));
Writeln('Presence of GetLocaleInfoEx API: ',
BoolToStr(GetProcAddress(GetModuleHandle('kernel32'), 'GetLocaleInfoEx') <> nil, true));
//http://msdn2.microsoft.com/en-us/library/aa480152.aspx
Writeln('HKCU AppCompatFlags for ', ParamStr(0),': ',
DSiReadRegistry('Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers',
ParamStr(0), '', HKEY_CURRENT_USER));
Writeln('HKLM AppCompatFlags for ', ParamStr(0),': ',
DSiReadRegistry('Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers',
ParamStr(0), '', HKEY_LOCAL_MACHINE));
if not DSiIsWow64 then
Writeln('Not a 64-bit OS, KEY_WOW64_64KEY not tested')
else begin
//http://msdn.microsoft.com/library/en-us/sysinfo/base/about_the_registry.asp
Writeln('HKLM64 CurrentVersion registry value: ', DSiReadRegistry(
'\SOFTWARE\Microsoft\Windows NT\CurrentVersion', 'CurrentVersion', '',
HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE or KEY_WOW64_64KEY));
Writeln('HKLM64 ProductName registry value: ', DSiReadRegistry(
'\SOFTWARE\Microsoft\Windows NT\CurrentVersion', 'ProductName', '',
HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE or KEY_WOW64_64KEY));
Writeln('HKLM64 AppCompatFlags for ', ParamStr(0),': ',
DSiReadRegistry('Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers',
ParamStr(0), '', HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE or KEY_WOW64_64KEY));
end;
Writeln('DSiGetAppCompatFlags: ', DSiGetAppCompatFlags(ParamStr(0)));
Writeln('DSiGetTrueWindowsVersion: ', CDSiWindowsVersionStr[DSiGetTrueWindowsVersion]);
Readln;
end.

Friday, February 23, 2007

Nasty inline codegen bug in BDS 2006

I'm using assign-test-and-do-something code pattern quite frequently. You know, code like this:

Result := CalculateSomething(some, param); // assign
if not Result then // test
Exit; // do something

As all real programmers, I'm lazy and tend to rewrite such code as:

if Asgn(Result, CalculateSomething(some, param)) then
Exit;

Most of the dirty work is done by the Asgn function, which acts as a splitter - it copies one value to two destinations - output parameter and function result.

function Asgn(var output: integer; const value: integer): integer;
begin
output := value;
Result := value;
end;

(Actually, I have four overloaded Asgn functions, but that's beside the point.)


Yesterday, I did two things almost at the same time (which was, in hindsight, very lucky - it helped me to find the problem in "mere" 30 minutes). Firstly, I changed all Asgn functions to be inlined on modern Delphis and secondly, I used same variable as part of the value calculation and as the output parameter. In other words, I did this: 

function Asgn(var output: integer; const value: integer): integer; inline;
begin
output := value;
Result := value;
end;

copy := Asgn(v1, v1+v2);

(It was slightly more complicated than that but the code above nicely summarizes the problem.)


This code should calculate a sum of two variables and assign it to both v1 and copy. It should, I said, but it didn't. Correct value was assigned to v1, but something completely different was assigned to the copy variable. When I removed the inline, Delphi immediately started to generate correct code.


So, for now, I removed inline from all my Asgn functions. I also know that I'll be extremely causious when using inline in the future.


CodeGear has been notified of the problem (QC #41166) and I hope they'll be able to fix the codegen before Delphi 2007 is out.


If you don't believe me, try it for yourself. Demo is below. It should output four numbers 17, but it doesn't.

program Project8;

{$APPTYPE CONSOLE}

uses
SysUtils;

function Asgn1(var output: integer; const value: integer): integer;
begin
output := value;
Result := value;
end;

function Asgn2(var output: integer; const value: integer): integer; inline;
begin
output := value;
Result := value;
end;

var
v1, v2: integer;

begin
v1 := 10;
v2 := 7;
Writeln(Asgn1(v1, v1+v2));
Writeln(v1);
v1 := 10;
v2 := 7;
Writeln(Asgn2(v1, v1+v2));
Writeln(v1);
Readln;
end.

Thursday, February 08, 2007

Free tools I use for development

Now and then I find a nice free tool hidden on the web. Lately, I found some nice gems mentions on other blogs and I thought I should share my list with you, dear reader. Maybe you'll find something of use and maybe you could recommend some other good tool to me.

 

Another Lens  - A screen magnification tool, written in Delphi 4. [Bjorn Ischo; link from the author's home is not working, this is a correct one: http://www.b-zone.de/zips/lens.zip]

Desktop Icons Save and Restore* - Shell extension that allows you to save and restore position of icons on the desktop. [Jamie O'Connell, http://www.midiox.com/html/desktop.htm]

FireBird - Great SQL database server. I'm particularly interested in the embedded version. [Firebird Foundation, http://www.firebirdsql.org/, http://www.ibphoenix.com/]

GpProfile - My old but still useful profiler. (I am working on a new version.) [Primoz Gabrijelcic, http://gp.17slon.com/gpprofile/index.htm]

GraphEdit -  A tool for visual DirectX filter graph editing.  [Microsoft, part of the DirectX SDK, http://msdn2.microsoft.com/en-us/library/ms783346.aspx, but is easy to find on the web if you don't want to download and install entire SDK]

IcoFX - Great icon editor and newest addition to my toolbox. [Attila Kovrig, http://icofx.xhost.ro/]

Microsoft Spy++ - Window browser. [Microsoft, part of Visual Studio, http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcug98/html/_asug_home_page.3a_.spy.2b2b.asp, easy to find on the web if you don't need complete Visual Studio]

Putty - Ssh and sftp client. [Simon Tatham, http://www.chiark.greenend.org.uk/~sgtatham/putty/]

IBExpert Personal Edition - InterBase/FireBird management tool. [HK Software, http://www.hk-software.net/ibexpert.com/content/doc_122943-5-10-00.php]

Synergy* - Control multiple computers with one mouse and keyboard. [http://synergy2.sourceforge.net/authors.html, http://synergy2.sourceforge.net/, http://www.stevetrefethen.com/blog/default,month,2006-06.aspx]

TightVNC* - Remote control client and server. [http://www.tightvnc.com/people.html, http://www.tightvnc.com/]

VLC Media player - Media player, which is of particular interest to me because it can play (and wrap/unwrap) DVB Transport Streams. [VideoLAN, http://www.videolan.org/]

WinCVS - CVS GUI which is so good that I don't want to switch to Subversion. [http://sourceforge.net/project/memberlist.php?group_id=10072, http://sourceforge.net/projects/cvsgui]

Window Clippings - Nice screen grabber, useful when writing documentation. [Kenny Kerr, http://weblogs.asp.net/kennykerr/archive/2005/09/30/Window-Clippings-1.0.aspx]

Windows SysInternals tools - I need them all. Constantly. [Mark Russinovich, http://www.microsoft.com/technet/sysinternals/default.mspx]

Wink - Records all changes on the computer screen and makes a movie. Good for tutorials and to demonstrate a problem to a coworker. [DebugMode, http://www.debugmode.com/wink/]

WinPCap - Tool to monitor and transmit data in link-layer network access, used in Wireshark (below). [http://www.winpcap.org/misc/credits.htm, http://www.winpcap.org/]

WinSCP - Graphical sftp interface, based on the Putty. [http://winscp.net/eng/docs/contributions, http://winscp.net/eng/index.php]

Wireshark [ex Ethereal] - Capture and analyze network packets. [http://www.wireshark.org/about.html, http://www.wireshark.org]

XanaNews - Good newsgroups reader, written in Delphi. [Colin Wilson, http://www.wilsonc.demon.co.uk/xananews.htm]

XN Resource Editor - Resource editor, written by the XanaNews author. Includes simple icon editor. [Colin Wilson, http://www.wilsonc.demon.co.uk/d10resourceeditor.htm]

 


*More on this product: Remote access to the fourth power

Friday, February 02, 2007

When Vista doesn't print

I installed Vista RTM immediately as it was available on the MSDN.

That was quite a surprise for me as I thought that many months would pass before my first Vista installation. But alas, I had to write an article on Vista for Slovenian leading computing magazine Monitor and I just got completely new Vista-ready laptop which I could use for playground. (It got Windows XP Home preinstalled - yuck!)

Anyway, I installed Vista (or, to be more precise, Windows Vista Ultimate) on that laptop. Everything worked fine (even though I had to scrap together few drivers from all over the internet). Everything except that I couldn't print. I have this nice colour laser printer (Samsung CLP-510, really smooth) in the office, and it is connected to the Windows 2003 Server. All other computers (running XP everyone of them) could use this shared printer but not the Vista, nooo sir.

It was quite an interesting malfunction, too - when I tried to connect to my shared printer, Vista started copying drivers (XP drivers, of course), and then reported that driver installation 'ran out of memory'. OK, I only have measly 2 gigs installed, but still...

I tried a different trick - installed printer driver locally (no problem here) and only after that tried to connect to the shared printer (in hope that currently installed driver will be used). But nooo, Vista reported same error again.

I gave up then saying: "Oh, well, it's only about two months until Vista is available for general public. Surely Samsung will have Vista drivers ready by then." But the problem was still nagging me and I spilled out my soul to my friends on Slovenian Delphi forum. Somebody has an interesting idea - why don't I connect to the Samsung as if it were a TCP/IP printer and then spool EMF content to the server. Still, he had no idea how to make this work in practice.

I searched for the solution occasionally but found out that Microsoft documentation on that topic is really hard to find if you don't know what you're searching for. I did'nt spend much time on that, though, as I could always use the printer from my main computer.

Solution, at last!

Today I returned to the problem and somehow managed to find the answer in Microsoft's documentation. It really is simple - when you already know how to do it :( .

For anybody trying to solve a similar problem - and for all of you that just want to learn more - here is the solution.

First, install Print Services for Unix on the Windows 2003 Server (Control Panel, Add or Remove Programs, Add/Remove Windows Components, Other Network File and Print Services, Details). Make sure that the TCP/IP Print Server service is running.

Next, install LPR Port Monitor feature on the Windows Vista (Control Panel, Programs and Features, Turn Windows features on or off, Print Services).

Create new port on the Windows Vista computer. Run Printers and clik Add Printer. Choose Add a local printer. Select Create a new port setting and set Type of port to LPR Port.

Click Next and enter address of the server providing Unix printing services and name of the printer.

Click OK and proceed as usual - select printer manufacturer etc.

All work to no avail?

Using this procedure I managed to get my Samsung working from the Vista.

I learned a lot and maybe I will be able to use this knowledge in the future.

Maybe it will help somebody out there with a similar (or completely different) problem.

It didn’t help me at all :(

You see, it is February already and Samsung published updated printer drivers two days ago.