Adopt renderer/context in dynamically loaded (at runtime) library

I’m trying to create a cinder block for creating a plugin system for your cinder app that loads plugins dynamically at runtime. So far it seems works to work UNTIL the plugin starts calling cinder::gl::draw* methods to do visual stuff, at which point the plugin crashes the host app, because it didn’t setup its own render context properly.

Is it possible for the dynamically loaded pre-build plugin to adopt the host-app’s renderer/context/whatever-is-required?

So far I’ve tried passing on the app’s renderer (cinder::app::App::get()->getRenderer()) and calling makeCurrentContext() on it in the plugin, as well as passing on the gl::Context (cinder::gl::Context::getCurrent()) and calling makeCurrent on it in the plugin, but both result in crashes because of what seems like uninitialized parts in the cinder::gl::Environment class.

Any pointers are welcome! Thanks.

Try not to call makeCurrent on non-main thread since it’s dangerous.
dispatchAsync() will save you.

//! Executes a std::function on the App's primary thread ahead of the next update()
void	dispatchAsync( const std::function<void()> &fn );

That won’t work, Vinjin, as you would not actually use a second context at all. You would make it current on the main thread, instead of on your separate thread.

Thanks for the feedback. Threads don’t seem to be the issue, though I’ve double checked by both making sure the plugins get loaded inside the update function and by trying vinjn’s suggested dispatchAsync to schedule the imports. Both result in the same crashes.

These are my crash stacktraces (getPluginInterface is the function exported by the plugin);

When calling Context::makeCurrent in the plugin

* thread #1: tid = 0x9afe2, 0x00007fffdea1add6 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007fffdea1add6 libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00000001005eba1c libsystem_pthread.dylib`pthread_kill + 90
    frame #2: 0x00007fffde980420 libsystem_c.dylib`abort + 129
    frame #3: 0x00007fffde947893 libsystem_c.dylib`__assert_rtn + 320
  * frame #4: 0x000000010dd4967d libdrawablePlugin-01.dylib`cinder::gl::env() + 61 at Environment.cpp:81
    frame #5: 0x000000010da92ae1 libdrawablePlugin-01.dylib`cinder::gl::Context::makeCurrent(this=0x00000001010060d0, force=false) const + 129 at Context.cpp:191
    frame #6: 0x000000010d99e7c0 libdrawablePlugin-01.dylib`getPluginInterface + 64
    frame #7: 0x00000001000033bf drawableApp`CinderPlugin::PluginManager<Drawable>::loadFile(this=0x00000001010034f0, path=<unavailable>) + 1967 at PluginManager.h:53
    frame #8: 0x0000000100001c5f drawableApp`drawableApp::update(this=0x00000001010032c0) + 271 at drawableApp.cpp:38
    frame #9: 0x0000000100030226 drawableApp`cinder::app::AppBase::privateUpdate__(this=0x00000001010032c0) + 358 at AppBase.cpp:232
    frame #10: 0x0000000100031624 drawableApp`-[AppImplMac timerFired:](self=0x0000610000062c00, _cmd="timerFired:", t=0x0000618000163b40) + 100 at AppImplMac.mm:152
    frame #11: 0x00007fffcada1f7f Foundation`__NSFireTimer + 83
    frame #12: 0x00007fffc92f8294 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    frame #13: 0x00007fffc92f7f23 CoreFoundation`__CFRunLoopDoTimer + 1075
    frame #14: 0x00007fffc92f7a7a CoreFoundation`__CFRunLoopDoTimers + 298
    frame #15: 0x00007fffc92ef5d1 CoreFoundation`__CFRunLoopRun + 2081
    frame #16: 0x00007fffc92eeb54 CoreFoundation`CFRunLoopRunSpecific + 420
    frame #17: 0x00007fffc8879acc HIToolbox`RunCurrentEventLoopInMode + 240
    frame #18: 0x00007fffc8879901 HIToolbox`ReceiveNextEventCommon + 432
    frame #19: 0x00007fffc8879736 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 71
    frame #20: 0x00007fffc6e1fae4 AppKit`_DPSNextEvent + 1120
    frame #21: 0x00007fffc759a21f AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 2789
    frame #22: 0x00007fffc6e14465 AppKit`-[NSApplication run] + 926
    frame #23: 0x00000001000c9599 drawableApp`cinder::app::AppMac::launch(this=0x00000001010032c0) + 57 at AppMac.cpp:67
    frame #24: 0x000000010002fc99 drawableApp`cinder::app::AppBase::executeLaunch(this=0x00000001010032c0) + 73 at AppBase.cpp:190
    frame #25: 0x0000000100004114 drawableApp`void cinder::app::AppMac::main<drawableApp>(defaultRenderer=ptr = 0x610000120000 strong=3 weak=1, title="drawableApp", argc=3, argv=0x00007fff5fbff850, settingsFn=0x00007fff5fbff7f0)> const&) + 292 at AppMac.h:94
    frame #26: 0x0000000100002462 drawableApp`main(argc=3, argv=0x00007fff5fbff850) + 194 at drawableApp.cpp:72
    frame #27: 0x00007fffde8ec255 libdyld.dylib`start + 1

