//============================================================================= // // File : libkviperlcore.cpp // Creation date : Tue Jul 13 13:03:31 2004 GMT by Szymon Stefanek // // This file is part of the KVIrc IRC client distribution // Copyright (C) 2004-2010 Szymon Stefanek (pragma at kvirc dot net) // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #include "KviModule.h" #include "kvi_settings.h" #include "KviLocale.h" #include "kvi_out.h" #include "KviWindow.h" #include "KviApplication.h" #include "KviKvsScript.h" #include "KviKvsVariant.h" #include "KviUserInput.h" #include "KviPointerHashTable.h" #include #ifdef DEBUG #undef DEBUG #endif // I MUST say that the perl embedding process is somewhat ugly :( // First of all the man pages are somewhat unreadable even // for a non-novice perl user. The writer of each page assumed // that you have already read each other page... // Also browsing the pages with "man" is obviously out of mind // but this can be solved by looking up some html docs on the net. // Embedding multiple interpreters isn't that hard (after you // have read perlembed) but to start passing parameters // around you have to read at least perlembed, perlguts, perlxs,.. // take a look at the perlinternals and have a good trip // around the web to find some examples for the functions // that aren't explained enough in the pages. // It gets even more weird when you attempt to include // some XS functions... (what the heck is boot_DynaLoader ?). // ... and I'm still convinced that I'm leaking memory with // the perl values, but well ... // anyway, once you struggled for a couple of days with all that // stuff then you start getting things done... and it rox :) // Note for kvirc4: perl embedding has changed between perl 5.8 and perl 5.10. // This version should work nice with both, but warranty is 5.10-only. #ifdef COMPILE_PERL_SUPPORT #include #include #include #define NEED_eval_pv #include "ppport.h" #include "KviKvsRunTimeContext.h" static KviKvsRunTimeContext * g_pCurrentKvsContext = nullptr; static bool g_bExecuteQuiet = false; static KviCString g_szLastReturnValue(""); static QStringList g_lWarningList; // this is why we can't have nice things -- part of perl 5.17.1+ #ifdef __cplusplus #define dNOOP (void)0 #else #define dNOOP extern int Perl___notused(void) #endif #include "xs.inc" #endif // COMPILE_PERL_SUPPORT // perl redefines bool :/// #ifdef bool #undef bool #endif #ifdef COMPILE_PERL_SUPPORT #include "perlcoreinterface.h" // This should be able to be rewritten in the form: // static PerlInterpreter *m_pInterpreter // people ... are you mad ? ... what the heck is "my_perl" ? #define my_perl m_pInterpreter class KviPerlInterpreter { public: KviPerlInterpreter(const QString & szContextName); ~KviPerlInterpreter(); protected: QString m_szContextName; PerlInterpreter * m_pInterpreter; public: bool init(); // if this fails then well.. :D void done(); bool execute(const QString & szCode, QStringList & args, QString & szRetVal, QString & szError, QStringList & lWarnings); const QString & contextName() const { return m_szContextName; }; protected: QString svToQString(SV * sv); }; KviPerlInterpreter::KviPerlInterpreter(const QString & szContextName) { m_szContextName = szContextName; m_pInterpreter = nullptr; } KviPerlInterpreter::~KviPerlInterpreter() { done(); } // this kinda sux :( // It SHOULD be mentioned somewhere that // this function is in DynaLoader.a in the perl // distribution and you MUST link it statically. // Update: it is no more needed as of perl 5.10, since it is // included in the standard libperl interface. extern "C" void boot_DynaLoader(pTHX_ CV * cv); extern "C" void xs_init(pTHX) { char * file = (char *)__FILE__; // boot up the DynaLoader newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); // now bootstrap the KVIrc module // This stuff is simply cutted and pasted from xs.inc // since I don't really know if it's safe to call // something like // CV * dummy; // boot_KVIrc(aTHX,dummy); // ... newXS("KVIrc::echo", XS_KVIrc_echo, file); newXS("KVIrc::say", XS_KVIrc_say, file); newXS("KVIrc::warning", XS_KVIrc_warning, file); newXS("KVIrc::getLocal", XS_KVIrc_getLocal, file); newXS("KVIrc::setLocal", XS_KVIrc_setLocal, file); newXS("KVIrc::getGlobal", XS_KVIrc_getGlobal, file); newXS("KVIrc::setGlobal", XS_KVIrc_setGlobal, file); newXS("KVIrc::eval", XS_KVIrc_eval, file); newXS("KVIrc::internalWarning", XS_KVIrc_internalWarning, file); } bool KviPerlInterpreter::init() { if(m_pInterpreter) done(); const char * daArgs[] = { "yo", "-e", "0", "-w" }; m_pInterpreter = perl_alloc(); if(!m_pInterpreter) return false; PERL_SET_CONTEXT(m_pInterpreter); PL_perl_destruct_level = 1; perl_construct(m_pInterpreter); perl_parse(m_pInterpreter, xs_init, 4, (char **)daArgs, nullptr); QString szInitCode; // this part of the code seems to be unnecessary // even if it is created by the perl make process... // "our %EXPORT_TAGS = ('all' => [qw(echo)]);\n" // "our @EXPORT_OK = (qw(echo));\n" // "our @EXPORT = qw();\n" // This is probably needed only if perl has to load // the XS through XSLoader ? // Maybe also the remaining part of the package // declaration could be dropped as well... // I just haven't tried :D szInitCode = QString( "{\n" "package KVIrc;\n" "require Exporter;\n" "our @ISA = qw(Exporter);\n" "1;\n" "}\n" "$g_szContext = \"%1\";\n" "$g_bExecuteQuiet = 0;\n" "$SIG{__WARN__} = sub\n" "{\n" " my($p,$f,$l,$x);\n" " ($p,$f,$l) = caller;\n" " KVIrc::internalWarning(\"At line \".$l.\" of Perl code: \");\n" " KVIrc::internalWarning(join(' ',@_));\n" "}\n") .arg(m_szContextName); eval_pv(szInitCode.toUtf8().data(), false); return true; } void KviPerlInterpreter::done() { if(!m_pInterpreter) return; PERL_SET_CONTEXT(m_pInterpreter); PL_perl_destruct_level = 1; perl_destruct(m_pInterpreter); perl_free(m_pInterpreter); m_pInterpreter = nullptr; } QString KviPerlInterpreter::svToQString(SV * sv) { QString ret = ""; if(!sv) return ret; STRLEN len; char * ptr = SvPV(sv, len); if(ptr) ret = ptr; return ret; } bool KviPerlInterpreter::execute( const QString & szCode, QStringList & args, QString & szRetVal, QString & szError, QStringList & lWarnings) { if(!m_pInterpreter) { szError = __tr2qs_ctx("Internal error: Perl interpreter not initialized", "perl"); return false; } g_lWarningList.clear(); QByteArray szUtf8 = szCode.toUtf8(); PERL_SET_CONTEXT(m_pInterpreter); // clear the _ array AV * pArgs = get_av("_", 1); SV * pArg = av_shift(pArgs); while(SvOK(pArg)) { SvREFCNT_dec(pArg); pArg = av_shift(pArgs); } if(args.count() > 0) { // set the args in the _ arry av_unshift(pArgs, (I32)args.count()); int idx = 0; for(auto tmp : args) { QByteArray szVal = tmp.toUtf8(); pArg = newSVpv(szVal.data(), tmp.length()); if(!av_store(pArgs, idx, pArg)) SvREFCNT_dec(pArg); idx++; } } // call the code SV * pRet = eval_pv(szUtf8.data(), false); // clear the _ array again pArgs = get_av("_", 1); pArg = av_shift(pArgs); while(SvOK(pArg)) { SvREFCNT_dec(pArg); pArg = av_shift(pArgs); } av_undef(pArgs); // get the ret value if(pRet) { if(SvOK(pRet)) szRetVal = svToQString(pRet); } if(!g_lWarningList.isEmpty()) lWarnings = g_lWarningList; // and the eventual error string pRet = get_sv("@", false); if(pRet) { if(SvOK(pRet)) { szError = svToQString(pRet); if(!szError.isEmpty()) return false; } } return true; } static KviPointerHashTable * g_pInterpreters = nullptr; static KviPerlInterpreter * perlcore_get_interpreter(const QString & szContextName) { KviPerlInterpreter * i = g_pInterpreters->find(szContextName); if(i) return i; i = new KviPerlInterpreter(szContextName); if(!i->init()) { delete i; return nullptr; } g_pInterpreters->replace(szContextName, i); return i; } static void perlcore_destroy_interpreter(const QString & szContextName) { KviPerlInterpreter * i = g_pInterpreters->find(szContextName); if(!i) return; g_pInterpreters->remove(szContextName); i->done(); delete i; } static void perlcore_destroy_all_interpreters() { KviPointerHashTableIterator it(*g_pInterpreters); while(it.current()) { KviPerlInterpreter * i = it.current(); i->done(); delete i; ++it; } g_pInterpreters->clear(); } #endif // COMPILE_PERL_SUPPORT static bool perlcore_module_ctrl(KviModule *, const char * cmd, void * param) { #ifdef COMPILE_PERL_SUPPORT if(kvi_strEqualCS(cmd, KVI_PERLCORECTRLCOMMAND_EXECUTE)) { KviPerlCoreCtrlCommand_execute * ex = (KviPerlCoreCtrlCommand_execute *)param; if(ex->uSize != sizeof(KviPerlCoreCtrlCommand_execute)) return false; g_pCurrentKvsContext = ex->pKvsContext; g_bExecuteQuiet = ex->bQuiet; if(ex->szContext.isEmpty()) { KviPerlInterpreter * m = new KviPerlInterpreter("temporary"); if(!m->init()) { delete m; return false; } ex->bExitOk = m->execute(ex->szCode, ex->lArgs, ex->szRetVal, ex->szError, ex->lWarnings); m->done(); delete m; } else { KviPerlInterpreter * m = perlcore_get_interpreter(ex->szContext); ex->bExitOk = m->execute(ex->szCode, ex->lArgs, ex->szRetVal, ex->szError, ex->lWarnings); } return true; } if(kvi_strEqualCS(cmd, KVI_PERLCORECTRLCOMMAND_DESTROY)) { KviPerlCoreCtrlCommand_destroy * de = (KviPerlCoreCtrlCommand_destroy *)param; if(de->uSize != sizeof(KviPerlCoreCtrlCommand_destroy)) return false; perlcore_destroy_interpreter(de->szContext); return true; } #endif // COMPILE_PERL_SUPPORT return false; } static bool perlcore_module_init(KviModule *) { #ifdef COMPILE_PERL_SUPPORT g_pInterpreters = new KviPointerHashTable(17, false); g_pInterpreters->setAutoDelete(false); int daArgc = 4; const char * daArgs[] = { "yo", "-e", "0", "-w" }; char ** daEnv = nullptr; PERL_SYS_INIT3(&daArgc, (char ***)&daArgs, &daEnv); return true; #else // !COMPILE_PERL_SUPPORT return false; #endif // !COMPILE_PERL_SUPPORT } static bool perlcore_module_cleanup(KviModule *) { #ifdef COMPILE_PERL_SUPPORT perlcore_destroy_all_interpreters(); delete g_pInterpreters; g_pInterpreters = nullptr; // ifdef workaround for #842 #ifndef COMPILE_ON_MAC PERL_SYS_TERM(); #endif #endif // COMPILE_PERL_SUPPORT return true; } static bool perlcore_module_can_unload(KviModule *) { #ifdef COMPILE_PERL_SUPPORT return false; /* return (g_pInterpreters->count() == 0); * Perlcore module can't be cleanly unloaded since PERL_SYS_INIT3 * and PERL_SYS_TERM should never be called more than once (#1105) */ #endif // COMPILE_PERL_SUPPORT return true; } KVIRC_MODULE( "PerlCore", // module name "4.0.0", // module version "Copyright (C) 2008 Szymon Stefanek (pragma at kvirc dot net)", // author & (C) "Perl scripting engine core", perlcore_module_init, perlcore_module_can_unload, perlcore_module_ctrl, perlcore_module_cleanup, "perl")