Borland. Delphi. 2005 Migration to .NET using VCL for .NET

By: Bob Swart

Abstract: This tutorial demonstrates the migration of Delphi Win32 source code, units and (database) applications to the Microsoft .NET Framework using Borland Delphi 2005 and VCL (for .NET).

by Bob Swart, Bob Swart Training & Consultancy

    Introduction

In this tutorial we will migrate a real-world Win32 application to the Microsoft .NET Framework using Delphi 2005, using the Borland VCL (Visual Component Library) as the framework. A number of existing VCL for Win32 applications - taken from the Delphi 7 Demos directory - will be opened in Delphi 2005 and migrated to a .NET target using VCL for .NET.

These exercises will demonstrate several migration issues, small and more significant alike.

    VCL and VCL for .NET

The VCL has been made available for the .NET Framework, enabling us to easily migrate Win32 VCL applications to .NET (its also possible to take VCL projects to WinForms).

Delphi 2005 comes with a large number of example applications, in the BDS\3.0\Demos directory, which contains - among others - the Delphi.NET and DelphiWin32 subdirectories. The BDS\3.0\DelphiWin32\VCLWin32\ directory contains several Win32 VCL examples, some of which are already migrated to .NET, and found in the BDS\3.0\Delphi.NET\VCL directory.

One of the examples which is not already migrated to .NET is the Threads example, demonstrating the QuickSort, SelectionSort and BubbleSort algorithms in a multi-threaded example. The Threads project is a Win32 VCL application that we will be migrating to .NET using VCL for .NET.

First, let's create a copy of the necessary files.

- Create a new subdirectory Threads in the BDS\3.0\Demos\Delphi.NET\VCL directory. The result of our migration will then be available as another example of a Delphi for .NET VCL project.

- Copy all files from the BDS\3.0\Demos\DelphiWin32\VCLWin32\Threads directory to the BDS\3.0\Demos\Delphi.NET\VCL\Threads directory.

- Remove the thrddemo.bdsproj file, since that file stores the fact that this was a Delphi Win32 project (and we want to migrate to .NET instead).

Now we are ready to work on the new Threads project, and migrate it to .NET.

- Start Delphi 2005

- Click on the Open Project button in the Welcome Page and open the thrddemo.dpr project file from the BDS\3.0\Demos\Delphi.NET\VCL\Threads directory (the copy we just made of the Win32 demo).

Since this project has no .bdsproj file associated with it, the Delphi 2005 IDE needs to ask you if you want to upgrade it to a Win32 or .NET project. This is done with the Project Upgrade dialog, shown in the following figure:

Hide image

Project Upgrade thrddemo to .NET

- Specify the Delphi for .NET target, and click on the OK button.

This will generate a thrddemo.bdsproj for us, with the .NET personality specified inside. We will now save the project, so the new personality information is stored in the thrddemo.bdsproj file.

- Do File | Save All, so the thrddemo project is saved, including the new thrddemo.bdsproj file.

- Press Ctrl+F9 to compile the thrddemo project for the first time.

This should give you about 11 warnings and 5 errors, which are as follows:

[Warning] thrddemo.dpr(8): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Windows' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Messages' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Graphics' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Controls' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Dialogs' is specific to a platform
[Warning] ThSort.pas(7): W1005 Unit 'Borland.Vcl.ExtCtrls' is specific to a platform
[Warning] ThSort.pas(7): W1005 Unit 'Borland.Vcl.StdCtrls' is specific to a platform
[Warning] SortThds.pas(6): W1005 Unit 'Borland.Vcl.Graphics' is specific to a platform
[Warning] SortThds.pas(6): W1005 Unit 'Borland.Vcl.ExtCtrls' is specific to a platform
[Error] SortThds.pas(18): E2397 Unsafe pointer only allowed if compiling with {$UNSAFECODE ON}
[Error] SortThds.pas(57): E2003 Undeclared identifier: 'Point'
[Error] SortThds.pas(65): E2396 Unsafe code only allowed in unsafe procedure
[Error] SortThds.pas(107): E2396 Unsafe code only allowed in unsafe procedure
[Fatal Error] ThSort.pas(39): F2063 Could not compile used unit 'SortThds.pas'

We can ignore all warnings for now, and will get back to those later. First, let's fix the compiler errors.

