"You can sometimes write faster code in C, but you can always write code faster in Perl. Because you can use each from the other, combine them as you wish."
- perlembed by Jon Orwant and Doug MacEachern.
Table of Contents
Synopsis
#include "PXPerlWrap/PXPerlWrap.h"
PXInitializeRedirect(GetSafeHwnd());
PXSetUTF8(UTF8_auto);
CPerlInterpreter interp1, interp2 ... ;
CScript script1, script2 ...;
interp1.Load(true);
interp2.Load(false);
script1.Load(_T("http://pixigreg.com/hello2.txt"),
URL, Plain);
interp1.Parse(script1);
interp2.Parse(script1);
interp1.Run(script1);
interp2.Run(script1);
interp1.Run(script1);
interp2.Run(script1);
interp1.Clean(script1);
script2.Load(_T("use Win32; Win32::MsgBox('Hello from Perl!');"
" our $test_scalar = 'abc';"),
Inline, Plain);
if (!script2.Test())
{
}
script.Reformat();
interp1.Parse(script2);
CPerlScalar s = interp1.GetScalar(script2, _T("test_scalar"));
s += _T("def");
CPerlArray a = s.split(_T("m!!"));
CPerlScalar s2 = a[1];
AfxMessageBox(LPCTSTR(s2));
s2 = 123;
AfxMessageBox(LPCTSTR(s2));
AfxMessageBox(LPCTSTR(a[1]));
a.Add(_T("d"));
a.Add(_T("e"));
a.Add(_T("f"));
a.unshift(a);
...
s = interp.Eval(script2, _T("print qq(%s ), %d; %.03f"),
_T("it's pracical to use, since it's vararg"), 123, 3.14159f);
script2.ChangeType(Bytecode);
script2.SaveToFile(_T("TestScript.plc");
interp1.Unload();
interp2.Unload();
PXUninitializeRedirect();
The Story (Reloaded)
I won't tell you the language "threesome" story again. (if you don't know it, a bit of imagination might do the trick :)) (No? OK... clue: me, Perl and C++) (You find it bad? That's alright, go on reading then :P.)
I released PXPerl more than a year ago, and it met a good success. Why? Because embedding Perl is very practical. Quickly developing applications is made very easy when Perl is available. Perl can achieve easily many things you would take a lot of time to program and debug in C++, and without guarantee of a significant speed gain.
I therefore decided to write a new, more powerful and comprehensive version. Also, more object-oriented, and thread-safe. For example, I wanted to be able to modify a scalar within a thread:
- without crash (...)
- that modification affects the interpreter for all threads.
Also, I wanted to access my C++ functions from within my Perl script. Hey, I wanted a real standard streams redirection too. And the wish-list doesn't stop here... I was full of new ideas :P
That revealed to be quite ambitious (I'm not a computer science student and I'm supposed to have not that time to spend on this...), but I did it. I learned much on Perl and Perl embedding while developing it.
While developing it, I decided to create my own Perl distribution. First, for a practical reason: not to bother users with "install SWIG in the following directory. Open XXX with Notepad. blah blah blah". This Perl distribution is loaded with a bunch of modules, and among them is GraphicsMagick, a powerful image manipulation library. It was compiled for maximum performance, with an average speed gain of 16% over ActivePerl. It was designed to ease the developer task: with syntax highlighted HTML documentation, Explorer integration, and SciTE bundled for visual Perl scripts editing.
So eventually, I decided to change the wrapper's name, to give name PXPerl to the Perl distribution, and PXPerlWrap to the wrapper/embedding solution.
PXPerlWrap comes packaged within PXPerl, and the all-in-one package is available on my site.
In a nutshell
Embed a world widely used scripting language without efforts: enjoy the power of Perl in minutes. Write high quality applications in seconds. Maintain them as easily. Enrich your applications of a scripting ability, extending to the infinity the possibilities for your end-users.
Features:
PXPerlWrap
is a bi-directional wrapper. That is:
- you can execute Perl code from your MFC application, and manipulate variables etc.
- NEW! you can call your application, C/++ functions and classes from within your Perl scripts.
The first direction is achieved by PXPerlWrap. The second direction is achieved by SWIG, a free wrapper to several languages, on which you can find information here.
PXPerWrap
is a namespace containing an intuitive set of classes, multithread-safe:
CPerlInterpreter
: represents a Perl interpreter, persistent or not. Several different interpreters can be loaded.
- parse scripts;
- run scripts, several times;
- get a variable object to manipulate it or simply retrieve its value(s);
eval
a piece of Perl quickly.
CScript
: a script object. Each script keeps record of its own properties towards each interpreter.
- load a plain text script or Perl bytecode;
- load it from various sources: inline, file, URL, resource;
- save it to file;
- compile a plain script into byte code;
- test a script;
- reformat a script;
- set its
ARGV
.
CPerlScalar
: interfaces a Perl scalar variable.
- string, integer, float assignment and arithmetic operations, as simple as
s = "hello"
, s /= 1
and s += 1.0f
, the way Perl supports it.
split
it into a CPerlArray
;
- deal with UTF8 manually.
CPerlArray
: interfaces a Perl array variable.
- common
CStringArray
operations;
push
, unshift
, unshift
in reverse order, push
in reverse order, either a CPerlArray
object, a CStringArray
, or several elements from vararg
;
join
it into a CPerlScalar
.
CPerlHash
: interfaces a Perl hash variable.
- common
CMapStringToString
operations;
- Perl's
each
, keys
, and values
.
- The
stdout
and stderr
standard streams can be redirected and sent to a window.
PXPerlWrap
supports UTF8 Perl encoded strings and offers various strategies in Unicode builds towards automatic strings conversion.
- Easy installation within your existing project through the setup script.
You said persistent interpreter?
The idea of the persistent interpreter was found in perlembed. It consists of parsing once a script (see Behind the Persistent interpreter for this script) which will take care of parsing and running itself other scripts. Each scripts are assigned a different package name. This way, cleaning the package associated with a script will clean the variables used by the script, and hopefully free the memory associated with them.
The major benefit of the persistent interpreter is the ability to clean a script namespace, hence freeing memory for other scripts. I didn't perform any benchmark on it yet, but I think there is no real speed gain compared to a non-persistent interpreter: loading the interpreter is a bit longer (in fact, parsing the persistent script), but parsing a script is likely to be a little shorter. Running a script should take the same.
Therefore, prefer a persistent interpreter when you have a long running application, i.e., a long-running interpreter, so you can progressively clean scripts you don't need anymore.
Public methods
I used the excellent Doxygen to generate the HTML formatted PXPerlWrap documentation. Here is the relevant header file parts:
Globals
typedef enum
{
UTF8_off = 0,
UTF8_on,
UTF8_auto
} UTF8Mode;
PXPERL_API UTF8Mode PXSetUTF8(UTF8Mode mode=UTF8_on);
#define WM_PXPERL_OUTPUT (WM_USER+0x85)
#define PXPERL_STDOUT 1
#define PXPERL_STDERR 2
PXPERL_API bool PXInitializeRedirect(HWND hWnd,
UINT nRedirectMessage=WM_PXPERL_OUTPUT);
PXPERL_API void PXUninitializeRedirect(void);
CPerlInterpreter
typedef void (*XS_INIT_PROC)(LPVOID);
typedef void (*PXPERL_ERROR_PROC)(LPCTSTR);
class PXPERL_API CPerlInterpreter
{
friend class CPerlVariable;
public:
CPerlInterpreter();
~CPerlInterpreter();
bool Parse(CScript& script);
bool Run(CScript& script);
bool Clean(CScript& script);
CPerlScalar Eval(CScript& script, LPCTSTR szEval, ...);
CPerlScalar GetScalar(CScript& script, LPCTSTR szVariable);
CPerlArray GetArray(CScript& script, LPCTSTR szVariable);
CPerlHash GetHash(CScript& script, LPCTSTR szVariable);
bool Load(bool bPersistent=true, XS_INIT_PROC xs_init_addr=NULL,
PXPERL_ERROR_PROC pxperl_critical_error_addr=NULL);
bool IsLoaded(void) const;
void Unload(void);
void* GetMyPerl(void);
bool IsPersistent(void) const;
};
CScript
typedef enum
{
NoType = 0,
Plain,
Bytecode
} ScriptType;
typedef enum
{
NoSource = 0,
Inline,
File,
Resource,
URL
} SourceType;
class PXPERL_API CScript
{
friend class CPerlInterpreter;
public:
CScript();
~CScript();
bool Load(LPCTSTR szSource, SourceType source=Inline,
ScriptType type=Plain);
bool IsLoaded(void) const;
bool SaveToFile(LPCTSTR szFile);
bool Test(void);
bool Reformat(void);
CStringArray& GetARGV(void);
CStringArray& GetCustomOpts(void);
CString& GetScript(CString& strRet);
const CString& GetScript(void);
LPVOID GetScript(DWORD &dwSize);
ScriptType GetType(void) const;
bool ChangeType(ScriptType newType);
bool IsParsed(CPerlInterpreter *pInterp) const;
bool IsRun(CPerlInterpreter *pInterp) const;
const CString& GetPersistentPackage(CPerlInterpreter *pInterp) const;
};
CPerlScalar
class PXPERL_API CPerlScalar : public CPerlVariable
{
friend class CPerlArray;
friend class CPerlHash;
friend class CPerlInterpreter;
public:
CPerlScalar();
CPerlScalar(const CPerlScalar &scalar);
~CPerlScalar();
const CPerlScalar& operator= (const CPerlScalar &scalar);
int length(void);
void undef(void);
operator int() const;
int Int() const;
int operator*= (int value);
int operator/= (int value);
int operator+= (int value);
int operator-= (int value);
int operator= (int value);
operator double() const;
double Double() const;
double operator*= (double value);
double operator/= (double value);
double operator+= (double value);
double operator-= (double value);
double operator= (double value);
operator LPCTSTR() const;
CString& String(CString& strRet) const;
const CString& operator= (const CString& value);
LPCTSTR operator= (LPCTSTR value);
LPCTSTR operator+= (LPCTSTR value);
bool IsTrue() const;
bool IsInt() const;
bool IsDouble() const;
bool IsString() const;
bool IsUTF8() const;
CPerlArray split(LPCTSTR szPattern);
void UTF8CheckSetFlag();
void UTF8SetForceFlag(bool bIsUTF8=true);
void UTF8Upgrade();
void UTF8Downgrade();
char* GetPV();
};
CPerlArray
class PXPERL_API CPerlArray : public CPerlVariable
{
public:
CPerlArray();
CPerlArray(const CPerlArray &array);
~CPerlArray();
const CPerlArray& operator= (const CPerlArray& array);
CStringArray& StringArray(CStringArray &strARet,
bool bAppend=false) const;
const CStringArray& operator= (const CStringArray& array);
int operator+= (const CPerlArray& array);
int operator+= (const CStringArray& array);
int operator+= (LPCTSTR element);
CPerlScalar operator[](int nIndex);
CPerlScalar GetAt(int nIndex);
int GetSize() const;
int GetCount() const;
bool IsEmpty() const;
int GetUpperBound() const;
void SetSize(int nNewSize);
void RemoveAll();
void SetAt(int nIndex, LPCTSTR newElement);
const CString& ElementAt(int nIndex) const;
void SetAtGrow(int nIndex, LPCTSTR newElement);
int Add(LPCTSTR newElement);
int Add(const CString& newElement);
int Append(const CStringArray& newArray);
int Append(const CPerlArray& newArray);
void Copy(const CStringArray& newArray);
CPerlScalar pop(int nCount=1);
int push(LPCTSTR szFirst, int nCount=0, ...);
CPerlScalar shift(int nCount=1);
int unshift(LPCTSTR szFirst, int nCount=0, ...);
int unshift(const CStringArray& array);
int unshift(const CPerlArray& array);
int reverse_push(const CStringArray& array);
int reverse_push(const CPerlArray& array);
int reverse_unshift(const CStringArray& array);
int reverse_unshift(const CPerlArray& array);
void undef(void);
void clear(void);
CPerlScalar join(LPCTSTR szGlue);
};
CPerlHash
class PXPERL_API CPerlHash : public CPerlVariable
{
public:
CPerlHash();
CMapStringToString& MapStringToString(CMapStringToString
&mapRet, bool bAppend=false);
const CMapStringToString& operator=
(const CMapStringToString& map);
const CPerlHash& operator= (const CPerlHash& hash);
int GetCount() const;
int GetSize() const;
bool IsEmpty() const;
bool Lookup(LPCTSTR key, CString& rValue) const;
CPerlScalar Lookup(LPCTSTR key) const;
CPerlScalar operator[](LPCTSTR key) const;
void SetAt(LPCTSTR key, LPCTSTR newValue);
bool RemoveKey(LPCTSTR key);
void RemoveAll();
bool each(CString &strKey, CString &strValue);
CStringArray& keys(CStringArray &strARet);
CStringArray& values(CStringArray &strARet);
bool exists(LPCTSTR key);
void undef(void);
void clear(void);
};
Snippets
Here are pieces of code used in PXPerlWrap
, which are interesting and may be useful to you.
How to redirect standard streams without process spawning
Developing this part of PXPerlWrap
took me a lot of time. Why? Because I was looking for a bug in the wrong place. I got stuck for hours without figuring out why the standard streams got, either not at all, or partially redirected, in debug mode. In fact, and this may seem surely obvious to some people here, but not to me at this time, the problem was coming from the Perl DLL which was linked against a different CRT DLL (MSVCR71.DLL) than PXPerlWrap
debug DLL (MSVCR71D.DLL). The output and error file descriptors are therefore not the same. And wanting to redirect them is vain. That's why I had to provide a Perl debug DLL.
Credits for most of the code presented below goes directly to Vladimir Schneider. He also provided me a great help to get this redirection working. Thank you!
bool PXInitializeRedirect(HWND hWnd ,
UINT nRedirectMessage);
void PXUninitializeRedirect(void);
static HANDLE s_hThreadStdOut=NULL,
s_hThreadStdErr=NULL,
s_hThreadDispatch=NULL,
s_hEvent=NULL;
static int s_handlesStdOut[2] = { -1, -1};
static int s_handlesStdErr[2] = { -1, -1};
static bool s_bRedirecting = false;
#define REDIR_BUFSIZE 4096
typedef struct sThreadData
{
HWND hWnd;
int hReadPipe;
UINT nMsg;
WPARAM nMsgWParam;
} ThreadData;
static UINT __stdcall RedirectProc(void *pData)
{
ThreadData td;
int nBytesRead;
char *buffer;
memcpy(&td, pData, sizeof(ThreadData));
SetEvent(s_hEvent);
buffer = new char[REDIR_BUFSIZE];
while (s_bRedirecting)
{
if ((nBytesRead = _read(td.hReadPipe, buffer, REDIR_BUFSIZE)) < 1)
{
if (!s_bRedirecting || errno)
break;
Sleep(20);
continue;
}
buffer[nBytesRead] = 0;
if (!s_bRedirecting || !::IsWindow(td.hWnd))
break;
::SendMessage(td.hWnd, td.nMsg, td.nMsgWParam, (LPARAM)buffer);
}
delete [] buffer;
TRACE("[PXPW] RedirectThread(%d): deleted buffer\n", td.nMsgWParam);
_endthreadex(0);
return 0;
}
bool PXInitializeRedirect(HWND hWnd, UINT nRedirectMessage)
{
PXUninitializeRedirect();
UINT thread_id;
ThreadData td;
FILE *fp = NULL;
STARTUPINFO si;
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
s_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (_pipe(s_handlesStdOut, REDIR_BUFSIZE,
_O_TEXT | _O_NOINHERIT) == -1)
goto _redirect_failure;
if (_pipe(s_handlesStdErr, REDIR_BUFSIZE,
_O_TEXT | _O_NOINHERIT) == -1)
goto _redirect_failure;
_close(1);
_close(2);
if (-1 == _dup2(s_handlesStdOut[1], 1))
goto _redirect_failure;
if (-1 == _dup2(s_handlesStdErr[1], 2))
goto _redirect_failure;
_close(s_handlesStdOut[1]);
_close(s_handlesStdErr[1]);
s_handlesStdOut[1] = -1;
s_handlesStdErr[1] = -1;
if (si.hStdOutput != (HANDLE)-1)
SetStdHandle(STD_OUTPUT_HANDLE, si.hStdOutput);
if (si.hStdError != (HANDLE)-1)
SetStdHandle(STD_ERROR_HANDLE, si.hStdError);
s_bRedirecting = true;
td.hWnd = hWnd;
td.nMsg = nRedirectMessage;
td.nMsgWParam = PXPERL_STDOUT;
td.hReadPipe = s_handlesStdOut[0];
s_hThreadStdOut = (HANDLE)_beginthreadex(NULL, 0,
RedirectProc, (void*)&td, 1, &thread_id);
if (!s_hThreadStdOut)
goto _redirect_failure;
WaitForSingleObject(s_hEvent, INFINITE);
td.nMsgWParam = PXPERL_STDERR;
td.hReadPipe = s_handlesStdErr[0];
s_hThreadStdErr = (HANDLE)_beginthreadex(NULL, 0,
RedirectProc, (void*)&td, 1, &thread_id);
if (!s_hThreadStdErr)
goto _redirect_failure;
WaitForSingleObject(s_hEvent, INFINITE);
fp = _fdopen(1, "w");
setvbuf(fp, NULL, _IONBF, 0);
memcpy(stdout, fp, sizeof(FILE));
fp = _fdopen(2, "w");
setvbuf(fp, NULL, _IONBF, 0);
memcpy(stderr, fp, sizeof(FILE));
return true;
_redirect_failure:
PXUninitializeRedirect();
return false;
}
void PXUninitializeRedirect(void)
{
if (s_bRedirecting)
{
s_bRedirecting = false;
char eos[] = "\n";
_write(1, eos, sizeof(eos));
_write(2, eos, sizeof(eos));
Sleep(20);
}
if (s_hThreadStdOut)
{
WaitForSingleObject(s_hThreadStdOut, 1000);
CloseHandle(s_hThreadStdOut);
s_hThreadStdOut = NULL;
}
if (s_hThreadStdErr)
{
WaitForSingleObject(s_hThreadStdErr, 1000);
CloseHandle(s_hThreadStdErr);
s_hThreadStdErr = NULL;
}
if (s_hEvent)
CloseHandle(s_hEvent);
s_hEvent = NULL;
if (s_handlesStdOut[0] != -1)
_close(s_handlesStdOut[0]);
if (s_handlesStdOut[1] != -1)
_close(s_handlesStdOut[1]);
if (s_handlesStdErr[0] != -1)
_close(s_handlesStdErr[0]);
if (s_handlesStdErr[1] != -1)
_close(s_handlesStdErr[1]);
s_handlesStdOut[0] = -1;
s_handlesStdOut[1] = -1;
s_handlesStdErr[0] = -1;
s_handlesStdErr[1] = -1;
}
Behind the Persistent interpreter
In order to maintain the persistent interpreter, we need a script (which I call perlsistent.pl) to parse and run "sub-scripts", and also clean them. Such a script is suggested in perlembed. I modified it a bit for PXPerlWrap
. Here it is:
# Perl Persistent Interpreter. Core Script File.
# From perlembed, modified by PixiGreg.
#################################################
my $verbose = 0; # this was for testing purposes
our %Cache; # the package names cache
BEGIN
{
# disable stdout/stderr buffering
$|++;
select STDERR;
$|++;
select STDOUT;
use strict;
use warnings 'all';
use Symbol qw(delete_package);
}
# routine used to parse a sub-script
sub parse
{
my($packagename, $script, $empty) = @_;
$verbose and print STDERR "[Perlsistent] compiling $packagename\n";
# clean if a script is already compiled with same name
defined $Cache{$packagename} and
delete_package($packagename), undef $Cache{$packagename};
# wrap the code into a subroutine inside our unique package
my $eval = qq{package $packagename; sub handler { $script; }};
{
# hide our variables within this block
my($packagename, $script);
eval $eval;
}
# tell this package contains a valid executable routine
$Cache{$packagename} = 1;
return 1;
}
# routine used to run a sub-script
sub run
{
my($packagename, $empty) = @_;
$verbose and print STDERR "[Perlsistent] running $packagename\n";
if ($Cache{$packagename} == 1)
{
$packagename->handler();
}
return 1;
}
# routine used to clean a sub-script
sub clean
{
my $packagename = shift;
$verbose and print STDERR "[Perlsistent] cleaning $packagename\n";
defined $Cache{$packagename} and
delete_package($packagename), undef $Cache{$packagename};
}
1;
Setting up PXPerlWrap in your project
Setting up PXPerlWrap
in your project is made quite simple by the supplied setup script. Here are the steps to set up PXPerlWrap
in your projects (also available in PXPerl documentation):
- Set your project binaries output directory to a single and unique one, for every configuration, if not already the case. Modify the output filenames as well.
Example:
Configuration |
Output filename |
Debug |
bin/MyApp-d.exe |
Release |
bin/MyApp.exe |
Debug Unicode |
bin/MyApp-ud.exe |
Release Unicode |
bin/MyApp-u.exe |
This way, you won't have to have several copies of PXPerlWrap.dll, Perl58.dll, and Perl default modules, i.e., one for each output directory.
- Go to PXPerl installation directory.
Launch "Step 1 - Edit PXPerWrapSetup config file.bat".
You have to modify a couple of variable so as to get PXPerlWrap
set up in your project; this is explained in the config file.
- Launch "Step 2 - Launch PXPerWrapSetup.bat".
Several files will be created for you by the setup script in your project directory (i.e.: pxpw_*.bat, PXPerlWrap directory, DLL files under output directory, and modules directory).
You shouldn't see any error during this process. Otherwise, you probably specified a wrong path or option. Go to step 2 and read carefully. If the trouble persists, report it to me.
- Add the files "PXPerlWrap.h" and "PXPerlWrap.cpp" to your project (located in the PXPerlWrap directory in your project directory); and only these files.
Correction: you may also add export.h, for access convenience, see step 8 for purpose of this file.
- In your project settings, for each configuration, add a Pre-Build Event and Post-Build Event:
Configuration |
Prebuild Event |
Postbuild Event |
Debug (Unicode or not) |
PXPW_prebuild.bat |
PXPW_postbuild_debug.bat |
Release (Unicode or not) |
PXPW_prebuild.bat |
PXPW_postbuild.bat |
- For each configuration, add the additional include directory "$(PXPERL)\lib\CORE".
- Now, wherever you want to use
PXPerlWrap
classes, just add this to your program header file: #include "PXPerlWrap/PXPerlWrap.h"
- The prototypes of the functions you want to be exported with SWIG (i.e. available to your scripts) will have to appear in "export.h".
That's all :)
FAQ
Q: Grrrr, source code is not available!
A: I must admit I thought twice before deciding not making available the whole PXPerlWrap
source code. And, well, although I encourage open source projects, it's a personal choice. Nevertheless, you can buy the whole source code of PXPerlWrap on my site.
However, I made some code pieces public because it may be useful to the community. See snippets.
Q: Is PXPerlWrap free or not?
A: Basically, yes. PXPerlWrap
is free for using in freewares etc. Any commercial usage requires a commercial license, see details on my site. The PXPerl Perl distribution itself, apart from PXPerlWrap
and other software bundled which come with their own, basically free, licenses, is available under the same license as Perl, that is the artistic license.
Q: Why is PXPerlWrap shipped with such a heavy package, PXPerl? Are the 16 MB necessary to PXPerlWrap, i.e., will I have to ship 16 MB in my application??
A: PXPerlWrap
requires PXPerlWrap
is shipped in PXPerl, the Perl distribution, for convenience. In my opinion, an all-in-one package is more practical. The other reason is that PXPerl and PXPerlWrap
and tightly linked, the latter requires the first, through the setup script.
And, no, you won't have a 16 Mb overhead when using PXPerlWrap
in your application. With a decent compression, and only standard modules embedded, you'll have roughly 2 MB.
Q: So, the end-user doesn't have to have PXPerl or any other Perl binary distribution installed on his machine?
A: That's it.
Supplying the PXPerlWrap*.dll (* = depends on the build you supply to end-user, either Unicode or MBCS), perl58.dll, PXPerl.opt, and the modules directory you specified during setup, is enough. Just keep this file tree and it will be OK.
Q: When I supply relative paths to PXPerlWrap, what happens?
A: PXPerlWrap
will make these paths absolute, but not referring to the active directory, but to the PXPerlWrap
DLL. I had to do that because, when debugging, Visual Studio sets an unobvious active directory which is the project directory. So be careful when loading script files.
Q: I built the Release [Unicode] version of my application, tested it, and it worked. I then compiled the Debug [Unicode] version, and the release executable was no longer working/frozen!
A: That's because your release executable require the release Perl DLL, while your debug version requires the Perl debug DLL. Both DLLs have the same name. Why? Because, this way, I don't have to supply all the modules compiled, especially for the debug DLL. And why is a Perl debug DLL needed? Because, otherwise your application debug executable and the Perl release DLL would be linked against a different CRT DLL, one debug, the other not, and redirection would not work.
Q: Wow, this DLL is too big!
A: Yeah, sometimes you have to choose between speed and size, I prefer speed. Use UPX packer to reduce significantly the DLL's size (particularly perl58.dll, 2.4 MB).
Q: I would like to have all these DLLs statically linked. Is it possible?
A: No. Perl can't be statically linked safely. No plan to make a static PXPerlWrap
.
History
- 01 Nov 2004: v2.02: Important changes and bug fixes.
- Documentation and new CodeProject article!
- Fixed scalar reference count warnings. Actually, I didn't manage scalar reference counts in previous release...
- Fixed dramatic 2s latency when calling
PXUninitializeRedirect()
.
- Fixed temporary files which were not deleted.
- Fixed the debug Perl DLL import library path which was wrong in PXPerlWrapSetup, and which was producing errors in debug builds linking phase.
- Made
CPerlVariable::Destroy()
public, so you can re-use variable objects.
- Fixed some bugs in
CPerlArray::push
/unshift
and CPerlHash::Lookup()
.
- 27 Oct 2004: v2.01: completely rewritten. Lots of new features. New name:
PXPerlWrap
, part of the PXPerl package.
- 14 July 2003: v1.2: support for hashes! Removed Unicode compilation support. Updated article.
- 13 July 2003: v1.1: now using
PerlIO::scalar
and no real-files to store STD* outputs. Faster.
- 12 July 2003: Initial release (v1.0). Not released to the public
To come
A more comprehensive description/tutorial of the PXPerlWrap
behavior towards UTF8 encoded strings in Unicode builds.