changeset 136:01df93b56dde

Irccd: implement native plugins, #502 - Add brand new DynlibPlugin to load shared libraries as plugins, - Add a template as documentation.
author David Demelier <markand@malikania.fr>
date Wed, 18 May 2016 22:31:24 +0200
parents 6be066ad2329
children bc291b131f6a
files doc/examples/template-plugin.cpp lib/CMakeLists.txt lib/irccd/CMakeSources.cmake lib/irccd/dynlib.hpp lib/irccd/plugin-dynlib.cpp lib/irccd/plugin-dynlib.hpp lib/irccd/service-plugin.cpp lib/irccd/service-plugin.hpp
diffstat 8 files changed, 1080 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/examples/template-plugin.cpp	Wed May 18 22:31:24 2016 +0200
@@ -0,0 +1,229 @@
+/*
+ * This example file show how to fill irccd callbacks for a native plugin.
+ *
+ * All of the defined callbacks are optional and may be removed.
+ */
+
+#include <iostream>
+
+#include <irccd/plugin-dynlib.hpp>	// (in irccd_onReload, irccd_onLoad and irccd_onUnload)
+#include <irccd/server.hpp>
+#include <irccd/util.hpp>		// for util::join (in irccd_onNames)
+
+using namespace irccd;
+
+extern "C" {
+
+/* --- onCommand ---------------------------------------------------- */
+
+void irccd_onCommand(Irccd &,
+		     const std::shared_ptr<Server> &server,
+		     const std::string &origin,
+		     const std::string &channel,
+		     const std::string &message)
+{
+	std::cout << "onCommand: server=" << server->name()
+		  << ", origin=" << origin
+		  << ", channel=" << channel
+		  << ", message=" << message << std::endl;
+}
+
+/* --- onConnect ---------------------------------------------------- */
+
+void irccd_onConnect(Irccd &, const std::shared_ptr<Server> &server)
+{
+	std::cout << "onConnect: server=" << server->name() << std::endl;
+}
+
+/* --- onChannelMode ------------------------------------------------ */
+
+void irccd_onChannelMode(Irccd &,
+			 const std::shared_ptr<Server> &server,
+			 const std::string &origin,
+			 const std::string &channel,
+			 const std::string &mode,
+			 const std::string &arg)
+{
+	std::cout << "onChannelMode: server=" << server->name()
+		  << ", origin=" << origin
+		  << ", channel=" << channel
+		  << ", mode=" << mode
+		  << ", arg=" << arg << std::endl;
+}
+
+/* --- onChannelNotice ---------------------------------------------- */
+
+void irccd_onChannelNotice(Irccd &irccd,
+			   const std::shared_ptr<Server> &server,
+			   const std::string &origin,
+			   const std::string &channel,
+			   const std::string &notice)
+{
+	std::cout << "onChannelNotice: server=" << server->name()
+		  << ", origin=" << origin
+		  << ", channel=" << channel
+		  << ", notice=" << notice << std::endl;
+}
+
+/* --- onInvite ----------------------------------------------------- */
+
+void irccd_onInvite(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel)
+{
+	std::cout << "onInvite: server=" << server->name() << ", origin=" << origin << ", channel=" << channel << std::endl;
+}
+
+/* --- onJoin ------------------------------------------------------- */
+
+void irccd_onJoin(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel)
+{
+	std::cout << "onJoin: server=" << server->name() << ", origin=" << origin << ", channel=" << channel << std::endl;
+}
+
+/* --- onKick ------------------------------------------------------- */
+
+void irccd_onKick(Irccd &irccd,
+		  const std::shared_ptr<Server> &server,
+		  const std::string &origin,
+		  const std::string &channel,
+		  const std::string &target,
+		  const std::string &reason)
+{
+	std::cout << "onKick: server=" << server->name()
+		  << ", origin=" << origin
+		  << ", channel=" << channel
+		  << ", target=" << channel
+		  << ", reason=" << reason << std::endl;
+}
+
+/* --- onLoad ------------------------------------------------------- */
+
+void irccd_onLoad(Irccd &, DynlibPlugin &plugin)
+{
+	std::cout << "onLoad: plugin=" << plugin.name() << std::endl;
+}
+
+/* --- onMessage ---------------------------------------------------- */
+
+void irccd_onMessage(Irccd &irccd,
+		     const std::shared_ptr<Server> &server,
+		     const std::string &origin,
+		     const std::string &channel,
+		     const std::string &message)
+{
+	std::cout << "onMessage: server=" << server->name()
+		  << ", origin=" << origin
+		  << ", channel=" << channel
+		  << ", message=" << message << std::endl;
+}
+
+/* --- onMe --------------------------------------------------------- */
+
+void irccd_onMe(Irccd &irccd,
+		const std::shared_ptr<Server> &server,
+		const std::string &origin,
+		const std::string &channel,
+		const std::string &message)
+{
+	std::cout << "onMe: server=" << server->name()
+		  << ", origin=" << origin
+		  << ", channel=" << channel
+		  << ", message=" << message << std::endl;
+}
+
+/* --- onMode ------------------------------------------------------- */
+
+void irccd_onMode(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &mode)
+{
+	std::cout << "onMode: server=" << server->name() << ", origin=" << origin << ", mode=" << mode << std::endl;
+}
+
+/* --- onNames ------------------------------------------------------ */
+
+void irccd_onNames(Irccd &irccd,
+		   const std::shared_ptr<Server> &server,
+		   const std::string &channel,
+		   const std::vector<std::string> &list)
+{
+	std::cout << "onNames: server=" << server->name()
+		  << ", channel=" << channel
+		  << ", list=" << util::join(list.begin(), list.end(), ", ") << std::endl;
+}
+
+/* --- onNick ------------------------------------------------------- */
+
+void irccd_onNick(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &nick)
+{
+	std::cout << "onNick: server=" << server->name() << ", origin=" << origin << ", nick=" << nick << std::endl;
+}
+
+/* --- onNotice ----------------------------------------------------- */
+
+void irccd_onNotice(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &notice)
+{
+	std::cout << "onNotice: server=" << server->name() << ", origin=" << origin << ", notice=" << notice << std::endl;
+}
+
+/* --- onPart ------------------------------------------------------- */
+
+void irccd_onPart(Irccd &irccd,
+		  const std::shared_ptr<Server> &server,
+		  const std::string &origin,
+		  const std::string &channel,
+		  const std::string &reason)
+{
+	std::cout << "onPart: server=" << server->name()
+		  << ", origin=" << origin
+		  << ", channel=" << channel
+		  << ", reason=" << reason << std::endl;
+}
+
+/* --- onQuery ------------------------------------------------------ */
+
+void irccd_onQuery(Irccd &, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message)
+{
+	std::cout << "onQuery: server=" << server->name() << ", origin=" << origin << ", message=" << message << std::endl;
+}
+
+/* --- onQueryCommand ----------------------------------------------- */
+
+void irccd_onQueryCommand(Irccd &, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message)
+{
+	std::cout << "onQueryCommand: server=" << server->name() << ", origin=" << origin << ", message=" << message << std::endl;
+}
+
+/* --- onReload ----------------------------------------------------- */
+
+void irccd_onReload(Irccd &irccd, DynlibPlugin &plugin)
+{
+	std::cout << "onReload: plugin=" << plugin.name() << std::endl;
+}
+
+/* --- onTopic ------------------------------------------------------ */
+
+void irccd_onTopic(Irccd &irccd,
+		   const std::shared_ptr<Server> &server,
+		   const std::string &origin,
+		   const std::string &channel,
+		   const std::string &topic)
+{
+	std::cout << "onTopic: server=" << server->name()
+		  << ", origin=" << origin
+		  << ", channel=" << channel
+		  << ", topic=" << topic << std::endl;
+}
+
+/* --- onUnload ----------------------------------------------------- */
+
+void irccd_onUnload(Irccd &irccd, DynlibPlugin &plugin)
+{
+	std::cout << "onUnload: plugin=" << plugin.name() << std::endl;
+}
+
+/* --- onWhois ------------------------------------------------------ */
+
+void irccd_onWhois(Irccd &irccd, const std::shared_ptr<Server> &server, const ServerWhois &info)
+{
+	std::cout << "onWhois: server=" << server->name() << ", info-for=" << info.nick << std::endl;
+}
+
+} // !C
--- a/lib/CMakeLists.txt	Wed May 18 13:24:50 2016 +0200
+++ b/lib/CMakeLists.txt	Wed May 18 22:31:24 2016 +0200
@@ -38,6 +38,8 @@
 	list(APPEND LIBRARIES ws2_32 shlwapi)
 elseif (IRCCD_SYSTEM_MAC)
 	list(APPEND LIBRARIES resolv)