The first error is regarding line 18 of SortThds.pas, and mentions that "unsafe pointer only allowed if compiling with {$UNSAFECODE ON}". The actual line of code that causes this warning is the declaration of FSortArray of type PSortArray in the TSortThread class definition:

type
  PSortArray = ^TSortArray;
  TSortArray = array[0..MaxInt div SizeOf(Integer) - 1] of Integer;

  TSortThread = class(TThread)
  private
    FBox: TPaintBox;
    FSortArray: PSortArray;

The PSortArray is a pointer to TSortArray, and a pointer is not a safe type, hence the warning. The warning is also followed by a number of other unsafe warnings, related to the use of this unsafe pointer type.

Although the final result will have no unsafe types or code at all, at first it's usually easier to remove the compiler errors by marking code as unsafe first - first make the project compile successfully, and then replace the unsafe sections by safe code.

- Add the {$UNSAFECODE ON} compiler directive before the class definition for TSortThread, so the field declaration of FSortArray is accepted.

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

Ignoring the warnings, this should give you 4 errors left.

[Error] SortThds.pas(59): E2003 Undeclared identifier: 'Point'
[Error] SortThds.pas(67): E2396 Unsafe code only allowed in unsafe procedure
[Error] SortThds.pas(109): E2396 Unsafe code only allowed in unsafe procedure
[Fatal Error] ThSort.pas(39): F2063 Could not compile used unit 'SortThds.pas'

There is now an identifier Point which is unknown. The offending line of code is actually as follows:

procedure PaintLine(Canvas: TCanvas; I, Len: Integer);
begin
  Canvas.PolyLine([Point(0, I * 2 + 1), Point(Len, I * 2 + 1)]);
end;

It looks like a call to a function Point is made (twice), but somehow the .NET compiler cannot find the Point function. This is a good case to use the Refactor - Find Unit functionality, to help us locate the missing unit (with the Point function definition), and add it to the uses clause.

- Place the cursor in one of the Point identifiers in the source line in the Code Editor, and right click to select the Find Unit submenu of the Refactor menu (alternately you can use the Refactor menu choice to select the Find Unit choice).

This will bring up the Find Unit dialog with the Search criteria Point already specified. A number of units are also mentioned, including Borland.Vcl.Types.Point which seems like the perfect candidate for a definition of Point.

- Select the Borland.Vcl.Types.Point unit, and click on either the Interface or Implementation choice to decide where this unit will be added to the uses clause of the SortThds.pas unit.

Hide image

Refactoring - Find Unit

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will now result in 3 errors. As always, the last (fatal) error is caused by the fact that we have two earlier errors. So there are in fact only two errors to fix.

[Error] SortThds.pas(70): E2396 Unsafe code only allowed in unsafe procedure
[Error] SortThds.pas(112): E2396 Unsafe code only allowed in unsafe procedure
[Fatal Error] ThSort.pas(39): F2063 Could not compile used unit 'SortThds.pas'

We still use the unsafe pointer in FSortArray, and this is what the two errors report. The first error concerns a line of code in the Create constructor, where we take the address of the SortArray parameter and assign it to the unsafe FSortArray pointer. This code is unsafe, and doesn't compile unless we mark the Create constructor with the unsafe keyword.

- Add unsafe to the implementation of the Create constructor, as follows:

constructor TSortThread.Create(Box: TPaintBox; var SortArray: array of Integer); unsafe;
begin
  FBox := Box;
  FSortArray := @SortArray;
  FSize := High(SortArray) - Low(SortArray) + 1;
  FreeOnTerminate := True;
  inherited Create(False);
end;

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This time, the error message about the unsafe code in the Create constructor is no longer shown. However, another error message concerning the Create constructor now appears:

[Error] SortThds.pas(72): E2305 'Self' might not have been initialized
[Error] SortThds.pas(112): E2396 Unsafe code only allowed in unsafe procedure
[Fatal Error] ThSort.pas(39): F2063 Could not compile used unit 'SortThds.pas'

The error message concerns the fact that we can call the inherited constructor after we've done some specific property initialization. However, unless you have a good reason not to do so, the recommendation is to call the inherited constructor before the custom constructor code. This is easy to fix, of course.

- Move the inherited call to Create as first line of code in the Create constructor, as follows:

constructor TSortThread.Create(Box: TPaintBox; var SortArray: array of Integer); unsafe;
begin
  inherited Create(False);
  FBox := Box;
  FSortArray := @SortArray;
  FSize := High(SortArray) - Low(SortArray) + 1;
  FreeOnTerminate := True;
