Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

PXPerlWrap (PXPerl, reloaded)

0.00/5 (No votes)
3 Nov 2004 3  
A comprehensive Perl embedding solution.

"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());
// redirect output and errors


PXSetUTF8(UTF8_auto);
// set behaviour towards Perl UTF8 encoded strings; see doc



CPerlInterpreter interp1, interp2 ... ;
CScript script1, script2 ...;

interp1.Load(true);     // load a persistent interpreter

interp2.Load(false);    // load a standard interpreter


script1.Load(_T("http://pixigreg.com/hello2.txt"), 
             URL, Plain); // load a script from the web


interp1.Parse(script1); // parse it with interp #1

interp2.Parse(script1); // parse it with interp #2


interp1.Run(script1); // run it

interp2.Run(script1); //

interp1.Run(script1); //

interp2.Run(script1); //


// persistent interpreter benefit: you can clean

// the script, freeing associated variables

interp1.Clean(script1);

script2.Load(_T("use Win32; Win32::MsgBox('Hello from Perl!');"
             " our $test_scalar = 'abc';"), 
             Inline, Plain);

if (!script2.Test()) // test syntax

{
    // scripts failed to parse

}

script.Reformat(); // script will be reformated to output


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)); // pops up "b"

s2 = 123;
AfxMessageBox(LPCTSTR(s2)); // pops up "123"

AfxMessageBox(LPCTSTR(a[1])); // pops up "123" now


a.Add(_T("d"));
a.Add(_T("e"));
a.Add(_T("f"));

a.unshift(a); // unshift self

...

// this will copy 3.141 onto s

s = interp.Eval(script2, _T("print qq(%s ), %d; %.03f"), 
    _T("it's pracical to use, since it's vararg"), 123, 3.14159f);

// compile script 2 to bytecode

script2.ChangeType(Bytecode);
// save it

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:

  1. without crash (...)
  2. 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

///////////////// UTF8


/** @enum UTF8Mode Modes for PXSetUTF8(). Applies only to Unicode builds. */
typedef enum
{
    UTF8_off = 0,
/**< "No" conversion is made (strings are converted
*     to MBCS then passed to Perl).
*/
    UTF8_on,
/**< All strings are converted to UTF8 in Perl.
 */
    UTF8_auto
/**< If overwriting the value of an existing Perl scalar,
     check if it is an UTF8 encoded one, and in this case convert
     the string to UTF8. Otherwise, same behaviour as UTF8_off.
 */
} UTF8Mode;

/**
 * Sets the PXPerlWrap behaviour regarding C++ strings
 * to Perl strings conversion.
 * This doesn't affect Perl to C++ conversion:
 * all UTF8 encoded Perl strings will
 * be converted to wide strings, i.e. Unicode ones;
 * in MBCS build, an additional
 * conversion is made to pass from Unicode to MBCS string (ATL CW2A macro).
 * @param mode UTF8 mode. In MBCS builds this cannot be changed from UTF8_off.
 * @return the actual changed mode; in MBCS builds, will be always UTF8_off.
 * @see UTF8Mode for an explanation on the different modes available.
 */
PXPERL_API UTF8Mode PXSetUTF8(UTF8Mode mode=UTF8_on);


///////////////// Redirection


/** @def WM_PXPERL_OUTPUT The default window message for redirection */
#define WM_PXPERL_OUTPUT (WM_USER+0x85)
/** @def PXPERL_STDOUT Indicates if a redirection
            message is standard output (wParam) */
#define PXPERL_STDOUT 1
/** @def PXPERL_STDERR Indicates if a redirection
           message is standard errors (wParam) */
#define PXPERL_STDERR 2

/**
 * Initializes standard streams redirection,
 * i.e. roughly open pipes, take the stdout and
 * stderr handles and do the necessary dup2 etc.
 * tricks to have streams redirected.
 * @param hWnd Destination window for messages
 * @param nRedirectMessage Message. wParam will
 * be either PXPERL_STDOUT or PXPERL_STDERR depending
 * on where the content comes from, lParam must be cast
 * to a LPCSTR (i.e. const char*) and is a stream
 * content chunk.
 * @warning Your message handler must be multithread-safe.
 * @return true on success, false otherwise.
 */
PXPERL_API bool PXInitializeRedirect(HWND hWnd, 
                UINT nRedirectMessage=WM_PXPERL_OUTPUT);

/**
 * Stops redirection, stopping pipe reading threads.
 */
PXPERL_API void  PXUninitializeRedirect(void);

CPerlInterpreter

/** Function typedef for the xs_init Perl proc so you can customize Perl boot 
    when loading an interpreter */