+elseif (IRCCD_SYSTEM_LINUX)
+	list(APPEND LIBRARIES dl)
 endif ()
 
 target_link_libraries(libirccd extern-duktape extern-ircclient extern-jansson cppformat ${LIBRARIES})
--- a/lib/irccd/CMakeSources.cmake	Wed May 18 13:24:50 2016 +0200
+++ b/lib/irccd/CMakeSources.cmake	Wed May 18 22:31:24 2016 +0200
@@ -52,6 +52,7 @@
 	${CMAKE_CURRENT_LIST_DIR}/options.hpp
 	${CMAKE_CURRENT_LIST_DIR}/path.hpp
 	${CMAKE_CURRENT_LIST_DIR}/plugin.hpp
+	${CMAKE_CURRENT_LIST_DIR}/plugin-dynlib.hpp
 	${CMAKE_CURRENT_LIST_DIR}/plugin-js.hpp
 	${CMAKE_CURRENT_LIST_DIR}/rule.hpp
 	${CMAKE_CURRENT_LIST_DIR}/server.hpp
@@ -128,6 +129,7 @@
 	${CMAKE_CURRENT_LIST_DIR}/logger.cpp
 	${CMAKE_CURRENT_LIST_DIR}/options.cpp
 	${CMAKE_CURRENT_LIST_DIR}/path.cpp
