我发现article描述了如何使用Swift和Cocoa创建插件。它使用NSBundle加载插件,但据我所知,这不是纯swift(没有Cocoa)。有没有办法如何在不使用Cocoa的情况下实现相同的结果?
更多信息:
如果相关,这就是我想要实现的目标。我在swift中创建运行在linux服务器上的app。用户可以使用他们的浏览器连接到它。我希望能够让其他人编写“插件”来实现功能本身(用户在连接后可以看到和做什么),从打印出来的hello world,聊天程序到游戏,而不必担心提供低级别的东西通过我的应用程序某种dll,我的服务器应用程序加载并运行。
答案 0 :(得分:3)
解决这个问题并非易事,但要做到这一点并非不可能。我更喜欢使用swift包管理器来管理依赖项,使用Xcode作为IDE。这种组合并不完美,因为它需要大量的修补,但目前还没有任何其他可用的免费swift IDE。
您需要设置两个项目,我们称之为插件(第三方库)和 PluginConsumer (使用其他人插件的应用)。您还需要决定API,现在我们将使用简单的
TestPluginFunc()
在插件项目中使用TestPluginFunc实现创建Plugin.swift文件:
public func TestPluginFunc() {
print("Hooray!")
}
将项目设置为构建框架,而不是可执行文件并构建 [1] 。您将获得包含插件的Plugin.framework文件。
现在切换到 PluginConsumer 项目
将插件项目中的Plugin.framework复制到您可以轻松找到的位置。要实际加载框架并使用它:
// we need to define how our plugin function looks like
typealias TestPluginFunc = @convention(c) ()->()
// and what is its name
let pluginFuncName = "TestPluginFunc"
func loadPlugin() {
let pluginName = "Plugin"
let openRes = dlopen("./\(pluginName).framework/\(pluginName)", RTLD_NOW|RTLD_LOCAL)
if openRes != nil {
// this is fragile
let symbolName = "_TF\(pluginName.utf8.count)\(pluginName)\(initFuncName.utf8.count)\(initFuncName)FT_T_"
let sym = dlsym(openRes, symbolName)
if sym != nil {
// here we load func from framework based on the name we constructed in "symbolName" variable
let f: TestPluginFunc = unsafeBitCast(sym, to: TestPluginFunc.self)
// and now all we need to do is execute our plugin function
f()
} else {
print("Error loading \(realPath). Symbol \(symbolName) not found.")
dlclose(openRes)
}
} else {
print("error opening lib")
}
}
如果操作正确,你应该看到“万岁!”被打印到你的日志。
还有很大的改进空间,首先应该做的是用参数替换Plugin.framework字符串,最好使用一些文件库(我使用的是PerfectLib)。另一件需要注意的事情是将PluginConsumer项目中的插件API定义为协议或基类,从中创建框架,在插件项目中导入该框架并将实现基于该协议/基类。我想知道如何做到这一点。如果我能够正确地做到这一点,我会更新这篇文章。
[1]:我通常通过创建Package.swift文件并使用swift package generate-xcodeproj
创建xcode项目来完成此操作。如果您的项目不包含main.swift,xcode将创建框架而不是可执行文件
答案 1 :(得分:0)
你想要做的是创建一个程序将要查看的文件夹。假设它叫做'plugins'。它应该从那里的文件中创建一个名称列表,然后迭代使用它们,将参数传递给文件并获取输出并以某种方式使用它。
Activating a program and getting output:
func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {
var output : [String] = []
var error : [String] = []
let task = Process()
task.launchPath = cmd
task.arguments = args
let outpipe = Pipe()
task.standardOutput = outpipe
let errpipe = Pipe()
task.standardError = errpipe
task.launch()
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String(data: outdata, encoding: .utf8) {
string = string.trimmingCharacters(in: .newlines)
output = string.components(separatedBy: "\n")
}
let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String(data: errdata, encoding: .utf8) {
string = string.trimmingCharacters(in: .newlines)
error = string.components(separatedBy: "\n")
}
task.waitUntilExit()
let status = task.terminationStatus
return (output, error, status)
}
`
以下是swift插件如何接受参数:
for i in 1..C_ARGC {
let index = Int(i);
let arg = String.fromCString(C_ARGV[index])
switch arg {
case 1:
println("1");
case 2:
println("2")
default:
println("3)
}
}
因此,一旦你有程序和插件进行通信,你只需要根据输出在程序中添加处理,这样插件输出就可以做一些有意义的事情。没有可可库,这似乎是要走的路,但如果你使用C,那里还有其他几个选项。希望这可以帮助。