end;

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

We are now left with only one error to fix, again an unsafe code error message:

[Error] SortThds.pas(112): E2396 Unsafe code only allowed in unsafe procedure
[Fatal Error] ThSort.pas(39): F2063 Could not compile used unit 'SortThds.pas'

This time, it's the Execute method where we use the ^ operator on the unsafe pointer FSortArray. The fix consists of adding the unsafe keyword to this method as well.

- Add the unsafe keyword to the implementation of the Execute method, as follows:

procedure TSortThread.Execute; unsafe;
begin
  Sort(Slice(FSortArray^, FSize));
end;

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

[Error] SortThds.pas(114): E2454 Slice standard function not allowed for VAR nor OUT argument
[Fatal Error] ThSort.pas(39): F2063 Could not compile used unit 'SortThds.pas'

This is a bigger problem. For some reason, the Slice function under .NET has a problem with the way we pass the arguments right now. Considering what it does, it actually looks safe to ignore the call to Slice, and just pass the actual FSortArray^ to Sort. Within the Sort method, we'll use High and Low to determine the actual size of the array, and that should work just fine (note that we'll turn it into safe, managed code in a moment anyway).

- Remove the call to Sort that uses the Slice standard function, and replace it with a call to Sort passing just FSortArray^ as argument, as follows:

procedure TSortThread.Execute; unsafe;
begin
//  Sort(Slice(FSortArray^, FSize));
  Sort(FSortArray^);
end;

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This time, only warnings are left:

[Warning] thrddemo.dpr(8): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Windows' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Messages' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Graphics' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Controls' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform
[Warning] ThSort.pas(6): W1005 Unit 'Borland.Vcl.Dialogs' is specific to a platform
[Warning] ThSort.pas(7): W1005 Unit 'Borland.Vcl.ExtCtrls' is specific to a platform
[Warning] ThSort.pas(7): W1005 Unit 'Borland.Vcl.StdCtrls' is specific to a platform
[Warning] SortThds.pas(6): W1005 Unit 'Borland.Vcl.Graphics' is specific to a platform
[Warning] SortThds.pas(6): W1005 Unit 'Borland.Vcl.ExtCtrls' is specific to a platform
[Warning] SortThds.pas(71): W1047 Unsafe code '@ operator'
[Warning] SortThds.pas(113): W1047 Unsafe code '^ operator'

Most warnings only tell us that VCL for .NET is specific to a platform - not too surprising. Two warnings are more serious, and reflect unsafe code: the use of the @ and ^ operators. We will address these later. For now, let's ignore the warnings and run the project.

Hide image

Threads Sorting Demo for .NET

And here it runs as a native .NET executable with three concurrent threads.

However, it's also not a 100% safe application, so let's work on that aspect now.

    Safe Code

Applications that contain unsafe code will fail when you check them with PEVerify. This is a situation that you can accept as an intermediate migration result, but you may want to strive for a 100% safe version of your .NET application as end goal.

- Remove the {$UNSAFECODE ON} compiler directive, as well as the two unsafe keywords (for the Create constructor and the Execute method), and get ready to fix the unsafe code sections.

The first problem - causing all other problems - is the pointer type definition. We can replace the TSortArray definition by just an "array of integer". If you want to keep code which can be compiled by the Win32 as well as the .NET compiler, you may want to use compiler directives to distinguish between the original code and the new code.

- Change the type definition of TSortArray to an array of integer, and use compiler directives for the old WIN32 and the new .NET definition, as follows:

{$IFDEF WIN32}
  PSortArray = ^TSortArray;
  TSortArray = array[0..MaxInt div SizeOf(Integer) - 1] of Integer;
{$ELSE}
  TSortArray = array of Integer;
{$ENDIF}

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This time, you'll get an error message about the definition of FSortArray, since PSortArray is an unknown type now. Using the .NET compiler, we can simply define FSortArray as being of type TSortArray.

- Change the type of field FSortArray from PSortArray to TSortArray. If you want to use compiler directives for WIN32 vs. .NET code, this will be as follows:

  TSortThread = class(TThread)
  private
    FBox: TPaintBox;
    FSortArray: {$IFDEF WIN32}PSortArray{$ELSE}TSortArray{$ENDIF};

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This time, you'll get an error message in the create constructor about the @ operator which should not be used here.

- Remove the use of the @ operator in the Create constructor. Using compiler directives, this can be done as follows:

constructor TSortThread.Create(Box: TPaintBox; var SortArray: array of Integer);
begin
  inherited Create(False);
  FBox := Box;
  FSortArray := {$IFDEF WIN32}@{$ENDIF}SortArray;
  FSize := High(SortArray) - Low(SortArray) + 1;
  FreeOnTerminate := True;
end;

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

The last error is about the use of the ^ operator in the Execute method. Obviously, you can again eliminate that one when using the .NET compiler, changing it as follows:

procedure TSortThread.Execute;
begin
//  Sort(Slice(FSortArray^, FSize));
  Sort(FSortArray{$IFDEF WIN32}^{$ENDIF});
end;

*Note that Sort is taking a var argument, so don't worry about passing the entire array of integer here as value argument (otherwise it would make sense to see if you could add a const keyword).

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

We only have the platform specific warnings left, no unsafe code errors anymore. This is now a native, safe, managed .NET executable, using VCL for .NET. The Threads demo illustrated some points about quick-and-dirty (but unsafe) migration, followed by a full safe and managed migration path from Win32 to .NET for Delphi VCL applications.

    Data Access

Now that we've migrated a VCL application to .NET, it's time to move on to a bigger application, with some more real-world characteristics. This includes data access, so let's take a look in the Delphi7\Demos\Db directory. There are several example applications here, MastApp and IBMastApp the biggest, with the largest number of units and forms.

If you don't have Delphi 7, then just take a look at the BDS\3.0\Demos\DelphiWin32\Db directory, which also features the MastApp and IBMastApp projects (but already migrated to the new project format - including a .bdsproj file).

The MastApp has been migrated to .NET, and can be found in the BDS\3.0\Demos\Delphi.NET\DB\MastApp directory, but the IBMastApp project has not been migrated to VCL for .NET yet, and is absent from the BDS\3.0\Demos\Delphi.NET\DB or BDS\3.0\Demos\Delphi.NET\VCL\Db directory.

So, as our main example, let's migrate the IBMastApp application from Win32 to .NET - a non-trivial database example application.

First, let's create a copy of the necessary files.

- Create a new subdirectory IBMastApp in the BDS\3.0\Demos\Delphi.NET\VCL\Db directory. The result of our migration will then be available as another example of a Delphi for .NET VCL DB project.

- Copy all files from the BDS\3.0\Demos\DelphiWin32\VCLWin32\Db\IBMastApp directory to the BDS\3.0\Demos\Delphi.NET\VCL\Db\IBMastApp directory.

- Remove the mastapp.bdsproj file, since that file stores the fact that this was a Delphi Win32 project (and we want to migrate to .NET instead).

Now we are ready to work on the new IBMastApp project, and migrate it to .NET.

- Start Delphi 2005

- Click on the Open Project button in the Welcome Page and open the mastapp.dpr project file from the BDS\3.0\Demos\Delphi.NET\VCL\Db\IBMastApp directory (the copy we just made of the Win32 demo).

Since this project has no .bdsproj file associated with it, the Delphi 2005 IDE needs to ask you if you want to upgrade it to a Win32 or .NET project. This is done with the Project Upgrade dialog, shown in the following figure:

Hide image

Project Upgrade mastapp to .NET

- Specify the Delphi for .NET target, and click on the OK button.

This will generate a mastapp.bdsproj for us, with the .NET personality specified inside. We will now save the project, so the new personality information is stored in the mastapp.bdsproj file.

- Do File | Save All, so the mastapp project is saved, including the new mastapp.bdsproj file.

    Migrating Data Module

Now that we have associated the Delphi for .NET personality with this project, we first need to make sure that the database is pointing to the right location. This should be done before your first attempt to compile the application. Once the database connection is configured right, we can focus on the source code migration.

- Open the data module, which is located in DataMod.pas, by double-clicking on the DataMod.pas node in the Project Manager.

The data module is designed as shown in the following screenshot, using InterBase Express as data access technology.

Hide image

VCL for .NET Data Module at design-time

We need to reconfigure the Database component, which can be located in the lower-right corner of the data module.

- Click on the Database component (which is of type TIBDatabase) on the data module.

- Right-click on the Database component, this will display a pop-up menu that tells you the version of InterBaseExpress (9.09) and offers a choice for the Database Editor. Select the Database Editor, which will give you the following dialog:

Hide image

Database Component Editor