+	${CMAKE_CURRENT_LIST_DIR}/plugin-dynlib.cpp
 	${CMAKE_CURRENT_LIST_DIR}/plugin-js.cpp
 	${CMAKE_CURRENT_LIST_DIR}/rule.cpp
 	${CMAKE_CURRENT_LIST_DIR}/server.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/dynlib.hpp	Wed May 18 22:31:24 2016 +0200
@@ -0,0 +1,338 @@
+/*
+ * dynlib.hpp -- portable shared library loader
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DYNLIB_HPP
+#define IRCCD_DYNLIB_HPP
+
+/**
+ * \file dynlib.hpp
+ * \brief Portable shared library loader.
+ * \author David Demelier <markand@malikania.fr>
+ */
+
+/**
+ * \page Dynlib Dynlib
+ * \brief Portable shared library loader.
+ *
+ * The dynlib module let you open shared libraries dynamically at runtime.
+ *
+ * ## Operating system support
+ *
+ * | System  | Support | Remarks            |
+ * |---------|---------|--------------------|
+ * | Apple   | Ok      |                    |
+ * | FreeBSD | Ok      |                    |
+ * | Linux   | Ok      | Needs -ldl library |
+ * | Windows | Ok      |                    |
+ *
+ * ## How to export symbols
+ *
+ * When you want to dynamically load symbols from your shared library, make sure they are in a `extern "C"` block, if
+ * not they will be [mangled][name-mangling].
+ *
+ * Note, this does not mean that you can't write C++ code, it just mean that you can't use namespaces and function
+ * overloading.
+ *
+ * Example of **plugin.cpp**:
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "dynlib.hpp"
+ *
+ * extern "C" {
+ *
+ * DYNLIB_EXPORT void plugin_load()
+ * {
+ *   std::cout << "Loading plugin" << std::endl;
+ * }
+ *
+ * DYNLIB_EXPORT void plugin_unload()
+ * {
+ *   std::cout << "Unloading plugin" << std::endl;
+ * }
+ *
+ * }
+ * ````
+ *
+ * The \ref DYNLIB_EXPORT macro is necessary on some platforms to be sure that symbol will be visible. Make sure you always
+ * add it before any function.
+ *
+ * To compile, see your compiler documentation or build system. For gcc you can use the following:
+ *
+ * ````
+ * gcc -std=c++14 -shared plugin.cpp -o plugin.so
+ * ````
+ *
+ * ## How to load the library
+ *
+ * The dynlib module will search for the library in various places, thus you can use relative paths names but be sure
+ * that the library can be found. Otherwise, just use an absolute path to the file.
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "dynlib.hpp"
+ *
+ * int main()
+ * {
+ *   try {
+ *     Dynlib dso("./plugin" DYNLIB_SUFFIX);
+ *   } catch (const std::exception &ex) {
+ *     std::cerr << ex.what() << std::endl;
+ *   }
+ *
+ *   return 0;
+ * }
+ * ````
+ *
+ * ## How to load symbol
+ *
+ * The last part is symbol loading, you muse use raw C function pointer and the Dynlib::sym function.
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "dynlib.hpp"
+ *
+ * using PluginLoad = void (*)();
+ * using PluginUnload = void (*)();
+ *
+ * int main()
+ * {
+ *	try {
+ *		Dynlib dso("./plugin" DYNLIB_SUFFIX);
+ *
+ *		dso.sym<PluginLoad>("plugin_load")();
+ *		dso.sym<PluginUnload>("plugin_unload")();
+ *	} catch (const std::exception &ex) {
+ *		std::cerr << ex.what() << std::endl;
+ *	}
+ *
+ *	return 0;
+ * }
+ * ````
+ *
+ * [name-mangling]: https://en.wikipedia.org/wiki/Name_mangling
+ */
+
+#include <stdexcept>
+#include <string>
+
+#if defined(_WIN32)
+#  include <Windows.h>
+#else
+#  include <dlfcn.h>
+#endif
+
+/**
+ * \brief Export the symbol.
+ *
+ * This is required on some platforms and you should put it before your function signature.
+ *
+ * \code{.cpp}
+ * extern "C" {
+ *
+ * DYNLIB_EXPORT void my_function()
+ * {
+ * }
+ *
+ * }
+ * \endcode
+ */
+#if defined(_WIN32)
+#  define DYNLIB_EXPORT	__declspec(dllexport)
+#else
+#  define DYNLIB_EXPORT
+#endif
+
+/**
+ * \brief Usual suffix for the library.
+ *
+ * This macro expands to the suffix convention for this platform.
+ *
+ * \code{.cpp}
+ * Dynlib library("./myplugin" DYNLIB_SUFFIX);
+ * \endcode
+ *
+ * \note Don't use the suffix expanded value shown in Doxygen as it may be wrong.
+ */
+#if defined(_WIN32)
+#  define DYNLIB_SUFFIX ".dll"
+#elif defined(__APPLE__)
+#  define DYNLIB_SUFFIX ".dylib"
+#else
+#  define DYNLIB_SUFFIX ".so"
+#endif
+
+namespace irccd {
+
+/**
+ * \class Dynlib
+ * \brief Load a dynamic module.
+ *
+ * This class is a portable wrapper to load shared libraries on supported systems.
+ */
+class Dynlib {
+private:
+#if defined(_WIN32)
+	using Handle	= HMODULE;
+	using Sym	= FARPROC;
+#else
+	using Handle	= void *;
+	using Sym	= void *;
+#endif
+
+public:
+	/**
+	 * \brief Policy for symbol resolution.
+	 */
+	enum Policy {
+		Immediately,		//!< load symbols immediately
+		Lazy			//!< load symbols when needed
+	};
+
+private:
+	Handle	m_handle;
+
+	Dynlib(const Dynlib &) = delete;
+	Dynlib &operator=(const Dynlib &) = delete;
+
+	Dynlib(Dynlib &&) = delete;
+	Dynlib &operator=(Dynlib &&) = delete;
+
+#if defined(_WIN32)
+	std::string error()
+	{
+		LPSTR error = nullptr;
+		std::string errmsg;
+
+		FormatMessageA(
+			FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+			NULL,
+			GetLastError(),
+			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+			(LPSTR)&error, 0, NULL);
+
+		if (error) {
+			errmsg = std::string(error);
+			LocalFree(error);
+		}
+
+		return errmsg;
+	}
+#endif
+
+public:
+	/**
+	 * Constructor to load a shared module.
+	 *
+	 * \param path the absolute path
+	 * \param policy the policy to load
+	 * \throw std::runtime_error on error
+	 */
+	inline Dynlib(const std::string &path, Policy policy = Immediately);
+
+	/**
+	 * Close the library automatically.
+	 */
+	inline ~Dynlib();
+
+	/**
+	 * Get a symbol from the library.
+	 *
+	 * On some platforms the symbol must be manually exported.
+	 *
+	 * \param name the symbol
+	 * \return the symbol
+	 * \throw std::runtime_error on error
+	 * \see DYNLIB_EXPORT
+	 */
+	template <typename T>
+	inline T sym(const std::string &name);
+};
+
+#if defined(_WIN32)
+
+/*
+ * Windows implementation
+ * ------------------------------------------------------------------
+ */
+
+Dynlib::Dynlib(const std::string &path, Policy)
+{
+	m_handle = LoadLibraryA(path.c_str());
+
+	if (m_handle == nullptr)
+		throw std::runtime_error(error());
+}
+
+Dynlib::~Dynlib()
+{
+	FreeLibrary(m_handle);
+	m_handle = nullptr;
+}
+
+template <typename T>
+T Dynlib::sym(const std::string &name)
+{
+	Sym sym = GetProcAddress(m_handle, name.c_str());
+
+	if (sym == nullptr)
+		throw std::runtime_error(error());
+
+	return reinterpret_cast<T>(sym);
+}
+
+#else
+
+/*
+ * Unix implementation
+ * ------------------------------------------------------------------
+ */
+
+Dynlib::Dynlib(const std::string &path, Policy policy)
+{
+	m_handle = dlopen(path.c_str(), policy == Immediately ? RTLD_NOW : RTLD_LAZY);
+
+	if (m_handle == nullptr)
+		throw std::runtime_error(dlerror());
+}
+
+Dynlib::~Dynlib()
+{
+	dlclose(m_handle);
+	m_handle = nullptr;
+}
+
+template <typename T>
+T Dynlib::sym(const std::string &name)
+{
+	Sym sym = dlsym(m_handle, name.c_str());
+
+	if (sym == nullptr)
+		throw std::runtime_error(dlerror());
+
+	return reinterpret_cast<T>(sym);
+}
+
+#endif
+
+#endif // !IRCCD_DYNLIB_HPP
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/plugin-dynlib.cpp	Wed May 18 22:31:24 2016 +0200
@@ -0,0 +1,216 @@
+/*
+ * plugin-dynlib.cpp -- native plugin implementation
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "plugin-dynlib.hpp"
+
+namespace irccd {
+
+namespace {
+
+template <typename Sym>
+inline Sym sym(Dynlib &dynlib, const std::string &name)
+{
+	try {
+		return dynlib.sym<Sym>(name);
+	} catch (...) {
+		return nullptr;
+	}
+}
+
+template <typename Sym, typename... Args>
+inline void call(Sym sym, Args&&... args)
+{
+	if (sym)
+		sym(std::forward<Args>(args)...);
+}
+
+} // !namespace
+
+DynlibPlugin::DynlibPlugin(std::string name, std::string path, PluginConfig config)
+	: Plugin(name, path, std::move(config))
+	, m_dso(std::move(path))
+	, m_onCommand(sym<OnCommand>(m_dso, "irccd_onCommand"))
+	, m_onConnect(sym<OnConnect>(m_dso, "irccd_onConnect"))
+	, m_onChannelMode(sym<OnChannelMode>(m_dso, "irccd_onChannelMode"))
+	, m_onChannelNotice(sym<OnChannelNotice>(m_dso, "irccd_onChannelNotice"))
+	, m_onInvite(sym<OnInvite>(m_dso, "irccd_onInvite"))
+	, m_onJoin(sym<OnJoin>(m_dso, "irccd_onJoin"))
+	, m_onKick(sym<OnKick>(m_dso, "irccd_onKick"))
+	, m_onLoad(sym<OnLoad>(m_dso, "irccd_onLoad"))
+	, m_onMessage(sym<OnMessage>(m_dso, "irccd_onMessage"))
+	, m_onMe(sym<OnMe>(m_dso, "irccd_onMe"))
+	, m_onMode(sym<OnMode>(m_dso, "irccd_onMode"))
+	, m_onNames(sym<OnNames>(m_dso, "irccd_onNames"))
+	, m_onNick(sym<OnNick>(m_dso, "irccd_onNick"))
+	, m_onNotice(sym<OnNotice>(m_dso, "irccd_onNotice"))
+	, m_onPart(sym<OnPart>(m_dso, "irccd_onPart"))
+	, m_onQuery(sym<OnQuery>(m_dso, "irccd_onQuery"))
+	, m_onQueryCommand(sym<OnQueryCommand>(m_dso, "irccd_onQueryCommand"))
+	, m_onReload(sym<OnReload>(m_dso, "irccd_onReload"))
+	, m_onTopic(sym<OnTopic>(m_dso, "irccd_onTopic"))
+	, m_onUnload(sym<OnUnload>(m_dso, "irccd_onUnload"))
+	, m_onWhois(sym<OnWhois>(m_dso, "irccd_onWhois"))
+{
+}
+
+void DynlibPlugin::onCommand(Irccd &irccd,
+			     const std::shared_ptr<Server> &server,
+			     const std::string &origin,
+			     const std::string &channel,
+			     const std::string &message)
+{
+	call(m_onCommand, irccd, server, origin, channel, message);
+}
+
+void DynlibPlugin::onConnect(Irccd &irccd, const std::shared_ptr<Server> &server)
+{
+	call(m_onConnect, irccd, server);
+}
+
+void DynlibPlugin::onChannelMode(Irccd &irccd,
+				 const std::shared_ptr<Server> &server,
+				 const std::string &origin,
+				 const std::string &channel,
+				 const std::string &mode,
+				 const std::string &arg)
+{
+	call(m_onChannelMode, irccd, server, origin, channel, mode, arg);
+}
+
+void DynlibPlugin::onChannelNotice(Irccd &irccd,
+				   const std::shared_ptr<Server> &server,
+				   const std::string &origin,
+				   const std::string &channel,
+				   const std::string &notice)
+{
+	call(m_onChannelNotice, irccd, server, origin, channel, notice);
+}
+
+void DynlibPlugin::onInvite(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel)
+{
+	call(m_onInvite, irccd, server, origin, channel);
+}
+
+void DynlibPlugin::onJoin(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel)
+{
+	call(m_onJoin, irccd, server, origin, channel);
+}
+
+void DynlibPlugin::onKick(Irccd &irccd,
+			  const std::shared_ptr<Server> &server,
+			  const std::string &origin,
+			  const std::string &channel,
+			  const std::string &target,
+			  const std::string &reason)
+{
+	call(m_onKick, irccd, server, origin, channel, target, reason);
+}
+
+void DynlibPlugin::onLoad(Irccd &irccd)
+{
+	call(m_onLoad, irccd, *this);
+}
+
+void DynlibPlugin::onMessage(Irccd &irccd,
+			     const std::shared_ptr<Server> &server,
+			     const std::string &origin,
+			     const std::string &channel,
+			     const std::string &message)
+{
+	call(m_onMessage, irccd, server, origin, channel, message);
+}
+
+void DynlibPlugin::onMe(Irccd &irccd,
+			const std::shared_ptr<Server> &server,
+			const std::string &origin,
+			const std::string &channel,
+			const std::string &message)
+{
+	call(m_onMe, irccd, server, origin, channel, message);
+}
+
+void DynlibPlugin::onMode(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &mode)
+{
+	call(m_onMode, irccd, server, origin, mode);
+}
+
+void DynlibPlugin::onNames(Irccd &irccd,
+			   const std::shared_ptr<Server> &server,
+			   const std::string &channel,
+			   const std::vector<std::string> &list)
+{
+	call(m_onNames, irccd, server, channel, list);
+}
+
+void DynlibPlugin::onNick(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &nick)
+{
+	call(m_onNick, irccd, server, origin, nick);
+}
+
+void DynlibPlugin::onNotice(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &notice)
+{
+	call(m_onNotice, irccd, server, origin, notice);
+}
+
+void DynlibPlugin::onPart(Irccd &irccd,
+			  const std::shared_ptr<Server> &server,
+			  const std::string &origin,
+			  const std::string &channel,
+			  const std::string &reason)
+{
+	call(m_onPart, irccd, server, origin, channel, reason);
+}
+
+void DynlibPlugin::onQuery(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message)
+{
+	call(m_onQuery, irccd, server, origin, message);
+}
+
+void DynlibPlugin::onQueryCommand(Irccd &irccd,
+				  const std::shared_ptr<Server> &server,
+				  const std::string &origin,
+				  const std::string &message)
+{
+	call(m_onQueryCommand, irccd, server, origin, message);
+}
+
+void DynlibPlugin::onReload(Irccd &irccd)
+{
+	call(m_onReload, irccd, *this);
+}
+
+void DynlibPlugin::onTopic(Irccd &irccd,
+			   const std::shared_ptr<Server> &server,
+			   const std::string &origin,
+			   const std::string &channel,
+			   const std::string &topic)
+{
+	call(m_onTopic, irccd, server, origin, channel, topic);
+}
+
+void DynlibPlugin::onUnload(Irccd &irccd)
+{
+	call(m_onUnload, irccd, *this);
+}
+
+void DynlibPlugin::onWhois(Irccd &irccd, const std::shared_ptr<Server> &server, const ServerWhois &info)
+{
+	call(m_onWhois, irccd, server, info);
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/plugin-dynlib.hpp	Wed May 18 22:31:24 2016 +0200
@@ -0,0 +1,241 @@
+/*
+ * plugin-dynlib.hpp -- native plugin implementation
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_PLUGIN_DYNLIB_HPP
+#define IRCCD_PLUGIN_DYNLIB_HPP
+
+/**
+ * \file plugin-dynlib.hpp
+ * \brief Native plugin implementation.
+ */
+
+#include "dynlib.hpp"
+#include "plugin.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Dynlib based plugin.
+ */
+class DynlibPlugin : public Plugin {
+private:
+	using OnCommand = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &);
+	using OnConnect = void (*)(Irccd &, const std::shared_ptr<Server> &);
+	using OnChannelMode = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &, const std::string &);
+	using OnChannelNotice = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &);
+	using OnInvite = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &);
+	using OnJoin = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &);
+	using OnKick = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &, const std::string &);
+	using OnLoad = void (*)(Irccd &, DynlibPlugin &);
+	using OnMessage = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &);
+	using OnMe = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &);
+	using OnMode = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &);
+	using OnNames = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::vector<std::string> &);
+	using OnNick = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &);
+	using OnNotice = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &);
+	using OnPart = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &);
+	using OnQuery = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &);
+	using OnQueryCommand = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &);
+	using OnReload = void (*)(Irccd &, DynlibPlugin &);
+	using OnTopic = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &);
+	using OnUnload = void (*)(Irccd &, DynlibPlugin &);
+	using OnWhois = void (*)(Irccd &, const std::shared_ptr<Server> &, const ServerWhois &);
+
+	Dynlib m_dso;
+	OnCommand m_onCommand;
+	OnConnect m_onConnect;
+	OnChannelMode m_onChannelMode;
+	OnChannelNotice m_onChannelNotice;
+	OnInvite m_onInvite;
+	OnJoin m_onJoin;
+	OnKick m_onKick;
+	OnLoad m_onLoad;
+	OnMessage m_onMessage;
+	OnMe m_onMe;
+	OnMode m_onMode;
+	OnNames m_onNames;
+	OnNick m_onNick;
+	OnNotice m_onNotice;
+	OnPart m_onPart;
+	OnQuery m_onQuery;
+	OnQueryCommand m_onQueryCommand;
+	OnReload m_onReload;
+	OnTopic m_onTopic;
+	OnUnload m_onUnload;
+	OnWhois m_onWhois;
+
+public:
+	/**
+	 * Construct the plugin.
+	 *
+	 * \param name the name
+	 * \param path the fully resolved path (must be absolute)
+	 * \param config the optional configuration
+	 * \throw std::exception on failures
+	 */
+	DynlibPlugin(std::string name, std::string path, PluginConfig config = PluginConfig());
+
+	/**
+	 * \copydoc Plugin::onCommand
+	 */
+	void onCommand(Irccd &irccd,
+		       const std::shared_ptr<Server> &server,
+		       const std::string &origin,
+		       const std::string &channel,
+		       const std::string &message) override;
+
+	/**
+	 * \copydoc Plugin::onConnect
+	 */
+	void onConnect(Irccd &irccd, const std::shared_ptr<Server> &server) override;
+
+	/**
+	 * \copydoc Plugin::onChannelMode
+	 */
+	void onChannelMode(Irccd &irccd,
+			   const std::shared_ptr<Server> &server,
+			   const std::string &origin,
+			   const std::string &channel,
+			   const std::string &mode,
+			   const std::string &arg) override;
+
+	/**
+	 * \copydoc Plugin::onChannelNotice
+	 */
+	void onChannelNotice(Irccd &irccd,
+			     const std::shared_ptr<Server> &server,
+			     const std::string &origin,
+			     const std::string &channel,
+			     const std::string &notice) override;
+
+	/**
+	 * \copydoc Plugin::onInvite
+	 */
+	void onInvite(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) override;
+
+	/**
+	 * \copydoc Plugin::onJoin
+	 */
+	void onJoin(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) override;
+
+	/**
+	 * \copydoc Plugin::onKick
+	 */
+	void onKick(Irccd &irccd,
+		    const std::shared_ptr<Server> &server,
+		    const std::string &origin,
+		    const std::string &channel,
+		    const std::string &target,
+		    const std::string &reason) override;
+
+	/**
+	 * \copydoc Plugin::onLoad
+	 */
+	void onLoad(Irccd &irccd) override;
+
+	/**
+	 * \copydoc Plugin::onMessage
+	 */
+	void onMessage(Irccd &irccd,
+		       const std::shared_ptr<Server> &server,
+		       const std::string &origin,
+		       const std::string &channel,
+		       const std::string &message) override;
+
+	/**
+	 * \copydoc Plugin::onMe
+	 */
+	void onMe(Irccd &irccd,
+		  const std::shared_ptr<Server> &server,
+		  const std::string &origin,
+		  const std::string &channel,
+		  const std::string &message) override;
+
+	/**
+	 * \copydoc Plugin::onMode
+	 */
+	void onMode(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &mode) override;
+
+	/**
+	 * \copydoc Plugin::onNames
+	 */
+	void onNames(Irccd &irccd,
+		     const std::shared_ptr<Server> &server,
+		     const std::string &channel,
+		     const std::vector<std::string> &list) override;
+
+	/**
+	 * \copydoc Plugin::onNick
+	 */
+	void onNick(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &nick) override;
+
+	/**
+	 * \copydoc Plugin::onNotice
+	 */
+	void onNotice(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &notice) override;
+
+	/**
+	 * \copydoc Plugin::onPart
+	 */
+	void onPart(Irccd &irccd,
+		    const std::shared_ptr<Server> &server,
+		    const std::string &origin,
+		    const std::string &channel,
+		    const std::string &reason) override;
+
+	/**
+	 * \copydoc Plugin::onQuery
+	 */
+	void onQuery(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message) override;
+
+	/**
+	 * \copydoc Plugin::onQueryCommand
+	 */
+	void onQueryCommand(Irccd &irccd,
+			    const std::shared_ptr<Server> &server,
+			    const std::string &origin,
+			    const std::string &message) override;
+
+	/**
+	 * \copydoc Plugin::onReload
+	 */
+	void onReload(Irccd &irccd) override;
+
+	/**
+	 * \copydoc Plugin::onTopic
+	 */
+	void onTopic(Irccd &irccd,
+		     const std::shared_ptr<Server> &server,
+		     const std::string &origin,
+		     const std::string &channel,
+		     const std::string &topic) override;
+
+	/**
+	 * \copydoc Plugin::onUnload
+	 */
+	void onUnload(Irccd &irccd) override;
+
+	/**
+	 * \copydoc Plugin::onWhois
+	 */
+	void onWhois(Irccd &irccd, const std::shared_ptr<Server> &server, const ServerWhois &info) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_DYNLIB_HPP
--- a/lib/irccd/service-plugin.cpp	Wed May 18 13:24:50 2016 +0200
+++ b/lib/irccd/service-plugin.cpp	Wed May 18 22:31:24 2016 +0200
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 #include <functional>
+#include <regex>
 #include <stdexcept>
 
 #include <format.h>
