Hello folks,
I have been toying with dynamic libraries, trying to implement plugin
functionality. I was able to get to the point where I can call simple
function in loaded library, but I am having troubles starting more
sophisticated communication channel.
There are 3 projects
- PluginConsumer is an app that loads plugin libraries
- MyPlugin is a plugin implementation, output is dynamic library that
PluginConsumer loads
- PluginInterface is common interface that both MyPlugin and PluginConsumer
use, so that they know how to communicate
My first idea was to have PluginInterface be a simple SPM project with
single file where the bare-bones PluginInterface class would be:
open class PluginInterface {
open func sayHi()
}
Package.swift file:
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "PluginInterface",
products: [ .library(name: "PluginInterface", type: .dynamic, targets: [
"PluginInterface"]) ],
targets: [ .target(name: "PluginInterface") ]
)
UserPlugin is also very simple project containing only one file:
public func getPlugin() -> AnyObject {
return MyPlugin()
}
class MyPlugin: PluginInterface {
override func sayHi() {
print("Hi from my plugin")
}
}
Package.swift:
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "MyPlugin",
products: [ .library(name: "MyPlugin", type: .dynamic, targets: [
"MyPlugin"]) ],
dependencies: [ .package(url: "url_to_PluginInterface", from: "0.0.0"),
],
targets: [
.target(name: "PluginInterface", dependencies: ["PluginInterface"]),
.target(name: "MyPlugin", dependencies: ["PluginInterface"]),
]
)
The PluginConsumer is bit more complicated, but here is relevant part (lib
loading and function calling):
typealias InitFunction = @convention(c) () -> AnyObject
let openRes = dlopen(pathToLib, RTLD_NOW|RTLD_LOCAL)
if openRes != nil {
defer {
dlclose(openRes)
}
let symbolName = "mangled_symbol_name"
let sym = dlsym(openRes, symbolName)
if sym != nil {
let f: InitFunction = unsafeBitCast(sym, to: InitFunction.self)
let plugin = f() as? PluginInterface
}
}
Package.swift file:
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "PluginConsumer",
dependencies: [ .package(url: "path_to_plugin_interface", from: "0.0.0"
) ],
targets: [ .target(name: "PluginConsumer", dependencies: [
"PluginConsumer"]) ]
)
This all compiles nicely, MyPlugin project creates dylib file that
executable created by PluginConsumer can load, but the problem is with
following line:
let plugin = f() as? PluginInterface
Type of the plugin is MyPlugin, but from the consumer's view, it doesn't
inherit from PluginInterface so I can't call sayHi() method. I assume this
is because there is no relation between PluginInterface class that compiler
uses for MyPlugin project one that it uses for PluginConsumer project.
After library is loaded, they are two completely different classes that
happen to share same name. Is my assumption correct and how do I go about
fixing it?
I had an idea I could make PluginInterface emit dynamic library that would
be dynamically linked by both MyPlugin and PluginConsumer, thus making them
share same PluginInterface class, but I can't figure out how to do that (or
if it's right way of doing this).
Any help appreciated :)
Lope