CPerlWrap - 用于将 Perl 嵌入 MFC 程序的类包装器






4.85/5 (22投票s)
一个简单的类,可以快速、轻松地访问 Perl 和 Perl 变量。
目录
引言
我经常发现自己必须在 VC++ 或 Perl 中做一个项目,而不是两者都做。Perl 在字符串操作、任意对象的哈希和数组以及 DWIM(做你想做的事)行为方面非常出色。VC++ 速度快,具有出色的类型检查和调试功能,并且可以轻松地将生成的程序打包到其他机器上。Perl 要求目标机器已安装 Perl。有些操作在 Perl 中只需一行或两行,而在 VC++ 中则需要 100 行或 200 行(反之亦然)。Perl 在原型设计等方面速度非常快,不胜枚举。
我曾看过 Perl 的手册页(perlguts、perlembed、perlapi 等),它们展示了将 Perl 嵌入 C/C++ 有多容易(哈!),但对于不深入 Perl 底层的人来说,它们几乎是不可理解的。几乎和 OLE 一样糟糕!:-)
此外,即使有了嵌入 Perl 的代码,仍然存在将 C++ 变量传入和传出该 Perl 实例的问题。还需要更多晦涩的魔法。这促使我花了一些时间阅读和测试 Perl 的嵌入功能。这里几乎所有的内容都来自 Perl 的手册页,特别是 perlguts、perlembed 和 perlapi。这些内容不适合胆小的人。它们肯定不适合随便使用。
这项工作,再加上一些使用嵌入式 Perl 的实际应用经验,产生了以下内容:
类 CPerlWrap
更新(2012 年 2 月 21 日):另请参见源代码存档中的 CPerlWrapSTL,这是一个非 MFC 版本,由 CodeProject 会员 SLJW(又名 jwilde)提供。
此类允许您创建 Perl 实例,将变量传入和传出该实例,并运行任意脚本。该实例“保持活动”状态,直到被显式销毁,因此您可以运行多个不同的脚本而无需重新实例化。
Perl 中的三个主要变量类型是标量 ($abc
)、列表 (@def
) 和哈希 (%ghi
),它们分别对应 MFC 的 CString
/int
/double
(对于标量)、CStringArray
(对于列表)和 CMapStringToString
(对于哈希)。对于这三者中的每一种,都有一个 get 和一个 set 函数。
// These are used to create and populate arbitrary variables.
// Good for setting up data to be processed by the script.
// They all return TRUE if the 'set' was successful.
// set scalar ($varName) to integer value
BOOL setIntVal(CString varName, int value);
// set scalar ($varName) to double value
BOOL setFloatVal(CString varName, double value);
// set scalar ($varName) to string value
BOOL setStringVal(CString varName, CString value);
// set array (@varName) to CStringArray value
BOOL setArrayVal(CString varName, CStringArray &value);
// set hash (%varName) to CMapStringToString value
BOOL setHashVal(CString varName, CMapStringToString &value);
// These are used to get the values of arbitrary
// variables ($a, $abc, @xyx, %gwxy, etc.)
// They all return TRUE if the variable was defined and set
// get scalar ($varName) as an int
BOOL getIntVal(CString varName, int &val);
// get scalar ($varName) as a double
BOOL getFloatVal(CString varName, double &val);
// get scalar ($varName) as a string
BOOL getStringVal(CString varName, CString &val);
// get array (@varName) as a CStringArray
BOOL getArrayVal(CString varName, CStringArray &values);
// get hash (%varName) as a CMapStringToString
BOOL getHashVal(CString varName, CMapStringToString &value);
因此,如果我有一个 CString
,我想对其执行一些 Perl 操作,例如将所有单词提取到一个单词数组中,这是我的 VC++ 代码:
// perlInst is an instance of CPerlWrap
CString str("this is a verylong set of words"
" that would be a pain to deal with in C++");
perlInst.setStringVal("string",str);
perlInst.doScript("@b = split(/\s+/, $string);");
CStringArray words;
perlInst.getArrayVal("b", words);
(是的,这可以用 C++ 完成,但这只是一个简单的例子!)
或者,也许我想使用以下 VC++ 代码将该字符串中的每个单词大写:
// perlInst is an instance of CPerlWrap
CString str("this is a verylong set of "
"words that would be a pain to deal with in C++");
perlInst.setStringVal("string",str);
perlInst.doScript("$string =~ s/(\w+)/\u\L$1/g;");
perlInst.getStringVal("string", str);
结果
This Is A Verylong Set Of Words That Would Be A Pain To Deal With In C++
或者,如何获取第一个非平凡大小的复数词和一些上下文?
// perlInst is an instance of CPerlWrap
CString str("this is a verylong set of words"
" that would be a pain to deal with in C++");
perlInst.setStringVal("string",str);
perlInst.doScript(
"$str =~ m/(\w+)\s+(\w{3,}s)\s+(\w+)/;\n"
"$match = \"lead context = '$1' "
"match = '$2' trail context = '$3'\";"
);
CString match;
perlInst.getStringVal("match", match);
这将导致 match
包含:
lead context = 'of' match = 'words' trail context = 'that'
啊!引起您的注意了!很好。
脚本不一定非得是一行。
CString script(
"$a = \"this is a verylong set of "
"words that would be a pain to deal with in C++\";\n"
"$a=~ s/(\w+)/\u\L$1/g;"
);
perlInst.doScript(script);
perlInst.getStringVal("a",str);
碰巧,这个特定的脚本实际上并不需要嵌入的换行符 \n
,但如果您希望错误消息指向除第 1 行之外的其他内容,您将添加换行符。
错误检测和错误消息
错误消息?是的,尽管听起来可能很令人震惊,但有时您运行的 Perl 脚本中存在错误。这对我来说(#include <NoseGettingLonger>
)当然从来没有发生过,但我包含了一些支持。这是一个展示错误并访问 Perl 报告的示例:
// this is missing the ';' at the end of the first line
CString script(
"my $d = 'this is a verylong set of words'\n"
"$d =~ m/(\w+)\s+(\w{3,}s)\s+(\w+)/;"
);
if(!perlInst.doScript(script))
{
CString errmsg = perlInst.getErrorMsg();
if(!errmsg.IsEmpty())
errmsg = getWarnings();
MessageBox(errmsg,"Script Failure");
}
这将产生:
Scalar found where operator expected at (eval 18)
line 2, near "'this is a verylong set of words'
$d"
(Missing operator before
$d?)
默认情况下,警告不被视为错误,并且在执行脚本之前会清除所有警告。但如果您想轻松检测警告和错误,可以使用这两个函数来调整 CPerlWrap
的行为:
// set to TRUE if warnings cause doScript() to return FALSE
BOOL SetfailOnWarning(BOOL);
// set to TRUE if warnings are
// cleared before executing a doScript()
BOOL SetclearWarningsOnScript(BOOL);
将 CPerlWrap 放入您的项目
首先也是最重要的,要使用 CPerlWrap
构建项目,您需要在您的构建机器上安装 Perl 5.14(或更高版本)。在目标机器上安装 Perl 不是必需的,但必须在您的构建机器上。您的目标机器必须具有 Perl512.dll 文件(或 Perl514.dll 或您编译所用的任何版本),所以别忘了将它与可执行文件一起打包!
但是,如果您使用 Perl 包,那么在目标机器上安装 Perl 可能更好。
访问 http://www.activestate.com/ 并下载 **免费** 的 Windows Perl。价格合适。然后安装它。我在这里等您安装完成。
好了?太好了!您花的时间够长的了!
接下来,将 PerlWrap.h 和 PerlWrap.cpp 复制到您的项目目录中。使用 Project->Add 到 Project->Files...(或任何最新的 Visual Studio 机制)将其添加到您的项目中。暂时不要构建;还有其他事情要做。
您需要添加 Perl CORE 文件的 *Include* 目录:
- 在 Project->Settings...
- Settings for: All Configurations(不要只保留 Win32 Debug!)
- C/C++ 选项卡
- Category: Preprocessor
- Additional include directories: C:\Perl\lib\CORE(是的,尽管这是用于文件包含,但它写着 'lib'!)
这假定您已将 Perl 安装到 C:\Perl。如果您安装在其他地方,请进行适当的调整 **并且** 对 PerlWrap.h 的顶部进行类似的调整。
// Adjust this to point to the proper .lib file for Perl on your machine
// Remember to package Perl514.dll along with your project when you install
// onto other machines!
#if !defined(NOIMPLINK) && (defined(_MSC_VER) || defined(__BORLANDC__))
# pragma comment(lib,"C:/Perl/lib/CORE/Perl514.lib")
#endif
现在重新构建。检查您是否可以浏览 CPerlWrap
类。如果可以,那么是时候用它做些什么了!
在您工作所在的类中添加一个成员变量。对我来说,这通常是 CMyProjectView
,我添加:
// Implementation
public:
CPerlWrap perlInstance;
virtual ~CCPerlWrapperView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
如果您愿意(推荐),可以调整 Perl 的行为:
void CMyProjectView::OnInitialUpdate()
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
perlInstance.SetclearWarningsOnScript(TRUE);
// blah, blah ...
提示和陷阱
反斜杠
使用 CPerlWrap
最困难的部分是反斜杠 (\
)。如果您有一个要在 Perl 中进行求值(插值)的字符串,例如 "$var1 is xyz to $var2"
,则该字符串必须用 "
字符括起来,并且您必须在 VC++ 代码中转义这些引号:
CString script("$string = \"$var1 is xyz to $var2\";");
perlInst.doScript(script);
另一方面,如果您只是想有一个 **不** 被插值的字符串,请使用单引号:
CString script("$string = 'this is an uninterpolated string';");
perlInst.doScript(script);
如果您需要在脚本中包含反斜杠,则需要将其加倍,以免 VC++ 解释它。请注意,\\d
用于将 \d
(匹配数字的模式)放入脚本中:
CString script(
"$string =~ m/(\\d+)/;\n"
"$firstNumber = $1;"
);
perlInst.doScript(script);
CString firstNumber;
perlInst.getIntVal("firstNumber",firstNumber);
如果您需要插入反斜杠,情况会变得非常糟糕:
perlInst.doScript("$StartDir =~ s%/%\\\\%g; "
"# change '/' to Windows-style '\'")
Perl 中的进程
出于我未能发现的原因,这个嵌入式 Perl 不允许子进程(**注意**:此声明来自 2003 年;到 2012 年情况可能已经发生变化)。所以 Perl 的常用方式,如:
open(F,"./unzip.exe -p db.zip |") or die("Cannot open pipe from unzip, $!");
@uncompressed = <F>; # suck in entire file, one line per list element
close(F);
就是不起作用!使用反引号“**`
**”或 system()
函数也是如此。就是不起作用。如果有人有解决办法,请告诉我,因为它一直让我很沮丧。
变量作用域
在 Perl 中,my
运算符用于声明当前作用域中的变量。作用域由像 VC++ 一样的周围 {}
对确定。doScript()
函数执行 Perl eval {script}
(注意 {}
对),因此使用 my
声明的任何变量都 **不** 可用于 get* 和 put* 函数;它们仅限于该 eval
实例。如果您喜欢在代码中使用 use strict;
,那么您将不得不使用 put* 函数定义所有“全局”变量(这会将它们放入 main::
模块中)。
使用 Perl 模块
Perl 的一个巨大优势是其大量的可用模块。这些是 C/C++ 库的 Perl 等价物。模块使用以下语法包含:
use CGI;
use Win32;
其中 CGI
和 Win32
是两个这样的模块。这些模块通常包含在 Perl 安装的目录树中。这意味着在 CPerlWrap
中使用 Perl 模块要求目标机器上存在该树。
如果模块是纯 Perl(没有嵌入 C 函数),那么您可以将模块(CGI.pm、Win32.pm 或其他)复制到目标机器,并使用 use lib('some new directory');
pragma 告诉 Perl 在哪里找到它。
但是(总有“但是”),如果您需要一个带有嵌入 C 函数的模块(例如,遗憾的是 Win32),那么您将不得不修改 xs_init()
函数(位于 PerlWrap.cpp 中),这“远远超出了我的知识范围”。我留下了一些注释(摘自手册页)供您开始,但我对此一无所知。如果您需要这样的模块,请从 perlguts、perlapi 和 perlembed 开始。
更新:最近版本的 Perl 对这类事情提供了更好的支持。事实上,这两个命令是您的朋友:
# gets a list of libraries that you can reference with:
# #pragma comment(lib,”libraryName.lib”)
perl -MExtUtils::Embed -e ccopts -e ldopts
# creates the xs_init() function with the hooks for compiled modules
perl -MExtUtils::Embed -e xsinit -- -o perlxsi.c
总结
CPerlWrap
可能会永远处于开发中,因此当我进行重大更改时,我会尝试更新本文。我怀疑更改的最大来源将是您们指出的所有错误修复!
我并不自称是 perlguts 专家——一切都在 Perl 手册页中,我所做的只是试图将其包装起来,使其易于使用。请参阅下面的免责声明。
免责声明
您的使用情况可能有所不同。禁止区域无效。请勿内服。不适用于眼科用途。不适用于 65 岁以下儿童。请勿在睡眠时使用。警告:可能引起嗜睡。仅限室内或室外使用。仅限越野使用。仅限办公室使用。请勿尝试用手或生殖器停止链条。请记住,镜子中的物体实际上比它们看起来更近。本产品未经动物测试。没有人类在创建此页面时受到伤害,甚至没有被使用。请勿内服,无论是字面意思还是严肃意义。
需要一些同化。抵抗是徒劳的。
本产品仅用于教育目的。制造商不对由此产生的任何损害或不便负责,并且不得合法地表达或暗示任何相反的主张。需要一些组装。仅按指示使用。不提供其他明示或暗示的保证。在操作机动车辆或重型设备时请勿使用。对某些观众来说可能过于激烈。内部无用户可维修部件。如有变更,恕不另行通知。打破封印即表示接受协议。包含大量非烟草成分。使用本产品可能会导致牙齿暂时变色。不对因任何缺陷、错误或未能履行而导致的直接、间接、偶然或后果性损害负责。不要在客厅里尝试这个;这是经过训练的专业人士。在此签名而不承认有罪。外出就餐。作者不对由此造成的任何精神困扰负责。请在成人监督下使用。不对印刷错误负责。请勿将此梯子的底部放在冰冻的粪便上。本产品中提到的一些商标仅用于识别目的。镜子中的物体可能比它们看起来更近。这些陈述未经美国食品药品监督管理局评估。本产品不用于诊断、治疗、治愈或预防任何疾病。未经授权不得用作生命支持设备或系统的关键部件。万一发生紧急情况,参与者可能要对代表他们或因其行为而产生的任何救援或疏散费用负责。在某些州,上述某些限制可能不适用于您。本通知取代所有先前的通知,除非另有说明。
参考文献
CodeProject 上的相关参考资料
- Uwe Keim 出色的 正则表达式简介,这足以证明您需要 Perl!
- Yap Chun Wei 的 CPerlString - 一个利用 Perl 字符串函数的类,该文章侧重于访问 Perl 的内置函数而不是执行 Perl 脚本(也许我应该将其与
CPerlWrap
合并?)。 - PixiGreg 的 PXPerl 命名空间,本文基于
CPerlWrap
,包含许多改进,其中一些已被我拉回CPerlWrap
。
发布历史
- 2002 年 10 月 14 日
- 首次发布给毫无准备的公众。
- 2002 年 10 月 18 日
- 根据读者反馈进行的微小更改。
- 2003 年 7 月 30 日
- 更改了可下载的类和演示文件,使其使用 Perl58.dll 而不是 Perl56.dll。
- 将 Perl CORE 头文件包含移至 PerlWrap.cpp,以减少命名空间污染(请参阅上面 PixiGreg 的文章)。
- 2012 年 2 月 22 日
- 更新至 Perl 5.14,包括最新的接口机制。
- 使用 PC-Lint 清理了代码。
- 添加了 VS2005 项目(您可以使用 VS2008 或 VS2010 打开它进行自动转换)。
- 添加了 STL 版本(PerlWrapSTL.h 和 PerlWrapSTL.cpp),由 CodeProject 会员 SLJW(又名 jwilde)提供,他的个人资料几乎是空的。谢谢,SLJW!
- 对 VS2005 使用的“安全”CRT 函数进行了一些微调。有关更多详细信息,请参见 StdAfx.h。