如何处理.dpr文件中的IFDEF语句?

6
每当您向项目添加新单元时,Delphi会重新构建.dpr文件,并且在uses部分中的所有IFDEF都会消失。
为了解决这个问题,我通常使用记事本创建新的.pas文件,并手动将其添加到.dpr文件中。如果我需要一个窗体,我使用“文件”->“新建”->“窗体”,然后将.dpr文件恢复到以前的版本。如果您问我,这不是非常快速应用程序设计(RAD);-)
您如何处理这个问题?有没有一种方法可以在IDE中添加单元并保留IFDEF?

请问您能否解释一下为什么您只希望某些时候将一个单元作为项目的成员?我不理解其目的。 - Rob Kennedy
4
我们的项目在发布版本中使用随 Delphi 附带的 FastMM,但在调试版本中使用外部的 FastMM4.pas。因此,在 "uses FastMM4;" 周围加了 IFDEF。 - Uli Gerhardt
@Ulrich:我在DEBUG和RELEASE版本中都使用FastMM4——为什么不呢?它是项目SVN存储库的一部分,而且无论编译器版本如何,我都可以得到相同的环境。有什么不喜欢的呢? - mghie
我必须承认我没有太多考虑过这个问题。直觉上,我不想在(发布)项目中加入我不使用的东西。 - Uli Gerhardt
2
@Rob:如果产品有不同版本(例如:便宜版和昂贵版),那么省略某些功能可能会很方便。 - Giel
@Rob:如果你正在使用类似Testcomplete这样的产品,并且想要完全访问类,你需要将一些Testcomplete单元添加到你的项目中。你不希望在生产环境中出现这些单元。IFDEF将是解决这个问题的干净方法。 - Lieven Keersmaekers
7个回答

9

有时我会创建一个单元,专门用作放置所有IFDEF和其他IDE可能会搞砸的内容的地方。这个单元通常放在dpr的uses子句的顶部。这个技巧并不能适用于所有情况,但有时可以节省很多繁琐的工作。


+1. 我不记得曾经使用过单元的条件包含。为什么有人想要这样做呢? - mghie
1
@mghie:跨编译器和RTL版本的可移植性。跨编译器的可移植性(Delphi vs. C++ Builder)。避免包含依赖于构建配置的单元(例如,在发布构建中,您可能不希望有堆栈转储支持,因为没有调试符号)。还有其他一百种情况。 - Mihai Limbășan
首先,这不会导致有条件包含的单元成为项目的一部分(因此可以通过项目管理器访问)。 - Oliver Giesen
2
@mghie:内存管理器必须是链接器看到的第一个单元,这只能通过.dpr文件实现。这就是我所提到的用例(我也使用FastMM,除了在一些应用程序中遇到错误时,我不得不在.dpr文件中维护IFDEFs以测试各种版本)。 - Mihai Limbășan
1
是的,我使用了额外的单元,但只是因为Delphi在dpr中放置IFDEFs时会感到困惑。 - gabr
显示剩余6条评论

3

我花了很长时间才明白这个问题,

最终我为每种构建类型准备了一个项目文件(.dpr), 在项目|项目选项|目录/条件中设置条件, 只将我想要添加到项目中的单元添加进去。

这样做的缺点是,如果你在.dpr中有自定义代码,当它发生变化时,必须手动复制到其他项目文件中。

正如Rob Kennedy所指出的,可以通过将自定义代码放入其自己的单元中,并调用单个过程来处理。从而最小化.dpr代码大小/需要进行的更改。

另外,你还能获得额外的好处,如果你将所有的.dpr文件添加到一个项目组中,你可以通过一个点击或命令行来构建所有不同版本。


1
大多数 DPR 代码可以通过将其移动到不同的单元并然后使用单行调用来最小化。这样,DPR 代码就不太可能需要更改。 - Rob Kennedy
谢谢你的提醒,我已经更新了我的回答并准备去更新我的代码 ;) - Christopher Chase

3

我不在dpr文件中放置任何ifdefs。如果我想在项目中根据某些条件使用不同的单元/窗体,我会将项目拆分为两个。


2
你可以在IDE内手动添加它(使用项目上的“查看源代码”选项)。
通常情况下,dpr是“隐藏”的。你不应该在其中更改任何内容,如果你这样做了,最好确保所有更改都是手动进行的,否则你可能会失去一些信息。