When calling Renderer::makeCurrentContext

* thread #1: tid = 0x9b5c8, 0x00007fffdd501a59 libc++abi.dylib`__dynamic_cast + 38, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x00007fffdd501a59 libc++abi.dylib`__dynamic_cast + 38
  * frame #1: 0x00000001000a3aeb drawableApp`cinder::gl::Environment::makeContextCurrent(cinder::gl::Context const*) [inlined] std::__1::enable_if<(!(is_array<cinder::gl::PlatformDataMac>::value)) && (!(is_array<cinder::gl::Context::PlatformData>::value)), std::__1::shared_ptr<cinder::gl::PlatformDataMac> >::type std::__1::dynamic_pointer_cast<cinder::gl::PlatformDataMac, cinder::gl::Context::PlatformData>(__r=0x00007fff5fbfc6d0) + 82 at memory:5006
    frame #2: 0x00000001000a3a99 drawableApp`cinder::gl::Environment::makeContextCurrent(this=0x0000610000000880, context=0x0000000101016e10) + 57 at Environment.cpp:215
    frame #3: 0x0000000100060c2d drawableApp`cinder::gl::Context::makeCurrent(this=0x0000000101016e10, force=false) const + 141 at Context.cpp:191
    frame #4: 0x00000001000d76bd drawableApp`-[RendererImplGlMac makeCurrentContext:](self=0x00006080000428b0, _cmd="makeCurrentContext:", force=false) + 61 at RendererImplGlMac.mm:131
    frame #5: 0x00000001000aeef3 drawableApp`cinder::app::RendererGl::startDraw(this=0x0000600000120dc0) + 99 at RendererGl.cpp:87
    frame #6: 0x0000000100086dcb drawableApp`-[CinderViewMac drawRect:](self=0x00006080001e0000, _cmd="drawRect:", rect=(origin = (x = 0, y = 0), size = (width = 640, height = 480))) + 187 at CinderViewMac.mm:184
    frame #7: 0x0000000100086cb3 drawableApp`-[CinderViewMac draw](self=0x00006080001e0000, _cmd="draw") + 179 at CinderViewMac.mm:169
    frame #8: 0x0000000100031956 drawableApp`-[AppImplMac timerFired:](self=0x00006000000661c0, _cmd="timerFired:", t=0x0000618000161e00) + 918 at AppImplMac.mm:165
    frame #9: 0x00007fffcada1f7f Foundation`__NSFireTimer + 83
    frame #10: 0x00007fffc92f8294 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    frame #11: 0x00007fffc92f7f23 CoreFoundation`__CFRunLoopDoTimer + 1075
    frame #12: 0x00007fffc92f7a7a CoreFoundation`__CFRunLoopDoTimers + 298
    frame #13: 0x00007fffc92ef5d1 CoreFoundation`__CFRunLoopRun + 2081
    frame #14: 0x00007fffc92eeb54 CoreFoundation`CFRunLoopRunSpecific + 420
    frame #15: 0x00007fffc8879acc HIToolbox`RunCurrentEventLoopInMode + 240
    frame #16: 0x00007fffc8879901 HIToolbox`ReceiveNextEventCommon + 432
    frame #17: 0x00007fffc8879736 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 71
    frame #18: 0x00007fffc6e1fae4 AppKit`_DPSNextEvent + 1120
    frame #19: 0x00007fffc759a21f AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 2789
    frame #20: 0x00007fffc6e14465 AppKit`-[NSApplication run] + 926
    frame #21: 0x00000001000c9599 drawableApp`cinder::app::AppMac::launch(this=0x0000000101600660) + 57 at AppMac.cpp:67
    frame #22: 0x000000010002fc99 drawableApp`cinder::app::AppBase::executeLaunch(this=0x0000000101600660) + 73 at AppBase.cpp:190
    frame #23: 0x0000000100004114 drawableApp`void cinder::app::AppMac::main<drawableApp>(defaultRenderer=ptr = 0x600000120000 strong=3 weak=1, title="drawableApp", argc=3, argv=0x00007fff5fbff850, settingsFn=0x00007fff5fbff7f0)> const&) + 292 at AppMac.h:94
    frame #24: 0x0000000100002462 drawableApp`main(argc=3, argv=0x00007fff5fbff850) + 194 at drawableApp.cpp:72
    frame #25: 0x00007fffde8ec255 libdyld.dylib`start + 1

