[swift-users] Communicating with dynamically loaded swift library
Ján Kosa
lopenka at gmail.com
Fri Oct 6 07:41:22 CDT 2017
It worked! Took me a while to iron out details, but it is working now. Huge
thanks sir, I will name my firstborn after you.
Thanks for the @_cdecl("initializePlugin") tip as well, I didn't know about
it and it will be very useful.
I am having slightly related problem now (it was there before, but I
ignored it for the time being), not sure if I should start a new thread?
The PluginInterface module has one external dependency on module A,
PluginConsumer has the dependency on module B which has dependency on same
module A that the PluginInterface uses. When I load the plugin library, I
get bunch of errors like:
Class A.XYZ is implemented in both libPluginInterface.dylib and
libMyPlugin.dylib
I know why it is there, but I don't know how to get rid of it. I can't just
remove dependency from PluginConsumer and use the one from PluginInterface
(if that would even work?) because PluginConsumer does not depend on it
directly, but it is going through module B first
Cheers,
Lope
On 4 October 2017 at 22:17, Daniel Dunbar <daniel_dunbar at apple.com> wrote:
> The way that I have done this in the past is pass a protocol as an unsafe
> pointer to an exposed entry point:
> ```swift
> let entryPoint = dlsym(handle, “initializePlugin”)
> guard entryPoint != nil else {
> fatalError("missing plugin entry point: \(pluginPath)")
> }
> typealias PluginInitializationFunc = @convention(c)
> (UnsafeRawPointer) -> ()
> let f = unsafeBitCast(entryPoint, to:
> PluginInitializationFunc.self)
> f(Unmanaged.passUnretained(self).toOpaque())
> ```
>
> and then in the plugin convert back to the appropriate type:
>
> ```
> @_cdecl("initializePlugin")
> public func initializePlugin(_ ptr: UnsafeRawPointer) {
> let manager = Unmanaged<PluginManager>.fromOpaque(ptr).
> takeUnretainedValue()
> ```
>
> HTH,
> - Daniel
>
> On Oct 4, 2017, at 11:02 AM, Ján Kosa via swift-users <
> swift-users at swift.org> wrote:
>
> 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
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20171006/42bbc1b7/attachment.html>
More information about the swift-users
mailing list