@@ -25,6 +26,7 @@
 #include "fs.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
+#include "plugin-dynlib.hpp"
 #include "plugin-js.hpp"
 #include "service-plugin.hpp"
 
@@ -32,6 +34,46 @@
 
 namespace irccd {
 
+namespace {
+
+std::shared_ptr<Plugin> find(std::unordered_map<std::string, PluginConfig> &configs, std::string name)
+{
+	PluginConfig config = configs[name];
+
+	for (const auto &path : path::list(path::PathPlugins)) {
+		std::string jspath = path + name + ".js";
+		std::string dynlibpath = path + name + DYNLIB_SUFFIX;
+
+		if (fs::isReadable(jspath))
+			return std::make_shared<JsPlugin>(std::move(name), std::move(jspath), std::move(config));
+		if (fs::isReadable(dynlibpath))
+			return std::make_shared<DynlibPlugin>(std::move(name), std::move(dynlibpath));
+	}
+
+	throw std::runtime_error("no suitable plugin found");
+}
+
+std::shared_ptr<Plugin> open(std::string name, std::string path)
+{
+	std::regex regex(".*(\\..*)$");
+	std::smatch match;
+	std::shared_ptr<Plugin> plugin;
+
+	if (std::regex_match(path, match, regex)) {
+		std::string extension = match[1];
+
+		if (extension == DYNLIB_SUFFIX)
+			plugin = std::make_shared<DynlibPlugin>(name, path);
+		else
+			plugin = std::make_shared<JsPlugin>(name, path);
+	} else
+		throw std::runtime_error("could not deduce plugin type from {}"_format(path));
+
+	return plugin;
+}
+
+} // !namespace
+
 PluginService::PluginService(Irccd &irccd) noexcept
 	: m_irccd(irccd)
 {
@@ -39,9 +81,8 @@
 
 PluginService::~PluginService()
 {
-	for (const auto &plugin : m_plugins) {
+	for (const auto &plugin : m_plugins)
 		plugin->onUnload(m_irccd);
-	}
 }
 
 bool PluginService::has(const std::string &name) const noexcept
@@ -57,9 +98,8 @@
 		return plugin->name() == name;
 	});
 
