Borland® Delphi® 2005 Migration to .NET using VCL for .NET
by Bob Swart, Bob Swart Training & Consultancy
摘要: 本教程示範了使用Delphi 2005 和 VCL (for .NET).將Delphi Win32源碼, 單元(units)和(數據庫)應用程序移植到Microsoft .NET Framework.
- 入門
- VCL和VCL for .NET
- 數據訪問
- 總結
入門
在這個教程, 我們將使用Delphi 2005移植一個真正的Win32應用程序到Microsoft .NET Framework, 并使用Borland VCL(Visual Component Library)作為框架. 幾個現存的VCL for Win32應用程序—從Delphi 7的示例目錄中找到—將用Delphi 2005 打開, 并且移植到.NET并轉為VCL for .NET.
These exercises will demonstrate several migration issues, small and more significant alike.
這個課程將演示几個移植例子,程序小但有代表性.
VCL 和 VCL for .NET
VCL已經可以在.NET Framework下使用, 這讓我們可以更容易的移植Win32 VCL 應用程序到.NET(同樣可能將VCL項目轉為WinForms)
Delphi 2005 在BDS/3.0/Demos目錄中帶了很多應用程序範例, 同時包含了—除了個別外—Delphi.Net 和 Delphi Win32的子目錄. 在BDS/3.0/DelphiWin32/VCLWin32/目錄下包含了幾個Win32 VCL 例子, 其中一些已經移植到.NET, 可以在BDS/3.0/Delphi.NET/VCL目錄中找到.
其中一個還沒移植到.NET的範例Threads, 演示了在多線程中實現QuickSort, SelectionSort 和 BubbleSort算法. Threads 項目是一個Win32 VCL 應用程序, 現在我們將要復印機它到.NET下并使用VCL for .NET
首先, 我們為重要的文件創建一個備份.
- 在BDS/3.0/Demos/Delphi.NET/VCL下創建一個子目錄Threads,.項目移植後將成為另外一個Delphi for .NET VCL的範例.
- 從目錄BDS/3.0/Demos/DelphiWin32/VCLWin32/Threads復製所有的文件到目錄BDS/3.0/Demos/Delphi.NET/VCL/Threads
- 移除thrddemo.bdsproj這個文件, 因為這個文件存在意味著這是一個Delphi Win32項目(而實際我們是想移植到.NET)
現在我們已經準備好要在一個新的Threads項目上工作, 并將其移植到.NET
- 運行Delphi 2005
- 單擊歡迎頁上的” Open Project”按鈕, 并且打開目錄BDS/3.0/Demos/Delphi.NET/VCL/Threads 下的thrddemo.dpr這個項目文件(我們剛從Win32 範例中復製過來的)
因為這個項目沒有存在bdsproj文件, Delphi 2005 IDE 會詢問你是要陞級到Win32還是.NET項目. 將會出現一個項目陞級對話框, 如下圖示
陞級thrddemo項目到.NET
這樣, 將會生成一個thrddemo.bdsproj文件給我們, 裹面有相應的.NET personality標識,.現在我們保存項目, 新的相關屬性信息就會保存到文件thrddemo.bdsproj裹.
- 執行菜單File ! Save All, 保存thrddemo項目, 包括新產生的文件thrddemo.bdsproj
- 按Ctrl+F9 來開始編繹thrddemo 項目
你將會得到大概11條警告和5個氏誤信息, 如下面:
[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'
我們現在可以忽略所有的警告信息, 等一會我們再回頭來處理. 首先, 我們先改正那些編繹錯誤
第一個錯誤是在SortThds.pas文件的第18行, 提示是: t "unsafe pointer only allowed if compiling with {$ UNSAFECODE ON }".(不安全的指針只有在使用{$UNSAFECODE ON}才能編繹).引起錯誤的代碼是在TSortThread類中聲明了指向FSortArray的類型PSortArray:
type
PSortArray = ^TSortArray;
TSortArray = array[0..MaxInt div SizeOf(Integer) - 1] of Integer;
TSortThread = class(TThread)
private
FBox: TPaintBox;
FSortArray: PSortArray;
PSortArray是指向TSortArray的指針, 而指針不是”安全”類型, 因此,會引發警告提示.後面還有一些其它不安全聲明的警告, 只要是使用了這個不安全的指針類型.
雖然最後的版本將不會使用到不安全的類型或代碼, 但首先, 將代碼標記為不安全的方式來解決編繹錯誤比較容易—先使項目順利編繹, 然後再將不安全的部分用安全的代碼代替.
- 在TSortThread類聲明之前增加{$UNSAFECODE ON}這個編繹指示, 這樣, 聲明指向FSortArray的類型就可以被通過
- 按Shift+F2保存所有項目中的文件, 然後按Shift+F9來重新編繹項目.
忽略相關的警告信息, 還有4個錯誤剩下.
[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'
現在有一個聲明為Point未知類型變量.這行令人不快的代碼如下:
procedure PaintLine(Canvas: TCanvas; I, Len: Integer);
begin
Canvas.PolyLine([Point(0, I * 2 + 1), Point(Len, I * 2 + 1)]);
end;
看上去它呼叫了(兩次)一個函數Point, 但不知為什麼, .NET的編繹器無法找到函數Point. 這是一個使用重構(Refactor)--- Find Unit functionality(查找單元函數)的好機會, 它會幫我們定位的缺少的單元(定義函數Point的), 并且增加到單元的uses子句中.
- 在代碼編輯器中, 將光標定位到源代碼的其中一個Point標識符中,然後按右鍵選擇菜單Refactor 下的子菜單Find Unit(另外一種選擇是在系統主菜單Refactor中選擇Find Unit選項)
這樣將會調用Find Unit對話框, 裹面包含查找到所有定義Point的標準單元. 許多單元被查找到, 包含Borland.Vcl.Types.Point, 一個看起來最適合這裹代碼的定義.
- 選擇Borland.Vcl.Types.Point單元,然後選擇Interface或者Implementation來決定在SortThds.pas代碼中的那個uses子句中加入本單元的聲明
-
[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'
我們仍使用FSortArray的不安全指針, 這是提示兩個錯誤發生的原因. 第一個錯誤相關代碼行中Create構造器中, 在這裹,我取得參數SortArray的地址并且指配給一個不安全的FSortArray類指針. 這個代碼是不安全的, 并且是編繹不過的, 除非我們給Create構造器標識一個unsafe的關鍵字.
- 增加unsafe到Create構造器的實現代碼中, 如下面:
· 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;
- 按Shift+F2保存所有項目中的文件, 然後按Shift+F9重新編繹項目.
現在, Create 構造器中的不安全代碼錯誤提示已經不再出現了,然而, 另外一個關于Create 構造器的錯誤信息出現了:
[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'
這個錯誤的實質是, 我們能夠在初始化一些特定的屬性後再呼叫inherited構造器. 然而, 除非你有很好的理由不這樣做, 建議的做法是在使用自定義的代碼前先呼叫inherited構造器. 這個錯誤當然非常容易修改.
- 將呼叫inherited Create移到 Create構造器函數的第一行, 如下所示:
· 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;
- 按Shift+F2保存所有項目中的文件, 然後按Shift+F9重新編繹項目.
現在我們只剩下一個錯誤要修改, 還是不安全代碼的錯誤提示:
[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'
這次, 是在Execute方法中我們對不安全指針FSortArray使用了^操作符. 修改的方法是增加unsafe關鍵字到這個函數.
- 增加unsafe關鍵字到實現Execute方法的代碼中, 如下所示:
· procedure TSortThread.Execute; unsafe;
· begin
· Sort(Slice(FSortArray^, FSize));
· end;
- 按Shift+F2保存所有項目中的文件, 然後按Shift+F9重新編繹項目.
· [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'
這是個大問題, 因為某些原因, Slice函數在.NET如現在我們做的當做參數傳遞時會有問題. 考慮到它在代碼中的作用, 實際可以安全的忽略呼叫Slice的部分, 直接傳遞當前的FSortArray^到函數Sort就可. 在Sort方法中, 我們將使用High和Low來決定實際的數組長度, 這個會運行得很好(注意, 稍後,我們將修改這些代碼為安全的, 受控的代碼).
- 移除呼叫Sort中使用的Slice函數, 代而直接用FSortArray^作為參數傳遞給Sort, 如下所示:
· procedure TSortThread.Execute; unsafe;
· begin
· // Sort(Slice(FSortArray^, FSize));
· Sort(FSortArray^);
· end;
- 按Shift+F2保存所有項目中的文件, 然後按Shift+F9重新編繹項目
現在, 只剩下警告信息了:
[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'
大部分的信息只是告訴我們VCL for .NET只能運行在指定的平台—不要太驚訝這個. 有兩個警告信息比較嚴懲, 指出使用了非安全的代碼:使用了 @ 和^操作符. 稍遲我們會處理這個. 現在, 讓我們忽略警告提示, 先運行項目:
現在項目以可執行的本地.NET方式運行, 它帶有三個并發的線程.
然而, 這個項目還不是100%的安全代碼程序, 現在, 我們繼續工作來修改它.
“安全”代碼
包含不安全代碼的應用程序, 當你用PEVerify檢查時會不能通過. 現在的情況是你能接受一個移植了可運行的結果, 但你的最後目標, 可能還想為你的應用程序實現一個100%安全代碼的版本
- 移除{$UNSAFECODE ON}編繹標識符, 當然還有兩個unsafe的關鍵字(在Create構造器和Execute方法中), 現在準備來修改不安全代碼的部分.
第一個問題—引起所有其它錯誤信息—是指針的聲明. 我們可以將TSortArray聲明為一個只是”整數的數組(array of integer)”. 如果你想保持代碼能同時在Win32和.NET下編繹, 你可以使用編繹指示符來區別原有的代碼和新的代碼.
- 修改TSortArray的定義類型為array of integer, 并且使用編繹指示符來處理舊的WIN32代碼和新的.NET定義, 如下所示:
· {$IFDEF WIN32}
· PSortArray = ^TSortArray;
· TSortArray = array[0..MaxInt div SizeOf(Integer) - 1] of Integer;
· {$ELSE}
· TSortArray = array of Integer;
· {$ENDIF}
- 按Shift+F2保存所有項目中的文件, 然後按Shift+F9重新編繹項目
現在, 你將得到一個關于FSortArray定義的錯誤提示信息, 因為PSortArray現在是一個未知的類型. 使用.NET編繹器, 我們能簡單的定義一個FSortArray, 類型為TSortArray.
- 將字段聲明FSortArray的類型從PSortArray改為TSortArray, 如果你想使用編繹指示符來自理WIN32和.NET代碼, 那將如下所示:
· TSortThread = class(TThread)
· private
· FBox: TPaintBox;
· FSortArray: {$IFDEF WIN32}PSortArray{$ELSE}TSortArray{$ENDIF};
- 按Shift+F2保存所有項目中的文件, 然後按Shift+F9重新編繹項目
現在, 你將得到一個錯誤的提示信息, 提示在create構造器中使用@操作符, 現在這個已經不用在這裹使用了
- 移除在Create構造器中使用的@操作符, 使用編繹指示符, 現在代碼應該如下所示:
· 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;
- 按Shift+F2保存所有項目中的文件, 然後按Shift+F9重新編繹項目
最後的錯誤提示是關于在Execute方法中使用^操作符. 顯而易見, 你能再次將它處理成在.NET編繹器中不使用, 修改如下:
procedure TSortThread.Execute;
begin
// Sort(Slice(FSortArray^, FSize));
Sort(FSortArray{$IFDEF WIN32}^{$ENDIF});
end;
*注意函數Sort帶有一個var參數, 所以, 不用擔心這裹傳送整個interger 數組會被當成值傳遞參數(另外, 你要注意檢查是否需要增加一個const關鍵字)
- 按Shift+F2保存所有項目中的文件, 然後按Shift+F9重新編繹項目
我們現在沒有了不安全代碼的錯誤的提示, 只剩下指定平台(platform specific)的警告了. 現在已經是一個本地的, 安全受控的.NET可執行程序, 使用了VCL for .NET. 這個Threads演示了將指針快速但不完美(不安全)的移植, 然後, 再將其從Win32移植到安全的, 完全受控的.NET for Delphi VCL應用程序的方法.
未完待續