
Table of Contents
I often find myself having to decide between making a project in VC++ or Perl
and having to make it one or the other, not both. Perl is wonderful for string
manipulation, hashes and arrays of arbitrary objects, and DWIM (Do What I Mean)
behavior. VC++ is fast, has excellent type checking and debugging, and the
resulting program can be easily packaged up for other machines. Perl requires
that the target machine has Perl already installed. Some operations are one or
two lines in Perl and 100 or 200 lines in VC++ (and vice versa). Perl is very
fast for prototyping, etc. ad nauseum.
I have seen the manual pages for Perl (perlguts, perlembed, perlapi, ...)
showing how easy (ha!) it is to embed Perl into C/C++, but they are almost
incomprehensible to somebody who doesn't get into the guts of Perl. Almost as
bad as OLE! :-)
Further, even with the code to have an embedded Perl, there is still the
issue of getting C++ variables into and out of that instance of Perl. Even more
arcane magic is required. This led me to spend some time reading and testing the
Perl embedding capabilities. Virtually everything I have here comes from the
Perl manual pages, particularly perlguts, perlembed, and perlapi. These are not
for the faint of heart. They certainly aren't for casual use.
This effort, plus a little experience in real-world applications using
embedded Perl yields the following:
This class allows you to create an instance of Perl, pass variables into and
out of that instance, and run arbitrary scripts. The instance "stays alive"
until explicitly destroyed, so you can run many different scripts without
re-instantiating.
The three major variable types in Perl are the scalar ($abc),
the list (@def), and the hash (%ghi) which correspond
to MFC types of CString/int/double (for scalars),
CStringArray (for lists), and CMapStringToString (for
hashes). For each of these there is a get and a set function:
BOOL setIntVal(CString varName, int value);
BOOL setFloatVal(CString varName, double value);
BOOL setStringVal(CString varName, CString value);
BOOL setArrayVal(CString varName, CStringArray &value);
BOOL setHashVal(CString varName, CMapStringToString &value);
BOOL getIntVal(CString varName, int &val);
BOOL getFloatVal(CString varName, double &val);
BOOL getStringVal(CString varName, CString &val);
BOOL getArrayVal(CString varName, CStringArray &values);
BOOL getHashVal(CString varName, CMapStringToString &value);
So if I have a CString that I want to do something Perlish on,
for instance extracting all the words into an array of words, here is my VC++
code:
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);
(Yes, this could be done in C++, but it's an easy example!)
Or perhaps I want to capitalize each word in that string, using the following
VC++ code:
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);
The results:
This Is A Verylong Set Of Words That Would Be A Pain To Deal With In C++
Or how about getting the first non-trivial-sized plural word and some
context:
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);
Which results in match containing:
lead context = 'of' match = 'words' trail context = 'that'
Ah! I have your attention now! Good.
The scripts needn't be one liners.
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);
As it happens, this particular script doesn't really need the embedded
new-line \n, but if you want the errors message to point to
something other than line 1, you'll add new-lines.
Error messages? Well, startling as it may seem, sometimes there are errors in
the Perl script that you run. It never happens to me (#include
<NoseGettingLonger>) of course, but I've included some support for
it. Here is an example showing an error and getting access to the problem report
from Perl:
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");
}
Which yields:
Scalar found where operator expected at (eval 18)
line 2, near "'this is a verylong set of words'
$d"
(Missing operator before
$d?)
By default, warnings are not considered errors and all warnings are cleared
before a script is executed. But if you want to easily detect warnings and
errors, you can use these two functions to tune CPerlWrap's
behavior:
BOOL SetfailOnWarning(BOOL);
BOOL SetclearWarningsOnScript(BOOL);
Putting CPerlWrap into your project
First and foremost, to build a project with CPerlWrap, you need
to have Perl 5.6 (or later) installed on your build machine. It is not
necessary for Perl to be installed on the target machine, but it must be on your
build machine. Your target machine must have the Perl56.dll file (or
Perl58.dll or whatever you built against), so don't forget to package
that up with your executable!
Go to http://www.activestate.com/ and download the
free Windows Perl. The price is right. Then install it. I'll wait
here until that is done.
Finished? Good. Took you long enough!
Next, copy PerlWrap.h and PerlWrap.cpp into your project's
directory. Use Project->Add to Project->Files... to add them
to your project. Don't build quite yet; there is something else that needs
doing.
You need to add the Include directory for the Perl CORE files:
- In Project->Settings...
- Settings for: All Configurations (don't just leave it on Win32
Debug!)
- C/C++ tab
- Category: Preprocessor
- Additional include directories:
- C:\Perl\lib\CORE (yes, it says 'lib' even though this is for file
includes!)
This assumes you have installed Perl into C:\Perl. If you have
installed elsewhere, make the appropriate adjustment AND make a similar
adjustment to the top of PerlWrap.h.
#if !defined(NOIMPLINK) && (defined(_MSC_VER) || defined(__BORLANDC__))
# pragma comment(lib,"C:/Perl/lib/CORE/Perl58.lib")
#endif
Now rebuild. Check that you can browse the CPerlWrap class. If
so, then it is time to do something with it!
Add a member variable to the class where you are doing your work. For me,
this tends to be something like CMyProjectView and I add:
public:
CPerlWrap perlInstance;
virtual ~CCPerlWrapperView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
If you like (recommended), you can tune Perl's behavior:
void CMyProjectView::OnInitialUpdate()
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
perlInstance.SetclearWarningsOnScript(TRUE);
perlInstance.SetfailOnWarning(TRUE);
The hardest part about using CPerlWrap is the backslashes
(\). If you have string that you want evaluated (interpolated) in
Perl, such as "$var1 is xyz to $var2", then that string must be
surrounded by " characters and you must escape those quotes in your
VC++ code:
CString script("$string = \"$var1 is xyz to $var2\";");
perlInst.doScript(script);
On the other hand, if you just want to have a string that is not
interpolated, then use single-quotes:
CString script("$string = 'this is an uninterpolated string';");
perlInst.doScript(script);
If you need to have a backslash in the script, you need to double it up so
that VC++ doesn't get it. Note the \\d is to get a \d
(the match-a-digit pattern) into the script:
CString script(
"$string =~ m/(\\d+)/;\n"
"$firstNumber = $1;"
);
perlInst.doScript(script);
CString firstNumber;
perlInst.getIntVal("firstNumber",firstNumber);
It gets really ugly if you need to insert a backslash:
perlInst.doScript("$StartDir =~ s%/%\\\\%g; "
"# change '/' to Windows-style '\'")
For reasons that I have not been able to discover, this embedded Perl doesn't
allow for sub-processes. So favorites like:
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);
just don't work! Same thing with using the backtick ` or
the system() function. Just don't work. If anybody has a fix for
this, please let me know, as it has been a source of frustration for me.
In Perl, the my operator is used to declare a variable in the
current scope. Scope is determined, much like in VC++, by surrounding
{} pairs. The doScript() function performs a Perl
eval {script} (note the {} pair) and so any variable
declared with my will not be available with the get* and
put* functions; they are local to that instance of the eval. If you
like to have use strict; in your code, then you will have to define
all your "global" variables using the put* functions (which puts them into the
main:: module).
One of the great advantages of Perl is the long list of available modules.
These are the Perl equivalent of C/C++ libraries. Modules are included using the
syntax:
use CGI;
use Win32;
where CGI and Win32 are two such modules. These
modules are usually included in the directory tree where Perl is installed.
Which means that using a Perl module in CPerlWrap requires that
tree be around on the target machine.
If the module in question is pure Perl (no embedded C functions), then you
can copy the module (CGI.pm, Win32.pm, or whatever) to the target
machine and tell Perl where to find it with the use lib('some new
directory'); pragma.
But (there is always a But), if you want a module that has embedded C
functions (such as, sadly, Win32) then you will have to diddle the
xs_init() function (found in PerlWrap.cpp) and that is 'way
beyond what I know about. I have put some comments (gleaned from the manual
pages) to get you started, but I really know nothing about it. If you need such
a module, start with perlguts, perlapi, and perlembed.
CPerlWrap will probably always be a work in progress, so I will
try and update this article when I make significant changes. I suspect that the
greatest source of changes will be fixes to bugs all of you have pointed
out!
I don't pretend to be a perlguts expert -- everything is in the Perl manual
pages and all I've done is to try and wrap it up so that it is easy to use. See
the disclaimers below.
Your Mileage May Vary. Void where prohibited. Do not take internally. Not
intended for opthamalic use. Not intended for children under the age of 65. Do
not use while sleeping. Warning: May cause drowsiness. For indoor or outdoor use
only. For off-road use only. For office use only. Do not attempt to stop chain
with your hands or genitals. Remember, objects in the mirror are actually behind
you. This product not tested on animals. No humans were harmed or even used in
the creation of this page. Not to be taken internally, literally or
seriously.
Some assimilation required. Resistance is futile.
This product is meant for educational purposes only. The manufacturer will
not be responsible for any damages or inconvenience that may result and no claim
to the contrary may legitimately be expressed or implied. Some assembly
required. Use only as directed. No other warranty expressed or implied. Do not
use while operating a motor vehicle or heavy equipment. May be too intense for
some viewers. No user-serviceable parts inside. Subject to change without
notice. Breaking seal constitutes acceptance of agreement. Contains a
substantial amount of non-tobacco ingredients. Use of this product may cause a
temporary discoloring of your teeth. Not responsible for direct, indirect,
incidental or consequential damages resulting from any defect, error or failure
to perform. Don't try this in your living room; these are trained professionals.
Sign here without admitting guilt. Out to lunch. The Author is not responsible
for any mental distress caused. Use under adult supervision. Not responsible for
typographical errors. Do not put the base of this ladder on frozen manure. Some
of the trademarks mentioned in this product appear for identification purposes
only. Objects in mirror may be closer than they appear. These statements have
not been evaluated by the Food and Drug Administration. This product is not
intended to diagnose, treat, cure, or prevent any disease. Not authorized for
use as critical components in life support devices or systems. In the unlikely
event of an emergency, participants may be liable for any rescue or evacuation
costs incurred either on their behalf or as a result of their actions. In
certain states some of the above limitations may not apply to you. This
supersedes all previous notices unless indicated otherwise.
References
References on CodeProject that
relate:
Release history
- 14-Oct-2002
- Initial release to an unsuspecting public.
- 18-Oct-2002
- Minor changes prompted by reader's comments.
- 30-July-2003
- Changed downloadable class and demo files to use Perl58.dll instead
of Perl56.dll.
- Moved Perl CORE header includes down to PerlWrap.cpp to reduce
namespace pollution (see PixiGreg's article above).