typedef void (*XS_INIT_PROC)(LPVOID);


/** Function typedef for the customizable error handling function */
typedef void (*PXPERL_ERROR_PROC)(LPCTSTR);


/** @class CPerlInterpreter
* Represents an interpreter. All interpreter are independent,
* and all Perl variable objects refer to a single interpreter.
* Besides, each script is parsed/run for a single interpreter.
*/

class PXPERL_API CPerlInterpreter
{
    friend class CPerlVariable;

public:
    /**
     * Constructs a Perl interpreter. Does not actually load it
     * (yeah that's not pure OO code :P).
     * I find this more practical, as you may want to reload an interpreter.
     * @see CPerlInterpreter::Load()
     */
    CPerlInterpreter();
    /**
     * Destruction. Nothing special done.
     */
    ~CPerlInterpreter();

    /**
     * Parses a script.
     * @param script A script object.
     * @return true if script is already parsed, or was parsed successfuly.
     * False if the script is not valid (not correctly loaded),
     * or if the parse failed.
     */
    bool Parse(CScript& script);

    /**
     * Runs a script.
     * @param script A script object.
     * @return true if script was run successfuly. False if script
     * is not parsed for the current interpreter, or if the run failed.
     */
    bool Run(CScript& script);

    /**
     * Cleans a script, freeing associated variables.
     * @warning Applies only to a persistent interpreter.
     * @param script A script object.
     * @return true if script was cleaned successfuly. False if script
     * is not parsed for the current interpreter, or if the clean failed.
     */
    bool Clean(CScript& script);

    /**
     * Evaluates a Perl snippet. The script must have been loaded,
     * and parsed only if the interpreter is persistent.
     * @param script A script object.
     * @param szEval The Perl code to be evaluated.
     * @param ... Variable arguments.
     * @return a scalar object, containing result of the Perl eval.
     * For example, if you do eval "$foo = 'bar'; $abc = join('', 'abc');",
     * the eval result is the value of $abc. If the evaluation
     * is unsuccessful, an invalid CPerlScalar is returned.
     * @see CPerlVariable::IsValid()
     */
    CPerlScalar Eval(CScript& script, LPCTSTR szEval, ...);

    /**
     * Get a scalar from a script. Scalar can exist
     * or not. If it doesn't, it will be created.
     * @param script A script object.
     * @param szVariable The variable name.
     * @return a scalar object. On failure,
     * an invalid CPerlScalar is returned.
     * @see CPerlVariable::IsValid()
     */
    CPerlScalar GetScalar(CScript& script, LPCTSTR szVariable);

    /**
     * Get an array from a script. Array can exist
     * or not. If it doesn't, it will be created.
     * @param script A script object.
     * @param szVariable The variable name.
     * @return a scalar object. On failure, an invalid CPerlArray is returned.
     * @see CPerlVariable::IsValid()
     */
    CPerlArray GetArray(CScript& script, LPCTSTR szVariable);

    /**
     * Get a hash from a script. Hash can exist or not.
     * If it doesn't, it will be created.
     * @param script A script object.
     * @param szVariable The variable name.
     * @return a scalar object. On failure, an invalid CPerlHash is returned.
     * @see CPerlVariable::IsValid()
     */
    CPerlHash GetHash(CScript& script, LPCTSTR szVariable);

    /**
     * Loads the interpreter.
     * @param bPersistent Specify wether you want a persistent
     * interpreter or not. For benefits and drawbacks
     * of a persistent interpreter, see the CodeProject article.
     * @param xs_init_addr Address to a custom xs_init procedure.
     * If none (NULL) specified, the default one is used (not recommended,
     * since PXPerlWrapSetup sets up a custom procedure allowing
     * using SWIG and exporting functions inside Perl).
     * @param pxperl_critical_error_addr Address to a custom (critical)
     * error handler. If none is specified,
     * a message box pops up on critical error.
     * @return true if successful, false otherwise.
     * @todo Improve error report (sort severity,
     * emit a warning/error each time a function
     * fail w/ extended info and so on).
     */
    bool Load(bool bPersistent=true, XS_INIT_PROC xs_init_addr=NULL, 
                 PXPERL_ERROR_PROC pxperl_critical_error_addr=NULL);

    /**
     * @return true if the interpreter is loaded, false otherwise.
     */
    bool IsLoaded(void) const;
    /**
     * Unloads the interpreter.
     * @return true if successful, false otherwise.
     */
    void Unload(void);

    /**
     * Returns a pointer to the interpreter. Cast to PerlInterpreter*.
     * For PXPerlWrap extension. Be careful using it.
     * @return a non-null pointer if interpreter is properly loaded.
     */
    void* GetMyPerl(void);