When not trying to adopt context and plugin just starts drawing

* thread #1: tid = 0x9b97c, 0x00007fffdd501a59 libc++abi.dylib`__dynamic_cast + 38, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x00007fffdd501a59 libc++abi.dylib`__dynamic_cast + 38
  * frame #1: 0x00000001000a3aeb drawableApp`cinder::gl::Environment::makeContextCurrent(cinder::gl::Context const*) [inlined] std::__1::enable_if<(!(is_array<cinder::gl::PlatformDataMac>::value)) && (!(is_array<cinder::gl::Context::PlatformData>::value)), std::__1::shared_ptr<cinder::gl::PlatformDataMac> >::type std::__1::dynamic_pointer_cast<cinder::gl::PlatformDataMac, cinder::gl::Context::PlatformData>(__r=0x00007fff5fbfc6d0) + 82 at memory:5006
    frame #2: 0x00000001000a3a99 drawableApp`cinder::gl::Environment::makeContextCurrent(this=0x00006000000005e0, context=0x000000010110b370) + 57 at Environment.cpp:215
    frame #3: 0x0000000100060c2d drawableApp`cinder::gl::Context::makeCurrent(this=0x000000010110b370, force=false) const + 141 at Context.cpp:191
    frame #4: 0x00000001000d76bd drawableApp`-[RendererImplGlMac makeCurrentContext:](self=0x0000608000044ef0, _cmd="makeCurrentContext:", force=false) + 61 at RendererImplGlMac.mm:131
    frame #5: 0x00000001000aeef3 drawableApp`cinder::app::RendererGl::startDraw(this=0x0000608000120320) + 99 at RendererGl.cpp:87
    frame #6: 0x0000000100086dcb drawableApp`-[CinderViewMac drawRect:](self=0x00006080001e0100, _cmd="drawRect:", rect=(origin = (x = 0, y = 0), size = (width = 640, height = 480))) + 187 at CinderViewMac.mm:184
    frame #7: 0x0000000100086cb3 drawableApp`-[CinderViewMac draw](self=0x00006080001e0100, _cmd="draw") + 179 at CinderViewMac.mm:169
    frame #8: 0x0000000100031956 drawableApp`-[AppImplMac timerFired:](self=0x0000608000062bc0, _cmd="timerFired:", t=0x0000608000162d00) + 918 at AppImplMac.mm:165
    frame #9: 0x00007fffcada1f7f Foundation`__NSFireTimer + 83
    frame #10: 0x00007fffc92f8294 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    frame #11: 0x00007fffc92f7f23 CoreFoundation`__CFRunLoopDoTimer + 1075
    frame #12: 0x00007fffc92f7a7a CoreFoundation`__CFRunLoopDoTimers + 298
    frame #13: 0x00007fffc92ef5d1 CoreFoundation`__CFRunLoopRun + 2081
    frame #14: 0x00007fffc92eeb54 CoreFoundation`CFRunLoopRunSpecific + 420
    frame #15: 0x00007fffc8879acc HIToolbox`RunCurrentEventLoopInMode + 240
    frame #16: 0x00007fffc8879901 HIToolbox`ReceiveNextEventCommon + 432
    frame #17: 0x00007fffc8879736 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 71
    frame #18: 0x00007fffc6e1fae4 AppKit`_DPSNextEvent + 1120
    frame #19: 0x00007fffc759a21f AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 2789
    frame #20: 0x00007fffc6e14465 AppKit`-[NSApplication run] + 926
    frame #21: 0x00000001000c9599 drawableApp`cinder::app::AppMac::launch(this=0x0000000100701870) + 57 at AppMac.cpp:67
    frame #22: 0x000000010002fc99 drawableApp`cinder::app::AppBase::executeLaunch(this=0x0000000100701870) + 73 at AppBase.cpp:190
    frame #23: 0x0000000100004114 drawableApp`void cinder::app::AppMac::main<drawableApp>(defaultRenderer=ptr = 0x618000120000 strong=3 weak=1, title="drawableApp", argc=3, argv=0x00007fff5fbff850, settingsFn=0x00007fff5fbff7f0)> const&) + 292 at AppMac.h:94
    frame #24: 0x0000000100002462 drawableApp`main(argc=3, argv=0x00007fff5fbff850) + 194 at drawableApp.cpp:72
    frame #25: 0x00007fffde8ec255 libdyld.dylib`start + 1