-	if (it == m_plugins.end()) {
+	if (it == m_plugins.end())
 		return nullptr;
-	}
 
 	return *it;
 }
@@ -68,9 +108,8 @@
 {
 	auto plugin = get(name);
 
-	if (!plugin) {
+	if (!plugin)
 		throw std::invalid_argument("plugin {} not found"_format(name));
-	}
 
 	return plugin;
 }
@@ -80,25 +119,6 @@
 	m_plugins.push_back(std::move(plugin));
 }
 
-std::shared_ptr<Plugin> PluginService::find(std::string name)
-{
-	PluginConfig config = m_config[name];
-
-	for (const auto &path : path::list(path::PathPlugins)) {
-		std::string fullpath = path + name + ".js";
-
-		if (!fs::isReadable(fullpath)) {
-			continue;
-		}
-
-		log::info("plugin {}: trying {}"_format(name, fullpath));
-
-		return std::make_shared<JsPlugin>(std::move(name), std::move(fullpath), std::move(config));
-	}
-
-	throw std::runtime_error("no suitable plugin found");
-}
-
 void PluginService::configure(const std::string &name, PluginConfig config)
 {
 	m_config.emplace(name, std::move(config));
@@ -108,9 +128,8 @@
 {
 	auto it = m_config.find(name);
 
-	if (it != m_config.end()) {
+	if (it != m_config.end())
 		return it->second;
-	}
 
 	return PluginConfig();
 }
@@ -121,19 +140,16 @@
 		return plugin->name() == name;
 	});
 
