Delphi 2005中Win32程序移植到.NET (第一部分)

Delphi 2005中Win32程序移植到.NET
 (第一部分)


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

by Bob Swart, Bob Swart Training & Consultancy, translate into chinese by Visli. 

摘要 - 本文示范如何在Borland Delphi 2005中用VCL (for .NET)移植已有VCL for Win32应用程序到Microsoft .NET Framework.

  • 引言
  • VCL 与 VCL for .NET
  • 数据存取
  • 总结

引言
本文中, 我们将在 Delphi 2005 中使用 Borland VCL (可视化组件库)移植两个现成的 Win32 应用程序到 Microsoft .NET Framework. 这两个现成的 VCL for Win32 应用程序来自 Delphi 7 的 Demos 目录 - 它们将在 Delphi 2005 中打开并被用 VCL for .NET 移植到 .NET 框架.

我们会示范如何解决移植过程中出现的若干问题, 虽然是些小问题但非常有意义.

VCL 与 VCL for .NET
现在 VCL 已经可以完美的应用到 .NET 框架, 这就允许我们可以很容易移植 Win32 VCL 应用程序到 .NET  (VCL 项目移植到 WinForms 也将会变成可能).

Delphi 2005 在 BDS/3.0/Demos 目录下自带有大量的示例程序, 该目录下有 Delphi.NET 和 DelphiWin32 两个子目录. BDS/3.0/DelphiWin32/VCLWin32/ 目录包含若干个 Win32 VCL 例程, 其中大部分已经被移植到 .NET, 读者可以在 BDS/3.0/Delphi.NET/VCL 目录中找到.

而其中尚有一个没有被移植到 .NET 的就是 Threads 例程, 该例程用来示范在多线程中进行快速排序, 选择排序以及冒泡法排序. 这个 Threads 工程就我们将要用 VCL for .NET 移植到 .NET 的Win32 VCL应用程序.

首先, 我们要创建一个必要文件的拷贝.

  • 在 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 工程 .

现在我们开始移植 Threads 工程到 .NET.

  • 运行 Delphi 2005
  • 从 BDS/3.0/Demos/Delphi.NET/VCL/Threads 目录打开工程文件 thrddemo.dpr

由于没了 .bdsproj 文件的的关联, 打开工程文件时,  Delphi 2005 IDE 会询问您是要更新为一个 Win32 工程还是 .NET 工程. 下图就是工程更新对话框:

thrddemo工程更新为 .NET

  • 选择 Delphi for .NET 单选框, 点击OK确定.

这将生成一个新的 thrddemo.bdsproj 文件, 里面指定了 .NET 的一些特性. 我们现在保存工程.

  • 点击主菜单 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行, 叙述为 "unsafe pointer only allowed if compiling with {$UNSAFECODE ON}"(不安全的指针, 除非使用{$UNSAFECODE ON}进行编译). 引起该报警的实际代码如下, 是用来在 TSortThread 类的定义中声明一个 PSortArray 类型的 FSortArray 变量:

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+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 函数. 这可是一个示范如何使用重构技术的好案例 - Find Unit (查找单元)功能, 该功能用于帮助我们定位遗漏的单元 (Point 函数定义的地方), 并把它添加到 uses 子句.

  • 在代码编辑器中, 把鼠标移到 Point 标识符上, 右击鼠标弹出菜单, 在 Refactor 菜单项上选择 Find Unit 子菜单项.

这将弹出 Find Unit 对话框, 并且 Point 已经自动填写在 Search 条件中. 而许多的单元也已被提出, 包括 Borland.Vcl.Types.Point, 它看上去应该就是定义 Point 的单元.

  • 选择 Borland.Vcl.Types.Point 单元, 对话框下面的 Interface 与 Implementation 选项用于指示添加单元引用到那个部分, 这里任选一个, 单击确定.

    重构技术 - 查找单元

  • 保存全部文件, 按下 Shift+F9 重新编译.

现在将返回3个错误. 总之, 最后那个致命错误(Fatal Error)事实上是由于前面有两个错误, 使用无法完成单元编译造成了. 所以事实上只有两个错误要改正.