Wait, when using dispatchAsync, there is no need to call makeCurrent in the plugin side.
These codes are running in app’s main thread / gl context.

Ah, right. Unfortunately it still doesn’t work

My example plugin basically exports a single class with only a draw method and a single line of functional code, which is;

cinder::gl::drawSolidCircle(vec2(0.0f,0.0f), 100.0f);

This draw method (override of the base class’ virtual draw method) gets invoked from within the host app’s draw method, so that’s already on the main thread (again, just to be sure, I did try if dispatchAsync could make a difference here, but it didn’t).

Something else I probably should have mentioned earlier is that I get the below warnings in my stdout as soon as the dylib is loaded. Currently both the host app and the plugin link against the cinder library, so both basically contain a copy of cinder (I haven’t found a way around this yet). As the warnings state, it’s unknown which one will be used, but my guess is that code inside the plugin will use the cinder implementation from the plugin.

This shouldn’t be a problem per se, since I’m currently building both the host app and the plugin against the exact same version of cinder, but my guess is that both copies of cinder keep their own versions of some static global variables. That’s what I hoped to resolve with some function like Context::makeCurrent.

objc[24956]: Class CaptureImplAvFoundation is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x100229380) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130a30). One of the two will be used. Which one is undefined.
objc[24956]: Class CocoaRendererQuartzView is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x1002293f8) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130aa8). One of the two will be used. Which one is undefined.
objc[24956]: Class RendererImpl2dMacQuartz is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x100229420) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130ad0). One of the two will be used. Which one is undefined.
objc[24956]: Class CinderWindow is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x100229498) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130b48). One of the two will be used. Which one is undefined.
objc[24956]: Class AppImplMac is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x1002294c0) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130b70). One of the two will be used. Which one is undefined.
objc[24956]: Class WindowImplBasicCocoa is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x1002294e8) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130b98). One of the two will be used. Which one is undefined.
objc[24956]: Class CinderViewMac is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x100229560) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130c10). One of the two will be used. Which one is undefined.
objc[24956]: Class MovieDelegate is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x1002295b0) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130c60). One of the two will be used. Which one is undefined.
objc[24956]: Class IStreamUrlImplCocoaDelegate is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x100229600) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130cb0). One of the two will be used. Which one is undefined.
objc[24956]: Class RendererImplGlMacTransparentView is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x100229678) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130d28). One of the two will be used. Which one is undefined.
objc[24956]: Class RendererImplGlMac is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x1002296a0) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130d50). One of the two will be used. Which one is undefined.
objc[24956]: Class WindowImplCocoaView is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x1002296f0) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130da0). One of the two will be used. Which one is undefined.
objc[24956]: Class AppImplCocoaView is implemented in both /Users/mark/code/Cinder-Plugin/samples/drawableApp/xcode/build/Debug/drawableApp.app/Contents/MacOS/drawableApp (0x100229740) and /Users/mark/code/Cinder-Plugin/samples/drawablePlugin/xcode/build/Debug/libdrawablePlugin-00.dylib (0x10d130df0). One of the two will be used. Which one is undefined.

I believe the solution is to link both the host app and the plugin to cinder as a dynamic library. You’ll find some info here about how to make this on mac, and a branch here for windows.

What’s happening now is that you have basically two versions of cinder. One in your host app, and another in the loaded module that is probably not initialized the way it should. I don’t remember the exact behavior on mac but I can tell that ci::app::App::get() will return a nullptr when called from a dll on windows.

The only way to ensure that everything live in the same space is to use a dynamic library, the same compiler and preferably the same compiler settings and runtime-library for both the module and the host app.

Hey Simon, thanks for the info. I figured I’d eventually need to switch to a dynamic lib version of cinder, but it seems like a very non-trivial task indeed. I’m gonna dig into the Cinder-Runtime repo, cheers!

I was interested in this topic so I did a quick test by compiling cinder dynamically and linking the cinder dylib in the drawableApp and in the plugin, and now works!

@dashandslash good stuff, how’d you go about doing that?

It could be great to have an official new scheme in the cinder project to compile the dylib, and then simply link the dynamic library and apply the quick changes explained by @Simon in the previous post.

L

Just a note that with this PR and CMake building Cinder as dylib is as simple as configuring with -DBUILD_SHARED_LIBS=1 .

i.e cd Cinder && mkdir build && cd build && cmake .. -DBUILD_SHARED_LIBS=1 && make -j4

The PR might change because it doesn’t seem that curl is really needed on OS X but the process of building Cinder as a dylib with CMake as described above will stay the same.

Petros