    /**
     * @return true if the interpreter is persistent, false otherwise.
     */
    bool IsPersistent(void) const;

};

CScript

/** Script types for CScript::Load() */
typedef enum
{
    NoType = 0, /**< Means type is not known, not actually used. */
    Plain, /**< Script is plain, good'old ASCII text. */
    Bytecode /**< Script is Perl bytecode. */
} ScriptType;

/** Source types for CScript::Load() */
typedef enum
{
    NoSource = 0, /**< Means there is no source, not actually used. */
    Inline, /**< The source is an inline, directly supplied, script. */
    File, /**< The source is a file path. */
    Resource, /**< The source is a RT_PERL resource.
                   Use MAKEINTRESOURCE(id) to get a LPCTSTR. */
    URL /**< The source is an URL. */
} SourceType;


/** @class CScript
* A script object. Holds the script content physically (that is,
* a string contains either the script file or script itself).
* Holds also information about whether the script is parsed
* and/or run for a particular interpreter.
*/
class PXPERL_API CScript
{
    friend class CPerlInterpreter;

public:
    /**
     * Constructs a script object.
     * @see CScript::Load() to load/reload a script.
     */
    CScript();

    /**
     * Destroys a script object.
     * Scripts cannot be unloaded, but can be reloaded.
     */
    ~CScript();

    /**
     * Loads a script, from various sources. Loading from an URL relies
     * onto API function URLDownloadToFile().
     * @param szSource The source, that is, either the script itself,
     * or a file containing the script, or an URL, or a resource
     * (use MAKEINTRESOURCE in this case).
     * @param source Specifies source type.
     * @param type Specifies script type.
     * @return true on success, false otherwise.
     */
    bool Load(LPCTSTR szSource, SourceType source=Inline, 
                                ScriptType type=Plain);

    /**
     * @return true if the script is loaded, false otherwise.
     */
    bool IsLoaded(void) const;

    /**
     * Saves the current script (either text or bytecode) to a file.
     * @param szFile File path.
     * @return true on success, false otherwise.
     */
    bool SaveToFile(LPCTSTR szFile);

    /**
     * Tests the current script.
     * @return true if script is parsed successfuly, false otherwise.
     */
    bool Test(void);

    /**
     * Reformats the current script to standard output.
     * @return true if script is parsed
     * and formatted successfuly, false otherwise.
     */
    bool Reformat(void);

    /**
     * @return the custom ARGV for the script. You can, for example,
     * do "script.GetARGV().Add("Arg1"); ..."
     */
    CStringArray& GetARGV(void);

    /**
     * @return the custom options for parsing.
     * Overrides the PXPerl.opt options if not empty.
     */
    CStringArray& GetCustomOpts(void);

    /**
     * Get the script as a string. If the script is bytecode,
     * "PLBCMSWin32-x86-multi-thread" is returned.
     * @param strRet A CString to receive the script.
     * @return the same string than passed as param.
     * Empty string is returned on failure.
     */
    CString& GetScript(CString& strRet);
    
    /**
     * Get the script as a string. If the script is bytecode,
     * "PLBCMSWin32-x86-multi-thread" is returned.
     * @return a read-only CString object with script content.
     * Empty string is returned on failure.
     */
    const CString& GetScript(void);
    
    /**
     * Get the script as a memory pointer.
     * Useful to access a bytecode script content.
     * @return a pointer to the script, allocated on the heap,
     * if successful, NULL otherwise.
     * Free the pointer using delete [] pointer.
     */
    LPVOID GetScript(DWORD &dwSize);

    /**
     * @return script type.
     */
    ScriptType GetType(void) const;

    /**
     * Change type. Only Plain -> Bytecode is supported for the moment.
     * Use it to compile script to bytecode.
     * @bug Stops redirection.
     * @todo Fix redirection problem.
     * Surely will have to modify O/B Perl modules.
     * @param newType The new type, only Bytecode supported.
     * @return true on success, false otherwise.
     */
    bool ChangeType(ScriptType newType);

    /**
     * @param pInterp Pointer to an interpreter.
     * @return true if the script has been parsed
     * successfuly by the specified interpreter.
     */
    bool IsParsed(CPerlInterpreter *pInterp) const;

    /**
     * @param pInterp Pointer to an interpreter.
     * @return true if the script has been run successfuly
     * (at least one time) by the specified interpreter.
     */
    bool IsRun(CPerlInterpreter *pInterp) const;