[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 关键字, 否则编译不会成功.

  • 在 Create 构造函数的实现部分加上  unsafe 关键字, 如下所示:
    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+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. 当然这很容易修改.

  • 在Create 构造函数实现代码中, 把 inherited 调用移到第一行:
    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+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 关键字.

  • 给 Execute 方法的实现部分添加上 unsafe 关键字, 如下所示:
    procedure TSortThread.Execute; unsafe;
    begin
      Sort(Slice(FSortArray^, FSize));
    end;
    
  • 保存全部文件, 按下 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'
    

这下遇到了一个大问题. 由于某些原因, .NET 下的Slice 函数在传递参数时存在一个问题. 考虑到它所实现的功能, 我们可以忽略调用 Slice, 而直接到 FSortArray^ 传递给 Sort. 在 Sort 方法内部, 我们将使用 High 和 Low 来判断数组的实际大小.(译注: Slice 函数在 VCL for Win32 中用于返回数组的片断,原型是 function Slice(var A: array; Count: Integer): array; Slice 函数只能作为参数使用, 而不能单独使用)

  • 在Sort 调用中直接用 FSortArray^ 代替 Slice 函数, 如下:
    procedure TSortThread.Execute; unsafe;
    begin
    //  Sort(Slice(FSortArray^, FSize));
      Sort(FSortArray^);
    end;
    
  • 保存全部文件, 按下 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 is specific to a platform. 最后两个警告比较严重, 反映了不安全代码:  @ 和 ^ 操作. 我们稍候来处理这些. 现在, 让我们先不理会这些警告, 而是先把工程运行起来.

.NET 下线程排序示例

现在, 程序是作为一个纯 .NET 可执行文件运行.

然而, 它并不是一个 100% 安全的应用程序, 所以我们要回到安全处理上来.

"Safe" 代码
应用程序包含不安全代码, 所以当您使用 PEVerify 检查它们时将会产生失败. 我们继续努力, 争取实现 100% 安全的 .NET 应用.

  • 移除 {$UNSAFECODE ON} 编译指令和两个 unsafe 关键字(Create 构造函数与 Execute 方法), 准备改正这些不安全代码部分.

第一个问题 - 也是引发所有其它问题的原因 - 就是指针类型的定义. 我们用一个 "array of integer" 来替换 TSortArray 的定义. 如果您希望使得代码即能在 Win32 下编译也能在 .NET 下编译, 您可以使用编译指令来进行区分.

  • 修改 TSortArray 的类型定义为整形数组, 并使用编译指令来区分 Win32 与 .NET 编译器, 如下所示:
    {$IFDEF WIN32}
      PSortArray = ^TSortArray;
      TSortArray = array[0..MaxInt div SizeOf(Integer) - 1] of Integer;
    {$ELSE}
      TSortArray = array of Integer;
    {$ENDIF}
    
  • 保存全部文件, 按下 Shift+F9 重新编译.

这次, 您将得到一个关于 FSortArray 定义的错误信息, 由于现在 PSortArray 成了一个未知的类型. 使用 .NET 编译器, 我们可以简单定义 FSortArray 作为一个 TSortArray 类型.

  • 修改 FSortArray 定义为 TSortArray. 当然也可以使用编译指令进行区分, 如下所示:
      TSortThread = class(TThread)
      private
        FBox: TPaintBox;
        FSortArray: {$IFDEF WIN32}PSortArray{$ELSE}TSortArray{$ENDIF};
    
  • 保存全部文件, 按下 Shift+F9 重新编译.

这时, 您将得到一个不能在 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+F9 重新编译.

最后一个错误就是关于在 Execute 方法中使用 ^ 操作. 同样, 我们可以进行如下修改:

procedure TSortThread.Execute;
begin
//  Sort(Slice(FSortArray^, FSize));
  Sort(FSortArray{$IFDEF WIN32}^{$ENDIF});
end;
  • 保存全部文件, 按下 Shift+F9 重新编译.

我们现在就只剩下平台特殊的警告信息了, 不再有不安全代码错误. 这下就得到了一个使用 VCL for .NET 的安全纯 .NET 可执行文件了.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值