-	if (it != m_plugins.end()) {
+	if (it != m_plugins.end())
 		return;
-	}
 
 	try {
 		std::shared_ptr<Plugin> plugin;
 
-		if (path.empty()) {
-			plugin = find(std::move(name));
-		} else {
-			// TODO: DYNLIB BASED PLUGINS
-			plugin = std::make_shared<JsPlugin>(std::move(name), std::move(path));
-		}
+		if (path.empty())
+			plugin = find(m_config, std::move(name));
+		else
+			plugin = open(std::move(name), std::move(path));
 
 		plugin->onLoad(m_irccd);
 		add(std::move(plugin));
@@ -146,9 +162,8 @@
 {
 	auto plugin = get(name);
 
-	if (plugin) {
+	if (plugin)
 		plugin->onReload(m_irccd);
-	}
 }
 
 void PluginService::unload(const std::string &name)
--- a/lib/irccd/service-plugin.hpp	Wed May 18 13:24:50 2016 +0200
+++ b/lib/irccd/service-plugin.hpp	Wed May 18 22:31:24 2016 +0200
@@ -101,15 +101,6 @@
 	void add(std::shared_ptr<Plugin> plugin);
 
 	/**
-	 * Search a plugin through the predefined directories.
-	 *
-	 * \param name the plugin name (without any extension)
-	 * \return the plugin if found
-	 * \throw std::exception on any errors
-	 */
-	std::shared_ptr<Plugin> find(std::string name);
-
-	/**
 	 * Configure a plugin.
 	 *
 	 * If the plugin is already loaded, its configuration is updated.