    /**
     * @param pInterp Pointer to an interpreter.
     * @return a read-only CString object containing the package
     * name created for this script by the specified interpreter.
     * String may be empty if interpreter is not persistent
     * or if script has not been parsed so far.
     */
    const CString& GetPersistentPackage(CPerlInterpreter *pInterp) const;

};

CPerlScalar

/** @class CPerlScalar
* Represents a Perl scalar.
*/
class PXPERL_API CPerlScalar : public CPerlVariable
{
    friend class CPerlArray;
    friend class CPerlHash;
    friend class CPerlInterpreter;

public:
    CPerlScalar(); /**< Constructs a CPerlScalar object, 
                        marked as invalid, since not associated 
                        with a real Perl scalar. */
    /**
     * Constructs a scalar, cloning the passed CPerlScalar object
     * if this one is invalid, or copying its value if valid.
     * @param scalar CPerlScalar object to be copied.
     * @see operator=()
     */
    CPerlScalar(const CPerlScalar &scalar);
    ~CPerlScalar(); /**< Destroys the object. Decrements reference count, 
                         freeing the scalar if possible. */
    
    /**
     * Clones the passed the passed CPerlScalar object if this
     * one is invalid, or copying its value if valid.
     * @param scalar CPerlScalar object to be cloned/copied.
     * @return the CPerlScalar object passed.
     */
    const CPerlScalar& operator= (const CPerlScalar &scalar);

    int length(void);
    /**< @return the length, in characters, or the string. */ 
    void undef(void); /**< Clears the Perl scalar value 
                           and frees the memory asscoiated with it. */ 
    //void clear(void); /**< Clears the Perl scalar value. */


    operator int() const;
    /**< @return the integer value of the Perl scalar. */

    int Int() const;
    /**< @return the integer value
         of the Perl scalar (explicit call). */

    int operator*= (int value);
    /**< Implements integer multiplication. 
         @return the result value. */

    int operator/= (int value);
    /**< Implements integer division. @return the result value. */

    int operator+= (int value);
    /**< Implements integer addition. @return the result value. */

    int operator-= (int value);
    /**< Implements integer substraction. 
         @return the result value. */

    int operator= (int value);
    /**< Implements integer assignment. @return the new value. */

    operator double() const;
    /**< @return the float value of the Perl scalar. */

    double Double() const;
    /**< @return the float value of the Perl scalar. */

    double operator*= (double value);
    /**< Implements float multiplication. 
         @return the result value. */

    double operator/= (double value);
    /**< Implements float division. @return the result value. */

    double operator+= (double value);
    /**< Implements float addition. @return the result value. */

    double operator-= (double value);
    /**< Implements float substraction. 
         @return the result value. */

    double operator= (double value);
    /**< Implements float assignment. @return the new value. */

    operator LPCTSTR() const;
    /**< @return the string value of the Perl scalar. */
    /**
     * @param strRet String to receive
     * the string value of the Perl scalar.
     * @return the passed string.
     */
    CString& String(CString& strRet) const;
    const CString& operator= (const CString& value);
    /**< Implements string assignment. @return the new value,
         a read-only CString object. */

    LPCTSTR operator= (LPCTSTR value);
    /**< Implements string assignment. @return the new value,
         a read-only string. */
    /** Implements string concatenation.
      * @return the result string, read-only.
      */
    LPCTSTR operator+= (LPCTSTR value);

    /**
     * @return true if the scalar is true
     * as Perl means it. False otherwise.
     */
    bool IsTrue() const;
    /**
     * @return true if the scalar native type for Perl is integer.
     */
    bool IsInt() const;
    /**
     * @return true if the scalar native type for Perl is float.
     */
    bool IsDouble() const;
    /**
     * @return true if the scalar native type for Perl is string.
     */
    bool IsString() const;
    /**
     * @return true if the scalar is UTF8 encoded.
     */
    bool IsUTF8() const;
    /**
     * The extra Perl-like function. However, there is no "sv_split"
     * function exported by Perl. Therefore, calling this function
     * is roughly the same as calling Eval("split...") in terms
     * of performance but is more convenient.
     * @param szPattern The split pattern (eg. "m!!" or "/[abc]{2}/i").
     * @return a valid CPerlArray object upon success.
     * @see CPerlArray::join()
     */
    CPerlArray split(LPCTSTR szPattern);
    

