我已经阅读过Haskell中的插件,但是我无法满足自己的目的(最好是在生产环境中使用).
我的插件系统目标是:
一个最小的例子可能是:
app/service~plugins界面
module SharedTypes (PluginInterface (..)) where
data PluginInterface =
PluginInterface { pluginName :: String
, runPlugin :: Int -> Int }
Run Code Online (Sandbox Code Playgroud)
一些插件列表
-- ~/plugins/plugin{Nth}.?? (with N=1..)
module Plugin{Nth}( getPlugin ) where
import SharedTypes
getPlugin :: PluginInterface
getPlugin = PluginInterface "{Nth}th plugin" $ \x -> {Nth} * x
Run Code Online (Sandbox Code Playgroud)
应用/服务
...
loadPlugins :: FilePath -> IO [PluginInterface]
loadPlugins = undefined
...
Run Code Online (Sandbox Code Playgroud)
我认为使用动态编译链接库(将每个编译Plugin{Nth}
为共享库)可以工作(作为FFI)但是
getPlugin
功能点)谢谢!
UPDATE
在伟大的@xnyhps回答之后,一个完整的运行示例使用 ghc 7.10
SharedTypes.hs
module SharedTypes (
PluginInterface (..)
) where
data PluginInterface =
PluginInterface { pluginName :: String
, runPlugin :: Int -> Int
}
Run Code Online (Sandbox Code Playgroud)
Plugin1.hs
module Plugin1 (
getPlugin
) where
import SharedTypes
getPlugin :: PluginInterface
getPlugin = PluginInterface "Plugin1" $ \x -> 1 * x
Run Code Online (Sandbox Code Playgroud)
Plugin2.hs
module Plugin2 (
getPlugin
) where
import SharedTypes
getPlugin :: PluginInterface
getPlugin = PluginInterface "Plugin2" $ \x -> 2 * x
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
编译和执行
[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
Run Code Online (Sandbox Code Playgroud)
有一个动态加载程序包,允许您将额外的目标文件或共享库加载到您的进程中.(Hackage上的版本不适用于7.10,但GitHub上的当前版本确实如此.)
有了这个,你可以这样做:
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
Run Code Online (Sandbox Code Playgroud)
但是,您必须记住整个过程非常不安全:如果您更改PluginInterface
数据类型并尝试加载使用旧版本编译的插件,您的应用程序将崩溃.你必须希望getPlugin
函数有类型PluginInterface
,没有检查.最后,如果插件来自不受信任的源,它可以执行任何操作,即使您尝试调用的函数在Haskel中应该是纯粹的.