Note the D:\ here in the path location. You may have to change that to C:\ and use Common Files instead of Borland to make InterBase connect to this database. For my default installation, the mastsql.gdb is found at C:\Program Files\Common Files\Borland Shared\Data\mastsql.gdb - your configuration may differ.

- Click on the Test button to verify that a connection to the InterBase database mastsql.gdb can be made. If not, make sure that mastsql.gdb can be found at the specified location, and that InterBase itself is actually running.

- Once you get a Successful Connection when you click on the Test button, you can close the Database Component Editor.

*Note that you will probably need to do this for every VCL project that you want to migrate to .NET: first make sure that the data access components on the data module point to the right database. And while most VCL data access components have a VCL for .NET counterpart, there is no support for SQL Links in .NET, so you may have to migrate those projects to dbExpress, dbGo for ADO, InterBaseExpress or some other VCL for .NET data access technology.

    Migrating Source Code

Once the data module and especially the data access connections have been verified, we can start to compile the project for the first time.

- Press Shift+F2 to Save All files in the project.

- Press Ctrl+F9 to compile the mastapp project for the first time.

This should give you about 17 warnings and 1 error, which are as follows:

  [Warning] mastapp.dpr(23): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform
  [Warning] MAIN.PAS(6): W1005 Unit 'Borland.Vcl.Windows' is specific to a platform
  [Warning] MAIN.PAS(6): W1005 Unit 'Borland.Vcl.Messages' is specific to a platform
  [Warning] MAIN.PAS(6): W1005 Unit 'Borland.Vcl.Graphics' is specific to a platform
  [Warning] MAIN.PAS(6): W1005 Unit 'Borland.Vcl.Controls' is specific to a platform
  [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform
  [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.Dialogs' is specific to a platform
  [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.Buttons' is specific to a platform
  [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.StdCtrls' is specific to a platform
  [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.Menus' is specific to a platform
  [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.ExtCtrls' is specific to a platform
  [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Windows' is specific to a platform
  [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Messages' is specific to a platform
  [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Graphics' is specific to a platform
  [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Controls' is specific to a platform
  [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform
  [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Dialogs' is specific to a platform
  [Fatal Error] DataMod.pas(9): F1026 File not found: 'VarUtils.dcuil'

The warnings can be ignored - especially since they are only warnings to tell us that we are using VCL for .NET units that are specific to a platform. To avoid these platform specific warnings, you can disable them if you wish.

- Do Project | Options to go to the Project Options. In the Compiler Messages category, you can uncheck the "Platform Unit" warning (as can be seen in the following screenshot). If you then rebuild the project, you will not get these warnings again.

Hide image

Project Options -Warning Messages

- Do Project | Build mastapp, or hit Shift+F9 to do a Build. This should no longer list the 17 warnings, but still the error.

  [Fatal Error] DataMod.pas(9): F1026 File not found: 'VarUtils.dcuil'

This error is caused by the fact that in VCL for .NET, the VarUtils unit is no longer available (most likely integrated in the Variants unit). So we can remove the no-longer-existent VarUtils unit from the uses clause.

- Remove VarUtils from the uses clause of the interface section of DataMod.pas.

*Note that if you want your project to be compilable to a Win32 target as well, you may want to place the VarUtils unit in an {$IFDEF WIN32} .... {$ENDIF} block instead of just removing the unit from the uses clause.

- If you decide to use the IFDEF solution, modify the uses clause so it looks as follows:

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  DB, IBQuery, IBCustomDataSet, IBTable, IBDatabase, IB, Variants
  {$IFDEF WIN32}, VarUtils {$ENDIF};

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will result in 3 hints and 2 errors.

[Error] EDCUST.PAS(54): E2010 Incompatible types: 'Variant' and 'Double'
[Fatal Error] BrCstOrd.pas(48): F2063 Could not compile used unit 'EDCUST.PAS'

The offending routine that contains the source causing the incompatible type warning is as follows:

procedure TEdCustForm.Edit(CustNo: Double);
begin
  MastData.Cust.Open;
  MastData.Cust.Locate('CustNo', CustNo, []);
  ShowModal;
end;

The error message is about the CustNo argument to the MastData.Cust.Locate. It's of type Double, but a Variant is expected. That should be no problem, but the compiler needs to have the Variants unit added to the uses clause.

- Add the Variants unit to the uses clause of the implementation section of unit Edcust.pas.

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will result in 3 hints and 1 error.

[Fatal Error] EDORDERS.PAS(10): F1026 File not found: 'DBLookup.dcuil'

It appears that the DBLookup unit is also no longer available in VCL for .NET - or required for this project. We can safely remove it.

- Remove the DBLookup unit from the uses clauses of the interface section of Edorders.pas.

*Note that if you want your project to be compilable to a Win32 target as well, you may want to place the DBLookup unit in an {$IFDEF WIN32} .... {$ENDIF} block instead of just removing the unit from the uses clause.

- If you decide to use the IFDEF solution, modify the uses clause so it looks as follows:

uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls,
  Dialogs, Forms, StdCtrls, DBGrids, DBCtrls, DB,
  Buttons, Grids, {$IFDEF WIN32} DBLookup, {$ENDIF} ExtCtrls, Mask;

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will result in 3 hints and 4 errors.

[Error] SrchDlg.pas(54): E2010 Incompatible types: 'Variant' and 'Double'
[Error] SrchDlg.pas(64): E2010 Incompatible types: 'Variant' and 'Double'
[Error] SrchDlg.pas(98): E2010 Incompatible types: 'Variant' and 'TCaption'
[Fatal Error] EDORDERS.PAS(76): F2063 Could not compile used unit 'SrchDlg.pas'

The first three problems are very similar to an error that we saw before, which could simply be solved by adding the Variants unit to the uses clause.

- Add the Variants unit to the uses clause of the implementation section of unit SrchDlg.pas.

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will result in 3 hints, 1 warning, and 2 errors.

[Error] EDORDERS.PAS(105): E2010 Incompatible types: 'Variant' and 'Double'
[Warning] EDORDERS.PAS(229): W1050 WideChar reduced to byte char in set expressions
[Fatal Error] BrCstOrd.pas(48): F2063 Could not compile used unit 'EDORDERS.PAS'

The error should be known by now, as well as the solution. The warning will be taken care of at the end of the cycle - first let's spend our efforts solving the compiler errors.

- Add the Variants unit to the uses clause of the implementation section of unit Edorders.pas.

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will result in 3 hints, 1 warning, and 3 errors.

- Add the Variants unit to the uses clause of the implementation section of unit BrCstOrd.pas.

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will result in 3 hints, 1 warning, and 1 error.

- Remove DBLookup from uses clause (interface) of Edparts.pas, or place the DBLookup unit in an {$IFDEF WIN32} block as follows:

uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, DB, StdCtrls, ExtCtrls, Mask, DBCtrls,
  {$IFDEF WIN32} DBLookup, {$ENDIF} Buttons;

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will result in 3 hints, 1 warning, and 2 errors.

- Add the Variants unit to the uses clause of the implementation section of unit Edparts.pas.

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will result in 3 hints, 1 warning, and 2 errors.

- Add the Variants unit to the uses clause of the implementation section of unit Brparts.pas.

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This will result in 3 hints, 1 warning, and 1 fatal error.

  [Hint] DataMod.pas(244): H2443 Inline function 'ExpandFileName' has not been expanded
  because unit 'System.IO' is not specified in USES list
  [Hint] DataMod.pas(743): H2443 Inline function 'FileExists' has not been expanded because
  unit 'System.IO' is not specified in USES list
  [Hint] DataMod.pas(748): H2443 Inline function 'ExtractFileName' has not been expanded
  because unit 'System.IO' is not specified in USES list
  [Warning] EDORDERS.PAS(229): W1050 WideChar reduced to byte char in set expressions
  [Fatal Error] CustRpt.pas(6): F1026 File not found: 'Quickrpt.dcuil'

At this time, you will also get a design-time error dialog that mentions properties which cannot be found when the Delphi 2005 VCL Designer tries to display the CustRpt unit.

    No QuickReports

The problems are caused by the fact that the Quickrpt.dcuil unit cannot be found by the CustRpt.pas unit: QuickReport for .NET is not included with Delphi 2005. The recommended way to migrate this functionality, is to use Rave Reports for example. Or look for a .NET version of QuickReports, or find some other reporting framework to use.

Reporting is left as exercise for the reader, but we still need to make the project compile - even without the reporting functionality.

- Do Project | View Source to open the project source file mastapp.dpr, and look for the QuickReports units in the uses clause (these are the lines with the TQuickRep types), and place them in comments as follows:

uses
  Forms,
  Main in 'MAIN.PAS' {MainForm},
  Brparts in 'BRPARTS.PAS' {BrPartsForm},
  QryCust in 'QryCust.pas' {QueryCustDlg},
  Edparts in 'EDPARTS.PAS' {EdPartsForm},
  BrCstOrd in 'BrCstOrd.pas' {BrCustOrdForm},
  Edcust in 'EDCUST.PAS' {EdCustForm},
  Edorders in 'EDORDERS.PAS' {EdOrderForm},
  SrchDlg in 'SrchDlg.pas' {SearchDlg},
  Splash in 'SPLASH.PAS' {SplashForm},
  Pickdate in 'PICKDATE.PAS' {BrDateForm},
  About in 'ABOUT.PAS' {AboutBox},
  Pickrep in 'PICKREP.PAS' {PickRpt},
//CustRpt in 'CustRpt.pas' {CustomerByInvoiceReport: TQuickRep},
//OrderRpt in 'OrderRpt.pas' {OrdersByDateReport: TQuickRep},
//InvcRpt in 'InvcRpt.pas' {InvoiceByOrderNoReport: TQuickRep},
  PickInvc in 'PickInvc.pas' {PickOrderNoDlg},
  DataMod in 'DataMod.pas' {MastData: TDataModule};

A nice quick way to place source lines in comments is to press the Ctrl+/ keys. This keystroke will toggle a line of code from uncommented to commented, and move the cursor to the next line. So you can quickly place all three lines in comments.

*Note that you can also select a block of lines of code and then press Ctrl+/ to place the entire block in comments all at once.

- Move to the bottom of the project source file, and locate the three CreateForm statements that create the QuickReports forms. Select them in a block, and press Ctrl+/ to place them all in comments, resulting in the following code:

begin
  Application.Initialize;
  SplashForm := TSplashForm.Create(Application);
  SplashForm.Show;
  SplashForm.Update;
  Application.Title := 'Marine Adventures Order Entry';
  Application.HelpFile := 'MASTAPP.HLP';
  Application.CreateForm(TMainForm, MainForm);
  Application.CreateForm(TBrPartsForm, BrPartsForm);
  Application.CreateForm(TQueryCustDlg, QueryCustDlg);
  Application.CreateForm(TEdPartsForm, EdPartsForm);
  Application.CreateForm(TBrCustOrdForm, BrCustOrdForm);
  Application.CreateForm(TEdCustForm, EdCustForm);
  Application.CreateForm(TEdOrderForm, EdOrderForm);
  Application.CreateForm(TSearchDlg, SearchDlg);
  Application.CreateForm(TBrDateForm, BrDateForm);
  Application.CreateForm(TAboutBox, AboutBox);
  Application.CreateForm(TPickRpt, PickRpt);
//  Application.CreateForm(TCustomerByInvoiceReport, CustomerByInvoiceReport);
//  Application.CreateForm(TOrdersByDateReport, OrdersByDateReport);
//  Application.CreateForm(TInvoiceByOrderNoReport, InvoiceByOrderNoReport);
  Application.CreateForm(TPickOrderNoDlg, PickOrderNoDlg);
  Application.CreateForm(TMastData, MastData);
  SplashForm.Hide;
  SplashForm.Free;
  Application.Run;
end.

This is not enough, however, since the units that use QuickReports are also used in other places of the application. We need to find and comment all of these.

- Do Search | Find in Files, and look for CustRpt (which could be in the uses clause of some other units, and should then be removed).

Hide image

Delphi 2005 Find in Files

*Note that we can group the results by file, and the results will be displayed in a new treeview. As a result, the Main.pas unit is shown.

- Open file Main.pas, and take a look at the uses clause of the implementation section, which includes the CustRpt unit.

*Note that Error Insight automatically marks this unit, as well as the OrderRpt and InvcRpt units as being invalid (the compiler cannot resolve the unit names).

Hide image
Click to see full-sized image

Cannot resolve unit names- plus other uncompilable code

- Use Ctrl+/ to place the CustRpt, OrderRpt, and InvcRpt units in comments.

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

The next error is shown in the PrintCustomerReport method.

- Select the code inside the PrintCustomerReport method and place it in comments, for example as follows (feel free to select more or less code to comment):

procedure TMainForm.PrintCustomerReport(Preview: Boolean);
begin

  with MastData.CustByLastInvQuery do
  begin
    Open;
//    if Preview then
//       CustomerByInvoiceReport.Preview
//    else
//       CustomerByInvoiceReport.Print;
    Close;
  end;
end;

The next problem can be found in the PrintOrderReport method, which again should be placed in comments.

- Select the code inside the PrintOrderReport method, and press Ctrl+/ to put everything in comments, as follows:

Hide image
Click to see full-sized image

PrintOrderReport in comments

There's one place left: the PrintInvoiceReport.

- Select the code inside the PrintInvoiceReport method and place it in comments, for example as follows (feel free to select more or less code to comment):

procedure TMainForm.PrintInvoiceReport(Preview: Boolean);
begin
  if PickOrderNoDlg.ShowModal = mrOk then
//     if Preview then
//        InvoiceByOrderNoReport.Preview
//     else
//        InvoiceByOrderNoReport.Print;
end;

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

This time, the project compiles and links without problems.

    DatabasePath

There's one more thing that needs some attention. If you run the application now, there's a chance that it will work but there's also a good chance that you'll get the message that the database could not be located.

This last issue is actually caused by the code in the application itself. Inside the data module, there's a function called DataDirectory, which is defined as follows:

function TMastData.DataDirectory: string;
begin
  { Assume data is in ..\..\..\..\..\Common Files\Borland Shared\DATA\data relative to where we are }
  Result := ExtractFilePath(ParamStr(0));
  //Result := ExpandFileName(Result + '..\..\DATA\');
  Result := ExpandFileName(Result + '..\..\..\..\..\Common Files\Borland Shared\DATA\');
end;

As the comments say, it's an assumption that the data is located in ..\..\..\..\..\Common Files\Borland Shared\DATA\data relative to where this application is located. This may work for the Demo directory structure, but is not something I would like to use when deploying the application.

In fact, right at the beginning when we started to migrate this project, we made sure that the database could be connected to at design-time. So the Database.DatabaseName is already pointing to the right place.

The DataDirectory function is used in one place: the MastDataCreate method. And we have to undo what's being done there.

- Edit the datamod.pas unit, and locate the MastDataCreate method. Make sure to assignment DataFile with the value of Database.DatabaseName, overwriting the value of the DataDirectory, as follows:

procedure TMastData.MastDataCreate(Sender: TObject);
var
  DataFile: string;
begin
  DataFile := DataDirectory + 'MASTSQL.GDB';
  DataFile := Database.DatabaseName; // already working at design-time!
  if not FileExists(DataFile) then
    if MessageDlg('Could not locate MASTSQL.GDB.  Would you like to locate the file?',
    mtError, [mbYes, mbNo], 0) = mrYes then
      if OpenDialog.Execute then
      begin
        if UpperCase(ExtractFileName(OpenDialog.FileName)) = 'MASTSQL.GDB' then
          DataFile := OpenDialog.FileName
        else
          raise Exception.Create('Invalid File: ' + OpenDialog.FileName);
      end
      else
        raise Exception.Create('Cannot locate Interbase data file: MASTSQL.GDB');
  Database.DatabaseName := DataFile;
  Database.Open;
  Transaction.StartTransaction;
end;

*Note that you can also remove the call to DataDirectory here, but you still need to assign the new value to DataFile.

- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.

The project again compiles and runs, showing the Marine Adventures Order Entry application, as follows:

Hide image

Marine Adventures Order Entry main application

- Click on the New Order button for the Order Form:

Hide image

Order Form

- Close the Order Form and Click on the Browse button for the Orders by Customer Form:

Hide image

Orders by Customer Form

- Close the Orders by Customer Form and Click on the Parts button for the Browse Parts Form:

Hide image

Browse Parts Form

- Close the Browse Parts Form and Click on the Reports button for the Report Selection Form:

Hide image

Report Selection Form

*Note that neither of the three reports will be available - the buttons are not working - because we placed all use of QuickReports and the reporting forms in comments. Feel free to add your own reports.

    Summary and Conclusions

In this tutorial, we have seen how to migrate existing Win32 VCL applications to .NET using Delphi 2005 and VCL for .NET. We started with a threading example, which was first migrated to an unsafe .NET application, and then turned into a 100% safe native .NET application.

We then moved on with a more complex example using databases, which also made use of QuickReports.

The amount of effort that it takes in migrating VCL applications from Win32 to .NET is far less than it would take to rebuild the application. Delphi 2005 offers great support for migration to .NET, protecting our investments of existing Win32 applications.

Server Response from: ETNASC03