1
对于包含单个类的表单、数据模块和其他单元,其功能将被替换,解决方案相当简单。只需不要直接将自定义单元添加到产品中,而是将它们保存在搜索路径中的某个位置(或修改项目搜索路径以包括它们的位置)。
1)创建一个新单元,其中包含所有其他类的父类或它们都将实现的接口(我通常更喜欢后者,因为它可以更轻松地进行定制)[例如,此单元称为uSpecialParent.pas]。
2)添加一个类变量,当您需要创建新功能时将引用该变量。例如,如果您只想显示一堆表单,所以不关心任何其他方法,那么您可以有一个变量看起来像以下内容:
TYPE
  TMySpecialFormClass : class of TForm;

VAR
  TMySpecialForm : TMySpecialFormClass;

3) 创建另一个单元,其中包含所有的IFDEF。它可能看起来像以下内容:

Unit uRegisterSpecialForms;

interface

uses
{$IFDF SPECIAL1}
  uSpecial1,
{$ENDIF}
{$IFDEF SPECIAL2}
  uSpecial2,
{$ENDIF}
  uSpecialParent;

implementation

// no code needed.

initialization

{$IFDEF SPECIAL1}
  TMySpecialForm := uSpecial1.TSpecialForm1;
{$ENDIF}
{$IFDEF SPECIAL2}
  TMySpecialForm := uSpecial2.TSPecialForm2;
{$ENDIF}

end.

4) 在您的代码中引用此内容,您只需要在请求特殊表单的单元中添加uSpecialParent,然后动态创建它。例如,要显示此模态框,您可以调用以下内容:

var
  frm : TForm;
begin
  frm := TMySpecialForm.Create(nil);
  try
    frm.showmodal;
  finally
    frm.free;
  end;
end;

1

为了完整起见,这里提供一种低技术的方法:

当IDE再次搞乱你的uses子句后:

  1. 关闭项目
  2. 使用类似WinMerge这样的合并差异工具,通过将DPR与最新签入版本进行比较来查看差异
  3. 撤销IDE所做的更改
  4. 保存DPR
  5. 继续工作

0

(Delphi 7)

我刚刚尝试了一下。 看看第一个代码版本和我的下面的评论:

program Project1;

{$IFDEF TESTIFDEF}
uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form2};

{$ELSE}
uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};
{$ENDIF TESTIFDEF}

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.CreateForm(TForm2, Form2);
  Application.Run;
end.

在那时,我刚刚插入了第二个窗体并注意到相应的单元(Unit2.pas)被插入到IFDEF的第一部分中,即在“TESTIFDEF”标记的部分内 - 因此未覆盖第二个块({$ELSE}之后)。

因此,您的解决方案应该是:

  1. 定义一个像 "{$IFDEF DELPHIBASISCONFIGURATION}" 这样的 IFDEF 语句,代替我的 "{$IFDEF TESTIFDEF}",在其中添加所有窗体。
  2. 为您想要使用的不同配置定义尽可能多的备用标签。
  3. 每次向项目添加窗体时,将第一个块中插入的行复制到下面的相应块中 - 根据您的需求...
  4. 使用定义语句或选项对话框激活所需的配置
  5. 永远不要定义 "DELPHIBASISCONFIGURATION" ;)

因此,它应该看起来像这样:

program Project1;

{$DEFINE MYCONFIG1} // THIS ONE IS NOW ACTIVE


{$IFDEF DELPHIBASISCONFIGURATION}
uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form2},
  Unit3 in 'Unit3.pas' {Form3};

{$ELSE}
     // THIS IS A "COMMON TO ALL CONFIG" PART
     uses
       Forms,

       // FIRST CONFIGURATION
       {$IFDEF MYCONFIG1}
       Unit1 in 'Unit1.pas' {Form1},
       Unit3 in 'Unit3.pas' {Form3}
       {$ENDIF MYCONFIG1}

       // SECOND CONFIGURATION    
       {$IFDEF MYCONFIG2}
       Unit1 in 'Unit1.pas' {Form1},
       Unit2 in 'Unit2.pas' {Form2}
       {$ENDIF MYCONFIG2}

     // THIS IS THE "COMMON TO ALL CONFIG" END :)
     ;

{$ENDIF TESTIFDEF}

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  //Application.CreateForm(TForm3, Form3);
  //Application.CreateForm(TForm2, Form2);
  Application.Run;
end.

正如您所看到的,我已经放弃了对Form2和Form3的Application.CreateForm(...)调用。

在我看来,通常最好在真正需要这些补充窗体的时候动态创建它们,即不是在程序启动时创建所有窗体...


1
这是一个非常聪明的想法。不幸的是,它在D2007上无法工作,因为它显然理解了定义并修改了“真实”部分 :-( - Giel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接