    void UTF8CheckSetFlag();
    /**< If UTF8 flag is not set, performs a check on bytes to 
         determine if the string is likely to be UTF8 encoded. 
         Set the UTF8 flag in this case. */ 
    void UTF8SetForceFlag(bool bIsUTF8=true);
    /**< Force the UTF8 flag to be set or not.
         @param bIsUTF8 true to set the flag, false otherwise. 
         @warning This function is intended for advanced users, 
         since it lets you deal with UTF8 manually. */ 
    void UTF8Upgrade();
    /**< Upgrades the Perl string to UTF8 encoding. 
         Calls the Perl sv_utf8_upgrade() function. 
         @warning This function is intended for advanced users, 
         since it lets you deal with UTF8 manually. */ 
    void UTF8Downgrade();
    /**<  Downgrade the Perl string from UTF8 encoding. 
          Calls the Perl sv_utf8_downgrade() function. 
          @warning This function is intended for advanced users, 
          since it lets you deal with UTF8 manually. */  
    char* GetPV();
    /**< @return the string value of the Perl scalar, 
         as SvPV() Perl function returns. No conversion is made.
         @warning This function is intended for advanced users. 
         Be careful of what you do with the returned pointer; 
         a misuse will result in a crash. */ 
};

CPerlArray

/** @class CPerlArray
* Represents a Perl array.
* @warning Although methods are optimized, array operations
* can be lengthy. Therefore, even if class usage is rather simple,
* be careful not making redundant code.
*/
class PXPERL_API CPerlArray : public CPerlVariable
{
public:
    CPerlArray(); /**< Constructs a CPerlArray object, 
                       marked as invalid, since not associated 
                       with a real Perl array. */
    /**
     * Constructs a CPerlArray object, cloning the passed CPerlArray 
     * object if this one is invalid, or copying its values if valid.
     * @param array CPerlArray object to be copied.
     * @see operator=()
     */
    CPerlArray(const CPerlArray &array); 
    ~CPerlArray(); 
    /**< Destroys the object. 
         Nothing is done concerning the Perl array itself.  */

    /**
     * Clones the passed the passed CPerlArray object if this
     * one is invalid, or copies its value if valid.
     * @param array CPerlArray object to be cloned/copied.
     * @return the CPerlArray object passed.
     */
    const CPerlArray& operator= (const CPerlArray& array);

    /**
     * Populates a CStringArray with the Perl array values.
     * Values can be appended to the existing CStringArray passed.
     * @param strARet CStringArray to receive the values.
     * @param bAppend true to append values
     * to the passed array, false otherwise.
     * @return the CStringArray object passed.
     */
    CStringArray& StringArray(CStringArray &strARet, 
                              bool bAppend=false) const;

    /**
     * Copies passed CStringArray values onto Perl array,
     * overwriting any existing value,
     * and resizing the Perl array as necessary.
     * @param array CStringArray to be copied.
     * @return the CStringArray object passed.
     */
    const CStringArray& operator= (const CStringArray& array);

    /**
     * Appends the CPerlArray's associated array
     * values to the current Perl array.
     * @param array CPerlArray to be appended.
     * @return the new index for the last array element, -1 on failure.
     */
    int operator+= (const CPerlArray& array);
    /**
     * @see Append()
     */
    int operator+= (const CStringArray& array);
    /**
     * @see Add()
     */
    int operator+= (LPCTSTR element);

    /**
     * @see GetAt()
     */
    CPerlScalar operator[](int nIndex);
    /**
     * Returns the value of element at the given index.
     * @warning If the result scalar is cloned, then modifying
     * it will also modify the array element (interesting behaviour!).
     * BUT, if the result scalar is copied, this will not modify
     * the array. Example: "CPerlScalar newscalar = a.GetAt(0);" => you'll
     * be able to modify the array element;
     * "CPerlScalar s = interp.GetScalar(...); s = a.GetAt(0);" => you won't.
     * @param nIndex Item index.
     * @return a valid CPerlScalar object upon success.
     */
    CPerlScalar GetAt(int nIndex);

    /**
     * @return the size of the array, -1 on failure.
     */
    int GetSize() const;
    /**
     * @see GetSize()
     */
    int GetCount() const;
    /**
     * @return true if array is empty, false otherwise.
     */
    bool IsEmpty() const;
    /**
     * @return the last element index.
     */
    int GetUpperBound() const;
    /**
     * Extends the array to desired size. Use it prior
     * to adding several elements using Add().
     * @param nNewSize New size. If nNewSize is smaller than
     * array actual size, overheading elements will be lost.
     */
    void SetSize(int nNewSize);
    /**
     * Clears the array. Does not free memory.
     * @see undef()
     */
    void RemoveAll();
    
    void SetAt(int nIndex, LPCTSTR newElement);
    const CString& ElementAt(int nIndex) const;
    void SetAtGrow(int nIndex, LPCTSTR newElement);

    /**
     * Appends a single item to the current Perl array.
     * @param newElement String to be appended.
     * @return the new index for the last array element.
     */
    int Add(LPCTSTR newElement);
    int Add(const CString& newElement);

