Perl 中的条件编译

Conditional Compilation in Perl

如何让下面的代码工作?

use strict;
use warnings;

if ($^O eq 'MSWin32' || $^O eq 'MSWin64') {
    use Win32;    
    Win32::MsgBox("Aloha!", MB_ICONINFORMATION, 'Win32 Msgbox');     
}
else {
    print "Do not know how to do msgbox under UNIX!\n";
}

以上在 Windows 下运行。但在 UNIX 下,会出现编译错误,找不到 Win32。将 "use" 替换为 "require" 会使事情变得更糟——代码将无法在 Windows 和 UNIX 下编译,因为包含 MB_ICONINFORMATION 的行始终被编译,而 "MB_ICONINFORMATION"将是一个未声明的裸词。

那么我该如何解决这个问题呢?

Perl 首先将代码编译为中间表示,然后执行它。由于 if 在 运行 时计算,但 use 在编译期间处理,因此您没有有条件地导入模块。

要解决此问题,有多种可能的策略:

  • 使用 use if pragma
  • 条件导入
  • 带 BEGIN 块的条件导入
  • require 模块
  • 使用 eval
  • 延迟编译

只在满足特定条件时导入模块,可以使用if pragma:

use if $^O eq 'MSWin32', 'Win32';

您还可以 运行 在编译期间将代码放入 BEGIN 块中:

BEGIN {
  if ($^O eq 'MSWin32') {
    require Win32;
    Win32->import;  # probably not necessary
  }
}

BEGIN 块的行为与上面的完全相同 use if

注意这里必须使用require。使用 use Win32,模块将在开始块的编译期间加载,绕过 if。使用 require,模块在开始块的 运行 期间加载,也就是在周围代码的编译期间。

在这两种情况下,Win32 模块只会在 Windows 下导入。这使得 MB_ICONINFORMATION 常量在非 Windows 系统上未定义。在这种代码中,最好不要导入任何符号。相反,对所有内容使用完全限定名称并使用括号进行函数调用(此处:Win32::MB_ICONINFORMATION())。有了这个改变,只使用 require 而不是 use if 也可能有效。

如果以后需要代码 运行,可以使用字符串求值。但是,这可能会导致安全问题,更难调试,而且通常速度较慢。例如,您可以这样做:

if ($^O eq 'MSWin32') {
    eval q{
        use Win32;    
        Win32::MsgBox("Aloha!", MB_ICONINFORMATION, 'Win32 Msgbox');     
        1;
    } or die $@;  # forward any errors
}
  • 因为 eval 默认情况下会消除任何错误,您必须检查是否成功并可能重新抛出异常。 1 语句确保 eval'ed 代码 returns 如果成功则为真值。 eval returns undef 如果发生错误。 $@ 变量保存最后一个错误。
  • q{...} 是另一种引用结构。除了作为字符串定界符的大括号外,它与 '...'(单引号)完全相同。

如果您有很多代码只能在特定平台上运行,那么对每个代码段使用上述策略是乏味的。相反,为每个平台创建一个模块。例如:

Local/MyWindowsStuff.pm:

package Local::MyWindowsStuff;
use strict;
use warnings;
use Win32;

sub show_message {
  my ($class, $title, $contents) = @_;
  Win32::MsgBox("Aloha!", MB_ICONINFORMATION, 'Win32 Msgbox');
}

1;

Local/MyPosixStuff.pm:

package Local::MyPosixStuff;
use strict;
use warnings;

sub show_message {
  warn "messagebox only supported on Windows";
}

1;

在这里,我将它们编写为可用作 classes。然后我们可以有条件地加载这些 classes 之一:

sub load_stuff {
  if ($^O eq 'MSWin32') {
    require Local::MyWindowsStuff;
    return 'Local::MyWindowsStuff';
  }
  require Local::MyPosixStuff;
  return 'Local::MyPosixStuff';
}

my $stuff = load_stuff();

最后,我们没有在您的代码中加入条件,而是在加载的 class:

上调用方法
$stuff->show_message('Aloha!', 'Win32 Msgox');

如果您不想创建额外的包,一种策略是评估代码参考:

sub _eval_or_throw { my ($code) = @_; return eval "$code; 1" or die $@ }

my $show_message =
  ($^O eq 'MSWin32') ? _eval_or_throw q{
    use Win32;
    sub {
      Win32::MsgBox("Aloha!", MB_ICONINFORMATION, 'Win32 Msgbox');
    }
  } : _eval_or_throw q{
    sub {
      warn "messagebox only supported on Windows";
    }
  };

然后:$show_message->()调用此代码。这避免了用 eval 重复编译相同的代码。当然,只有当此代码每个脚本 运行 不止一次时才重要,例如在循环或子程序中。