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
重复编译相同的代码。当然,只有当此代码每个脚本 运行 不止一次时才重要,例如在循环或子程序中。
如何让下面的代码工作?
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
returnsundef
如果发生错误。$@
变量保存最后一个错误。 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
重复编译相同的代码。当然,只有当此代码每个脚本 运行 不止一次时才重要,例如在循环或子程序中。