    /**
     * Appends the CStringArray values to the current Perl array.
     * @param newArray CStringArray to be appended.
     * @return the new index for the last array element, -1 on failure.
     */
    int Append(const CStringArray& newArray);
    /**
     * Appends the CPerlArray values to the current Perl array.
     * @param newArray CPerlArray to be appended.
     * @return the new index for the last array element, -1 on failure.
     */
    int Append(const CPerlArray& newArray);

    /**
     * Copies the CStringArray values onto the current Perl array.
     * @param newArray CStringArray to be copied.
     */
    void Copy(const CStringArray& newArray);

    /**
     * Pops a specified number of elements from the array.
     * That is, remove the nCount last elements from the array.
     * @param nCount number of elements to pop.
     * @return the last pop'ed element, a valid CPerlScalar upon success.
     */
    CPerlScalar pop(int nCount=1);

    /**
     * Pushes several elements to the array.
     * That is, appends several elements to the array.
     * @param szFirst First element to push.
     * @param nCount Number of other elements
     * to push (number of vararg arguments).
     * @param ... Other elements to be pushed.
     * @warning vararg arguments must be valid LPCTSTR pointers,
     * otherwise your application may crash.
     * @return the index of the last array element, -1 on failure.
     * @see Add()
     */
    int push(LPCTSTR szFirst, int nCount=0, ...);
    
    //int push(const CStringArray& array);

    //int push(const CPerlArray& array);

    /**
     * Shifts several elements from the array. That is,
     * remove several elements from the array head.
     * @param nCount Number of elements to shift.
     * @return last element shifted, a valid CPerlScalar upon success.
     */
    CPerlScalar shift(int nCount=1); // returns last unshift-ed


    /**
     * Unshifts several elements to the array. That is, add several
     * elements at the head of the array.
     * @param szFirst First element to unshift.
     * @param nCount Number of other elements
     * to unshift (number of vararg arguments).
     * @param ... Other elements to be unshifted.
     * @warning vararg arguments must be valid LPCTSTR pointers,
     * otherwise your application may crash.
     * @return the index of the last array element, -1 on failure.
     */
    int unshift(LPCTSTR szFirst, int nCount=0, ...);
    /**
     * Unshifts several elements to the array.
     * @param array CStringArray of elements to unshift.
     * @return the index of the last array element, -1 on failure.
     */
    int unshift(const CStringArray& array);
    /**
     * Unshifts several elements to the array.
     * @param array CPerlArray of elements to unshift.
     * @return the index of the last array element, -1 on failure.
     */
    int unshift(const CPerlArray& array);

    /**
     * Pushes, in reverse order, several elements to the array.
     * @param array CStringArray of elements to unshift.
     * @return the index of the last array element, -1 on failure.
     */
    int reverse_push(const CStringArray& array);
    /**
     * Pushes, in reverse order, several elements to the array.
     * @param array CPerlArray of elements to unshift.
     * @return the index of the last array element, -1 on failure.
     */
    int reverse_push(const CPerlArray& array);
    /**
     * Unshifts, in reverse order, several elements to the array.
     * @param array CStringArray of elements to unshift.
     * @return the index of the last array element, -1 on failure.
     */
    int reverse_unshift(const CStringArray& array);
    /**
     * Unshifts, in reverse order, several elements to the array.
     * @param array CPerlArray of elements to unshift.
     * @return the index of the last array element, -1 on failure.
     */
    int reverse_unshift(const CPerlArray& array);

    void undef(void); /**< Removes all the elements
                           and frees the memory asscoiated with them. */ 
    void clear(void); /**< Removes all the elements. */

    /**
     * The extra Perl-like function. However, there is no "sv_join"
     * function exported by Perl. Therefore, calling this function
     * is roughly the same as calling Eval("join...") in terms
     * of performance but is more convenient.
     * @param szGlue The joining glue.
     * @return a valid CPerlScalar object upon success.
     * @see CPerlScalar::split()
     */
    CPerlScalar join(LPCTSTR szGlue);
};

CPerlHash

/** @class CPerlHash
* Represents a Perl hash.
*/
class PXPERL_API CPerlHash : public CPerlVariable
{
public:
    CPerlHash();
    /**< Constructs a CPerlHash object, marked as invalid, 
         since not associated with a real Perl hash. */

    /**
     * Populates a CMapStringToString with the Perl hash keys.
     * Keys can be appended to the existing CMapStringToString passed.
     * @param mapRet CMapStringToString to receive the keys.
     * @param bAppend true to append values
     * to the passed hash, false otherwise.
     * @return the CMapStringToString object passed.
     */
    CMapStringToString& MapStringToString(CMapStringToString 
                               &mapRet, bool bAppend=false);

