skx has asked for the wisdom of the Perl Monks concerning the following question:

 Once upon a time I wrote a C++ MP3 streaming server, this had a core of code, and a framework for plugins.

 These plugins worked pretty simply via 'dlopen', etc.

 Now I'm recoding the server in perl, and I'd like to have a simple means of extending the software.

 Initially I though I could have a 'Plugins::Foo' package and 'use/require' it at runtime via 'eval' however this has the problem that the package loaded thusly can't access variables in the main scope of the server, because they're defined via 'my $foo = ..'.

 Is there a simple solution to this, without using 'our'??

 Right now the best solution I've got is something along these lines: (This loads the code via an eval; with the sideeffect that the three plugin functions are present at ::main:: scope - and can access things)

$plugin = 'foo'; if ( -e $plugin . ".pm" ) { #load plugin, import it into main scope. my $fileContents = &readFile( $plugin . ".pm" ); eval( $fileContents ); if ( $@ ) { handlePluginLoadError($@); exit; } # The following three functions _MUST_ be defined # by the plugin which has been loaded. Could test # they are present via symbol table introspection # to be sure, I guess. &pluginInit( "foo" ); &pluginProcess( "bar" ); &pluginTerminae( "baz" ); # Unload those functions so that they may be # reloaded without duplication warnings undef( &pluginInit ); undef( &pluginProcess ); undef( &pluginTerminate ); }

Replies are listed 'Best First'.
Re: Coding perl a plugin system?
by shotgunefx (Parson) on Dec 14, 2002 at 03:11 UTC
    You could do a few things, off the top of my head...

    You could use package vars instead of lexicals for the stuff that needs to be shared. There's nothing wrong with using package variables when it makes sense.

    Provide an OO wrapper around it so you could access the various things that need to be shared from other scopes.

    Use PadWalker to share you lexicals with the plug-in's. (Perhaps populating predefined package vars in the callers space. I would assume that all of the plugins should have a uniform interface and variable access. But on the other hand, if your jumping through a lot of hoops just to make lexicals "global" then you're probably better off with package vars in the first place. (Though exporting the lexicals will probably be easier than re-writing)


    "To be civilized is to deny one's nature."
Re: Coding perl a plugin system?
by Revelation (Deacon) on Dec 14, 2002 at 04:35 UTC
    Your method works, and is in no way bad. If you need to load code into your package,  evaling it is the standard, as far as I know... (You'll see NMS Inlines a module, and then evals the require.)

    However, there are other ways to do it, if you don't like how you're package is working right now:

    Update: Errr... Forgot the best way to do things, in my opinion. Here it is: If your plugin module uses only subroutines, you can either create a custom import routine or use to export your subroutines. This will work even if you have some special package variables, but I believe you'll have to specify those variables for export, and they will become package global to your new package (meaning they'll be like ours, not mys ). Look at perldoc -f import as well.

    Otherwise, if this doesn't work, you could always runtime load  @ISA. This may be constituted as a sin:

    If you do that, I would just put:
    eval { @YOURapp::ISA = $mod }; # May *seem* mad, but I deem it permis +sible here
    Or whatever the applicable set of modules is.
    I like shotgunefx's advice of an OO rapper, though. That's what I for code I need to be accessible. If you don't feel like writing such a rapper, you could also look into an autoload function that got the variables for you. This would probably be easiest and best done by creating a hash of variable's references you wanted to share with other packages, and then sending them with an autoload of some sort.

    However, it's easiest and probably best to just use package variables. Is there any specific reason not to do so? If so, is that reason large enough to use a more 'exotic' method for getting your variables?

    Gyan Kapur

       I think I'll switch to using the Exporter machanism soon; that feels like the correct solution.

       Many thanks for your inspiration.

Re: Coding perl a plugin system?
by Beatnik (Parson) on Dec 14, 2002 at 13:45 UTC
    I use plugin code in a few places and I got around the variable handling by passing certain variables thru the constructor, as a hash reference, array reference or similar. You can also just have an inherited method that sticks the vars in a complex data structure.

    ...Perl is like sex: if you're doing it wrong, there's no fun to it.

       Thanks for that. I did actually consider doing this, but if my plugins are to have the freedom to do arbitary things then they should have the freedom to read (and possibly modify) all the main codes variables/behaviour.

       This made me think that I'd end up having to pass all the variables I could to the plugin - which seems like a lot of housekeeping.

       The alternative is to give access to stuff in the main scope to the plugins - which was the source of my early problems.

Re: Coding perl a plugin system?
by Dogma (Pilgrim) on Dec 14, 2002 at 23:23 UTC
    I'm not advocating this as the best method but since nobody else has brought it up I thought I should. You can re-open name spaces with the 'package' keyword. The best example of this (that I can think of) are the plugins for Mail::Audit.

    from Mail/Audit/

    package Mail::Audit::KillDups; use Mail::Audit; use vars qw(@VERSION $dupfile $cache_bytes); $VERSION = '1.9'; $dupfile = ".msgid-cache"; $cache_bytes = 10000; 1; package Mail::Audit; use strict; use Fcntl; sub killdups { . . .
Re: Coding perl a plugin system?
by sauoq (Abbot) on Dec 15, 2002 at 02:27 UTC

    use Safe;

    "My two cents aren't worth a dime.";

       Thats an interesting idea - previously I've only used 'Safe' to run code from an untrusted environment.

       In this case I wanted to run trusted, external, code in ::main scope, and I didn't consider the Safe module.

       Thanks for the pointer!

        In this case I wanted to run trusted, external, code in ::main scope

        Eh? Well, it seems I misread your question. From the original:

        Initially I though I could have a 'Plugins::Foo' package and 'use/require' it at runtime via 'eval' however this has the problem that the package loaded thusly can't access variables in the main scope of the server, because they're defined via 'my $foo = ..'.

        Is there a simple solution to this, without using 'our'??

        Why look for tricks to give your plugins access to lexicals when you don't have to use lexicals in the first place?

        You really should allow interaction between your main program and your plugins via a well-defined API, not by jumping through hoops to get the plugin code evaluated in your main program's file scope. (Note that this has nothing to do with the main package scope as your reference to "::main scope" attempts to imply.) That will eliminate your ability to hide implementation specifics from your plugins. Your plugins will all be one unfortunate identifier choice away from clobbering your main program or another plugin.

        You might make such a system work as long as you are the only one writing plugins but that defeats the purpose and, in that case, you might as well just extend the main program directly.

        I really suggest you rethink your design. You should consider a design whereby the main program shares data with the plugins only by

        • Calling subs defined in the plugins,
        • Providing subs which are called by the plugins,
        • Managing documented global variables.

        "My two cents aren't worth a dime.";