开箱即用的Haskell插件系统

时间:2015-06-06 10:53:01

标签: haskell

我已经阅读过Haskell中的插件,但我无法满足自己的目的(理想情况下是在生产环境中使用)。

我的插件系统目标是:

  1. 生产环境必须开箱即用(全部预编译)。
  2. 加载插件已启用重置应用/服务,但理想情况下它会动态加载和更新插件。
  3. 一个最小的例子可能是:

    app / service~plugins接口

    module SharedTypes (PluginInterface (..)) where
    
    data PluginInterface =
         PluginInterface { pluginName :: String
                         , runPlugin  :: Int -> Int }
    

    某些插件列表

    -- ~/plugins/plugin{Nth}.??   (with N=1..)
    module Plugin{Nth}( getPlugin ) where
    
    import SharedTypes
    
    getPlugin :: PluginInterface
    getPlugin = PluginInterface "{Nth}th plugin" $ \x -> {Nth} * x
    

    应用/服务

    ...
    loadPlugins :: FilePath -> IO [PluginInterface]
    loadPlugins = undefined
    ...
    

    我认为使用动态编译链接库(将每个Plugin{Nth}编译为共享库)可以工作(作为FFI),但

    1. 如何在运行时枚举和加载每个共享库?(获取每个getPlugin功能点)
    2. 存在一些更好的方法? (例如,在运行应用程序/服务之前的一些“magic”进程)
    3. 谢谢!

      更新

      完整运行示例

      按照伟大的@xnyhps回答,使用ghc 7.10

      的完整运行示例

      SharedTypes.hs

      module SharedTypes (
        PluginInterface (..)
      ) where
      
      data PluginInterface =
           PluginInterface { pluginName :: String
                           , runPlugin  :: Int -> Int
                           }
      

      Plugin1.hs

      module Plugin1 (
        getPlugin
      ) where
      
      import SharedTypes
      
      getPlugin :: PluginInterface
      getPlugin = PluginInterface "Plugin1" $ \x -> 1 * x
      

      Plugin2.hs

      module Plugin2 (
        getPlugin
      ) where
      
      import SharedTypes
      
      getPlugin :: PluginInterface
      getPlugin = PluginInterface "Plugin2" $ \x -> 2 * x
      

      app.hs

      import SharedTypes
      import System.Plugins.DynamicLoader
      import System.Directory
      import Data.Maybe
      import Control.Applicative
      import Data.List
      import System.FilePath
      import Control.Monad
      
      loadPlugins :: FilePath -> IO [PluginInterface]
      loadPlugins path = getDirectoryContents path >>= mapM loadPlugin . filter (".plugin" `isSuffixOf`)
        where loadPlugin file = do
                m <- loadModuleFromPath (combine path file)  -- absolute path
                                        (Just path)          -- base of qualified name (or you'll get not found)
                resolveFunctions
                getPlugin <- loadFunction m "getPlugin"
                return getPlugin
      
      main = do
      
        -- and others used by plugins
        addDLL "/usr/lib/ghc-7.10.1/base_I5BErHzyOm07EBNpKBEeUv/libHSbase-4.8.0.0-I5BErHzyOm07EBNpKBEeUv-ghc7.10.1.so"
        loadModuleFromPath "/srv/despierto/home/josejuan/Projects/Solveet/PluginSystem/SharedTypes.o" Nothing
      
        plugins <- loadPlugins "/srv/despierto/home/josejuan/Projects/Solveet/PluginSystem/plugins"
      
        forM_ plugins $ \plugin -> do
          putStrLn $ "Plugin name: " ++ pluginName plugin
          putStrLn $ "     Run := " ++ show (runPlugin plugin 34)
      

      编制和执行

      [josejuan@centella PluginSystem]$ ghc --make -dynamic -fPIC -O3 Plugin1.hs
      [1 of 2] Compiling SharedTypes      ( SharedTypes.hs, SharedTypes.o )
      [2 of 2] Compiling Plugin1          ( Plugin1.hs, Plugin1.o )
      [josejuan@centella PluginSystem]$ ghc --make -dynamic -fPIC -O3 Plugin2.hs
      [2 of 2] Compiling Plugin2          ( Plugin2.hs, Plugin2.o )
      [josejuan@centella PluginSystem]$ mv Plugin1.o plugins/Plugin1.plugin
      [josejuan@centella PluginSystem]$ mv Plugin2.o plugins/Plugin2.plugin
      [josejuan@centella PluginSystem]$ ghc --make -dynamic -fPIC -O3 app.hs
      [2 of 2] Compiling Main             ( app.hs, app.o )
      Linking app ...
      [josejuan@centella PluginSystem]$ ./app
      Plugin name: Plugin1
           Run := 34
      Plugin name: Plugin2
           Run := 68
      

1 个答案:

答案 0 :(得分:5)

the dynamic-loader package,允许您将额外的对象文件或共享库加载到您的进程中。 (Hackage上的版本不适用于7.10,但是当前version on GitHub does。)

有了这个,你可以这样做:

import System.Plugins.DynamicLoader
import System.Directory

loadPlugins :: FilePath -> IO [PluginInterface]
loadPlugins path = do
    files <- getDirectoryContents path
    mapM (\plugin_path -> do
        m <- loadModuleFromPath (path ++ "/" ++ plugin_path) (Just path)
        resolveFunctions
        plugin <- loadFunction m "getPlugin"
        return plugin) files

但是,您必须记住整个过程非常不安全:如果您更改了PluginInterface数据类型并尝试加载使用旧版本编译的插件,应用程序将崩溃。你必须希望getPlugin函数有类型PluginInterface,没有检查。最后,如果插件来自不受信任的源,它可以执行任何操作,即使您尝试调用的函数在Haskel中应该是纯粹的。