    /**
     * Copies passed CStringArray keys onto Perl hash,
     * overwriting any existing key.
     * @param map CMapStringToString to be copied.
     * @return the CMapStringToString object passed.
     */
    const CMapStringToString& operator= 
                    (const CMapStringToString& map);

    /**
     * Clones the passed the passed CPerlHash object if this
     * one is invalid, or copies its keys if valid.
     * @param hash CPerlHash object to be cloned/copied.
     * @return the CPerlHash object passed.
     */
    const CPerlHash& operator= (const CPerlHash& hash);

    /**
     * @return the number of keys of the hash.
     */
    int GetCount() const;
    /**
     * @return the number of keys of the hash.
     */
    int GetSize() const;
    /**
     * @return true if the hash is empty, false otherwise.
     */
    bool IsEmpty() const;

    /**
     * Look up for a key in the hash.
     * @param key The key to look up for.
     * @param rValue A CString to receive the value associated
     * with the key. If key if not found, rValue is not modified.
     * @return true if the key was found, false otherwise.
     */
    bool Lookup(LPCTSTR key, CString& rValue) const;

    /**
     * Look up for a key in the hash.
     * @param key The key to look up for.
     * @return a valid CPerlScalar upon success.
     * @see CPerlArray::GetAt() for the same remark
     * about the CPerlScalar object returned.
     */
    CPerlScalar Lookup(LPCTSTR key) const;

    /**
     * @see Lookup()
     */
    CPerlScalar operator[](LPCTSTR key) const;

    /**
     * Adds (or modifies) a key to the hash.
     * @param key Key.
     * @param newValue Value. 
     */
    void SetAt(LPCTSTR key, LPCTSTR newValue);

    /**
     * Removes specified key.
     * @param key Key.
     * @return true if key is found and deleted
     * successfuly, false otherwise.
     */
    bool RemoveKey(LPCTSTR key);

    /**
     * Removes all keys from the hash.
     * @see clear()
     */
    void RemoveAll();

    /**
     * Iterates through the hash the same way Perl's each function do.
     * Each call returns next key/value pair,
     * in an unpredictable order, but always the same order.
     * @param strKey Next key encountered in the hash.
     * @param strValue Value associated with the key.
     * @return true as long as all the keys
     * have not been enumerated, false on end.
     */
    bool each(CString &strKey, CString &strValue);

    /**
     * Returns the hash keys the same way Perl's keys function do.
     * @warning Calling this function resets the iterator,
     * so next call to each() will return the first key/value pair.
     * @param strARet CStringArray to receive the keys.
     * @return the passed CStringArray.
     */
    CStringArray& keys(CStringArray &strARet);
    /**
     * Returns the hash values the same way Perl's values function do.
     * @warning Calling this function resets the iterator,
     * so next call to each() will return the first key/value pair.
     * @param strARet CStringArray to receive the values.
     * @return the passed CStringArray.
     */
    CStringArray& values(CStringArray &strARet);

    /**
     * Tells if a key exists.
     * @param key Key.
     * @return true if key exists, false otherwise. 
     */
    bool exists(LPCTSTR key);

    void undef(void);
    /**< Removes all the key/value pairs and frees
         the memory asscoiated with them. */ 
    void clear(void);
    /**< Removes all the key/value pairs. */

};

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 /*destination window for messages*/, 
                          UINT nRedirectMessage);
void PXUninitializeRedirect(void);

////



// static, process-wide variables

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;


// the pipes size

// also used for buffer size

#define REDIR_BUFSIZE 4096


// The structure which is passed to threads upon their creation

typedef struct sThreadData
{
    HWND hWnd;
    int hReadPipe;
    UINT nMsg;
    WPARAM nMsgWParam;
} ThreadData;


// threaded procedure

