Mercurial > hg
diff mcabber/doc/HOWTO_modules.txt @ 1735:5093b5ca1572
New modules loading scheme
author | Myhailo Danylenko <isbear@ukrpost.net> |
---|---|
date | Thu, 04 Mar 2010 13:03:20 +0200 |
parents | b09f82f61745 |
children | 4a7c7900f600 |
line wrap: on
line diff
--- a/mcabber/doc/HOWTO_modules.txt Tue Mar 02 13:47:43 2010 +0100 +++ b/mcabber/doc/HOWTO_modules.txt Thu Mar 04 13:03:20 2010 +0200 @@ -5,18 +5,48 @@ =========================================== -Mcabber loads modules via glib's GModule. - -Thus, in your module you can provide functions +To obtain information on module mcabber uses struct +module_info_t, that module should provide in public +variable with name info_<modulename>. If module name +contains any extra symbols except [a-z0-9_] they should +be replaced with '_'. -------------------------------------------------------- + #include <mcabber/modules.h> + + typedef void (*module_init_t)(void); + typedef void (*module_uninit_t)(void); + + typedef struct { + const gchar *mcabber_version; + module_init_t init; + module_uninit_t uninit; + const gchar **requires; + } module_info_t; +-------------------------------------------------------- + +Callbacks init and uninit will be called after module +and it's dependencies loading. 'requires' should contain +NULL-terminated list of module names, that should be loaded +before this. 'mcabber_version' is required and should contain +mcabber version, that this module is designed to work with. +Three other fields may be NULL. + +To load modules, mcabber uses glib's GModule, thus, in your +module you can also use functions + +-------------------------------------------------------- + #include <glib.h> + #include <gmodule.h> + const gchar* g_module_check_init (GModule *module); void g_module_unload (GModule *module); -------------------------------------------------------- -to do something when module is loaded and unloaded. On -success g_module_check_init should return NULL, and -error message otherwise. +to do something before any version/dependency checks will +be performed when module is loaded/unloaded. On success +g_module_check_init should return NULL, and error message +otherwise. As module is loaded, you can use mcabber functions, declared in mcabber's header files (though you should @@ -28,6 +58,46 @@ especially for module writers. -------------------------------------------------------- + #include <mcabber/modules.h> + + const gchar *module_load (const gchar *name, + gboolean manual, + gboolean force); + const gchar *module_unload (const gchar *name, + gboolean manual, + gboolean force); +-------------------------------------------------------- + +These functions load and unload modules respectively. +You can use them to handle optional dependencies. What +happens, when module is loaded: + - check if module is present, and if present just + increase it's reference count + - load .so via glib (and call g_module_check_init, if + present) + - check for information structure presence + - check target mcabber version compatibility + - load modules, that this module requires (note, that + dependency problems will be reported as error + invariably, force flag have no effect on this check) + - module placed into a list of modules + - module init routine is called +And when unloaded: + - check if module is present + - decrease reference count, if it is not zero, return + - run module uninit routine + - unload modules, that were loaded as dependencies for + this + - remove from modules list +They return error message or NULL in case of success. +'manual' flag indicates, that module will be loaded by +direct user request. It serves the purpose of tracking +user and automatic references (user can have only one). +'force' flag on module loading causes mcabber to ignore +most of the loading errors. On unload it forces +unloading even if reference count is not zero. + +-------------------------------------------------------- #include <mcabber/commands.h> void cmd_add (const char *name, const char *help, @@ -200,10 +270,10 @@ Now, compile this file (hello.c) with libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \ - gmodule-2.0` -c hello.c + gmodule-2.0 mcabber` -c hello.c libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \ - `pkg-config --libs glib-2.0 gmodule-2.0` -o libhello.la \ - hello.lo + `pkg-config --libs glib-2.0 gmodule-2.0 mcabber` \ + -o libhello.la hello.lo (you should substitute /usr/lib/mcabber to directory, where your modules are located) and then install obtained module with @@ -216,13 +286,68 @@ wide library, but maybe some systems require that. Now, set modules_dir mcabber variable to point to your modules -dir, and try to run /load hello. If all goes well, you should -see in status buffer message "Hello World!". -Now unload module by running command /unload hello, that -should bring up message "Bye, World!". +dir, and try to run /module -f load hello. If all goes well, +you should see in status buffer message "Hello World!" (as +well as some complaints, as we forced module loading). +Now unload module by running command /module unload hello, +that should bring up message "Bye, World!". That's it, you just created very simple dynamically loadable -mcabber module. +mcabber module. But, as you noticed, it needs force to be +loaded. Now, let's add information structure, that mcabber +wants. + +========================== + + Example: info struct + +========================== + +-------------------------------------------------------- +#include <mcabber/logprint.h> +/* module_info_t definition */ +#include <mcabber/modules.h> + +/* Print something on module loading */ +void hello_init (void) +{ + scr_LogPrint (LPRINT_NORMAL, "Hello, World!"); +} + +/* ... and unloading */ +void hello_uninit (void) +{ + scr_LogPrint (LPRINT_NORMAL, "Bye, World!"); +} + +module_info_t info_hello = { + .mcabber_version = "0.10.0", + .requires = NULL, + .init = hello_init, + .uninit = hello_uninit, +}; + +/* The End */ +-------------------------------------------------------- + +Here we now do not use glib nor gmodule, so, we can omit +them in compilation lines: + +libtool --mode=compile gcc `pkg-config --cflags mcabber` \ + -c hello.c +libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \ + `pkg-config --libs mcabber` -o libhello.la hello.lo + +Again compile it, copy, and try to load, now without -f +flag. As you may notice, when loading previous example, +mcabber first printed "Hello, World!", and only then +complaint about module not having information struct. +That's because g_module_check_init is called right +after module loading, before mcabber even have a chance +to look at module, while .init from info struct is +called afterwards by mcabber itself. You can try to +introduce some error (eg too high or missing target +mcabber version) and see the difference. ======================= @@ -233,11 +358,9 @@ Now, let's allow our module to do some real work. -------------------------------------------------------- -#include <glib.h> -#include <gmodule.h> - #include <mcabber/logprint.h> #include <mcabber/commands.h> +#include <mcabber/modules.h> /* Handler for command */ void do_hello (char *args) @@ -249,18 +372,24 @@ } /* Register command */ -const gchar* g_module_check_init (GModule *module) +void hello_init (void) { cmd_add ("hello", "", 0, 0, do_hello, NULL); - return NULL; } /* Unregister command */ -void g_module_unload (GModule *module) +void hello_uninit (void) { cmd_del ("hello"); } +module_info_t hello_info = { + .mcabber_version = "0.10.0", + .requires = NULL, + .init = hello_init, + .uninit = hello_uninit, +} + /* The End */ -------------------------------------------------------- @@ -281,11 +410,9 @@ their IDs are listed in compl.h. -------------------------------------------------------- -#include <glib.h> -#include <gmodule.h> - #include <mcabber/logprint.h> #include <mcabber/commands.h> +#include <mcabber/modules.h> #include <mcabber/compl.h> static guint hello_cid = 0; @@ -303,7 +430,7 @@ } /* Initialization */ -const gchar* g_module_check_init (GModule *module) +void hello_init (void) { /* Obtain handle for our completion * category */ @@ -315,11 +442,10 @@ "World"); cmd_add ("hello", "", hello_cid, 0, do_hello, NULL); - return NULL; } /* Deinitialization */ -void g_module_unload (GModule *module) +void hello_uninit (void) { /* Give back category handle */ if (hello_cid) @@ -327,6 +453,13 @@ cmd_del ("hello"); } +module_info_t hello_info = { + .mcabber_version = "0.10.0", + .requires = NULL, + .init = hello_init, + .uninit = hello_uninit, +} + /* The End */ -------------------------------------------------------- @@ -349,8 +482,6 @@ muc conference message, not just ones, directed to me. -------------------------------------------------------- -#include <glib.h> -#include <gmodule.h> #include <string.h> #include <mcabber/logprint.h> @@ -359,6 +490,7 @@ #include <mcabber/hooks.h> #include <mcabber/screen.h> #include <mcabber/settings.h> +#include <mcabber/module.h> static guint beep_cid = 0; @@ -400,7 +532,7 @@ } /* Initialization */ -const gchar* g_module_check_init (GModule *module) +void beep_init (void) { /* Create completions */ beep_cid = compl_new_category (); @@ -414,11 +546,10 @@ * We are only interested in incoming message events */ hk_add_handler (beep_hh, HOOK_MESSAGE_IN, NULL); - return NULL; } /* Deinitialization */ -void g_module_unload (GModule *module) +void beep_uninit (void) { /* Unregister event handler */ hk_del_handler (beep_hh, NULL); @@ -429,20 +560,16 @@ compl_del_category (beep_cid); } +module_info_t beep_info = { + .mcabber_version = "0.10.0", + .requires = NULL, + .init = beep_init, + .uninit = beep_uninit, +} + /* The End */ -------------------------------------------------------- -Note, that to compile this we also need to add loudmouth-1.0 -to pkg-config command line, so, you will have something like - -libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \ - gmodule-2.0 loudmouth-1.0` -c beep.c -libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \ - `pkg-config --cflags glib-2.0 gmodule-2.0` -o libbeep.la \ - beep.lo -libtool --mode=install install libbeep.la \ - /usr/lib/mcabber/libbeep.la - If you use CMake (as do I), corresponding CMakeLists.txt snippet: @@ -450,31 +577,86 @@ cmake_minimum_required(VERSION 2.6) project(beep C) -set(MCABBER_INCLUDE_DIR "/usr/include" CACHE FILEPATH - "Path to mcabber headers") - find_package(PkgConfig REQUIRED) -pkg_check_modules(GLIB REQUIRED glib-2.0) -pkg_check_modules(GMODULE REQUIRED gmodule-2.0) -pkg_check_modules(LM REQUIRED loudmouth-1.0) +pkg_check_modules(MCABBER REQUIRED mcabber) # this one should be before any target definitions -link_directories(${GLIB_LIBRARY_DIRS} - ${GMODULE_LIBRARY_DIRS}) +link_directories(${MCABBER_LIBRARY_DIRS}) add_library(beep MODULE beep.c) -include_directories(SYSTEM ${GLIB_INCLUDE_DIRS} - ${GMODULE_INCLUDE_DIRS} - ${LM_INCLUDE_DIRS} - ${MCABBER_INCLUDE_DIR}) -target_link_libraries(beep ${GLIB_LIBRARIES} - ${GMODULE_LIBRARIES}) +include_directories(SYSTEM ${MCABBER_INCLUDE_DIRS}) +target_link_libraries(beep ${MCABBER_LIBRARIES) include_directories(${beep_SOURCE_DIR} ${beep_BINARY_DIR}) install(TARGETS beep DESTINATION lib/mcabber) -------------------------------------------------------- +=========================== + + Example: dependencies + +=========================== + +I will not provide here a complete example of two +modules, one of which depends on other, only some +use cases. + +Info struct for module, that depends on two other +modules: + +-------------------------------------------------------- +#include <mcabber/modules.h> + +const gchar *a_deps[] = { "b", "c", NULL }; + +module_info_t info_a = { + .mcabber_version = "0.10.0", + .requires = a_deps, + .init = a_init, + .uninit = a_uninit, +}; +-------------------------------------------------------- + +If your module needs to "authenticate" mcabber version +too, this can be done in g_module_check_init: + +-------------------------------------------------------- +#include <glib.h> +#include <gmodule.h> + +#include <mcabber/main.h> + +const gchar *g_module_check_init (GModule *module) +{ + char *ver = mcabber_version (); + + // ver now contains version in format + // X.X.X[-xxx][ (XXXXXXXXX)] + if (...) + return "Incompatible mcabber version"; + + g_free (ver); + return NULL; +} +-------------------------------------------------------- + +Also you can use glib check_init routine to modify +module information, that will be checked by mcabber, +eg. if you want your module to always pass mcabber +version check, you can assign version, obtained from +mcabber_version() to corresponding field in your struct. +Or you can modify your module's dependencies, though +direct module_load() will have the same effect, and +can be used for optional dependencies, that your module +can work without. + +Note: remember, that g_module_check_init will be always +called, even if later module will not pass checks, thus: + - do not use functions from other modules there; + - provide g_module_unload to undo anything, check_init + has done. + ============== Further @@ -509,5 +691,5 @@ -- Myhailo Danylenko -- mailto:isbear@ukrpost.net -- xmpp:isbear@unixzone.org.ua - -- Mon, 18 Jan 2010 15:52:40 +0200 + -- Thu, 04 Mar 2010 09:32:38 +0200