static UINT __stdcall RedirectProc(void *pData)
{
    ThreadData td;
    int nBytesRead;
    char *buffer;
    // choose always a char* even under Unicode,

    // to avoid dubious conversions


    // make a copy of the thread info which is not static

    memcpy(&td, pData, sizeof(ThreadData));

    // signal the mother thread we are done with copying the thread data

    SetEvent(s_hEvent);

    // the buffer which receive data from read pipe

    // and is passed to destination window

    buffer = new char[REDIR_BUFSIZE];
    
    // begin the read/send loop

    while (s_bRedirecting)
    {
        if ((nBytesRead = _read(td.hReadPipe, buffer, REDIR_BUFSIZE)) < 1)
        {
            // if an error occurs while reading

            // the pipe or nothing to read, we'll get here

            if (!s_bRedirecting || errno)
                break;
            Sleep(20);
            continue;
        }

        buffer[nBytesRead] = 0;

        //TRACE("[PXPW] RedirectThread(%d): %d bytes sent\n", 

        //                        td.nMsgWParam, nBytesRead);

        
        // if we are asked to stop or window is invalid, break

        if (!s_bRedirecting || !::IsWindow(td.hWnd))
            break;

        // Send data

        ::SendMessage(td.hWnd, td.nMsg, td.nMsgWParam, (LPARAM)buffer);
    }

    // free memory

    delete [] buffer;
    TRACE("[PXPW] RedirectThread(%d): deleted buffer\n", td.nMsgWParam);

    // signal we ended

    _endthreadex(0);
    return 0;
}

bool PXInitializeRedirect(HWND hWnd, UINT nRedirectMessage)
{
    // stop any previous redirection

    PXUninitializeRedirect();

    UINT thread_id;
    ThreadData td;    
    FILE *fp = NULL;

    STARTUPINFO si;
    si.cb = sizeof(STARTUPINFO);
    GetStartupInfo(&si);

    // create an event used for threads initialization

    s_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    // create pipes

    // for output

    if (_pipe(s_handlesStdOut, REDIR_BUFSIZE, 
              _O_TEXT | _O_NOINHERIT) == -1)
        goto _redirect_failure;
    // for errors

    if (_pipe(s_handlesStdErr, REDIR_BUFSIZE, 
              _O_TEXT | _O_NOINHERIT) == -1)
        goto _redirect_failure;

    // close the existing output and errors file descriptors

    _close(1); // output

    _close(2); // errors

    
    // reassign the output and errors file descriptors

    if (-1 == _dup2(s_handlesStdOut[1], 1))
        goto _redirect_failure;
    if (-1 == _dup2(s_handlesStdErr[1], 2))
        goto _redirect_failure;
    
    // close the write file descriptors of the pipes,

    // which we don't need anymore

    // (we only need to read now)

    _close(s_handlesStdOut[1]);
    _close(s_handlesStdErr[1]);
    s_handlesStdOut[1] = -1;
    s_handlesStdErr[1] = -1;

    // this is not really needed, we just make sure the standard

    // console output and errors handles of our application are the same as 

    if (si.hStdOutput != (HANDLE)-1)
        SetStdHandle(STD_OUTPUT_HANDLE, si.hStdOutput);
    if (si.hStdError != (HANDLE)-1)
        SetStdHandle(STD_ERROR_HANDLE, si.hStdError);

    // let's start the threads now...


    s_bRedirecting = true;
    
    // fill the thread info struct

    td.hWnd = hWnd;
    td.nMsg = nRedirectMessage;

    td.nMsgWParam = PXPERL_STDOUT;
    td.hReadPipe = s_handlesStdOut[0];

    // start the first one

    s_hThreadStdOut = (HANDLE)_beginthreadex(NULL, 0, 
                       RedirectProc, (void*)&td, 1, &thread_id);
    if (!s_hThreadStdOut)
        goto _redirect_failure;
    // wait for signal thread finished initialization

    WaitForSingleObject(s_hEvent, INFINITE);

    // the same for the second

    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);

    // important: reset the stdout and stderr structs for current process,

    // so not only low level IO access function

    // are being redirected, but also

    // printf() and so on.

    fp = _fdopen(1, "w");
    setvbuf(fp, NULL, _IONBF, 0);
    memcpy(stdout, fp, sizeof(FILE));
    //memcpy(win32_stdout(), fp, sizeof(FILE));


    fp = _fdopen(2, "w");
    setvbuf(fp, NULL, _IONBF, 0);
    memcpy(stderr, fp, sizeof(FILE));
    //memcpy(win32_stderr(), fp, sizeof(FILE));


    return true;

_redirect_failure:
    // failure, close handles still open

    PXUninitializeRedirect();
    return false;
}

void PXUninitializeRedirect(void)
{
    if (s_bRedirecting)
    {
        // send something to the pipe, so the threads leave the _read(...)

        // and can test if they must quit

        s_bRedirecting = false;
        char eos[] = "\n";
        _write(1, eos, sizeof(eos));
        _write(2, eos, sizeof(eos));
        // let them a slice to leave their loop

        Sleep(20);
    }

    // then, close handles and set them to NULL


    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):

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

  6. For each configuration, add the additional include directory "$(PXPERL)\lib\CORE".

  7. Now, wherever you want to use PXPerlWrap classes, just add this to your program header file:
     #include "PXPerlWrap/PXPerlWrap.h"
  8. 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here