2016-07-11 7 views
0

Ich möchte eine pluginartige Architektur verwenden, um Module in eine Cocoa-basierte Anwendung laden zu können. Alle Module verwenden dieselbe API, aber Name und Anzahl der Module können variieren und sind beim Erstellen der Anwendung nicht bekannt.Dynamisches Laden der Laufzeit von Cocoa Bundles mit C++ Code

Derzeit verwende ich statische Bibliotheken, aber das erfordert, dass ich die Anwendung jedes Mal neu kompiliere, wenn ich ein Modul hinzufüge oder entferne. Ich möchte in der Lage sein, dies dynamisch zu tun - d. H. Meine Anwendung neu starten, um eine Liste von Modulen zu aktualisieren, die als Dateien hinzugefügt wurden.

Ich erwäge 2 Ansätze:

  1. Verwenden dynamische Bibliotheken (.dylib-Dateien) und laden sie zur Laufzeit mit dlopen() und dlsym()
  2. Verwenden Bundles (.bundle-Dateien) und Last sie zur Laufzeit mit den Cocoa Funktionen

Erschwerend kommt hinzu, der Module Code ist C++ (Legacy-Code) mit einer Schnittstelle wie folgt aus:

// MyModule_API.h 
class MyModule_API { 
public: 
    static MyModule* create(); 
    static void destroy(MyModule* m); 

    virtual void processMap(std::map<std::string, float>) = 0; 
    virtual std::vector<std::string> getNames() = 0; 
} 

Eine der aktuellen statischen Module würde wie folgt (Das Modul implementiert die statischen erstellen/Funktionen zerstören und den Rest der API) definiert werden:

#include "MyModule_API.h" 
class MyModule : MyModule_API { 
public: 
    explicit MyModule(std::string param1, std::string param2) : _param1(param1), _param2(param2) { } 
    ~MyModule() { }; 
    // MyModule_API: 
    void processMap(std::map<std::string, float>) override { ... } 
    std::vector<std::string> getNames() override { return std::vector<std::string({_param1, _param2}); } 
private: 
    std::string _param1, _param2; 
} 

MyModule_API* MyModule_API::create() { 
    MyModule* m = new MyModule("foo", "bar");   
    //cast to base/API class before returning 
    return (MyModule_API*) m; 
} 

void MyModule_API::destroy(MyModule_API* m) { 
    if (m != nullptr) { 
     delete m; 
    } 
} 

Wegen name-Mangeln in C++ unter Verwendung des dylib Ansatz scheint nicht durchführbar, da die Anwendung nicht in der Lage wäre, die Symbole nach Namen zu lokalisieren, ohne sie hart zu codieren.

Daher versuche ich Objective-C-Wrapper für die Module zu verwenden und sie dann als NSBundles aus Resources/Plugins-Ordner in der .app zu importieren.

// MyModule_ObjC.h 
#import <Foundation/Foundation.h> 
#include "MyModule_API.h" 
@interface MyModule_ObjC : NSObject { 
    MyModule_API* _myModule; 
} 
- (id) init; 
- (void) dealloc; 
- (MyModule_API*) getMyModule; 
@end 


// MyModule_ObjC.mm 
#import "MyModule_ObjC" 
@implementation MyModule_ObjC 
- (id)init { 
    self = [super init]; 
    if (self) { 
     _myModule = MyModule_API::create(); 
    } 
    return self; 
} 
- (void)dealloc { 
    MyModule_API::destroy(_myModule); 
} 
- (MyModule_API*) getMyModule { 
    return _myModule; 
} 
@end 

Mit diesem kann ich erfolgreich eine .bundle-Datei erstellen. Ich versuche dann dieses Bündel in ein Cocoa-basierten Test-App zu importieren:

#import <Cocoa/Cocoa.h> 
#import <mach-o/dyld.h> 
#import "MyModule_ObjC.h" 
#include "MyModule_API.h" 

int main(int argc, const char * argv[]) {  
    NSBundle *appBundle = [NSBundle mainBundle]; 
    NSArray *bundlePaths = [appBundle pathsForResourcesOfType:@"bundle" inDirectory:@"PlugIns"]; 
    for (id bundlePath in bundlePaths) { 
     NSBundle* bundle; 
     bundle = [NSBundle bundleWithPath:bundlePath]; 
     [bundle load]; 

     if (bundle) { 
      MyModule_ObjC* moduleAPIClass = [bundle principalClass]; 
      if (moduleAPIClass) { 
       id moduleInstance; 
       moduleInstance = [[MyModule_ObjC alloc] init]; 
       if (moduleInstance) { 
        MyModule_API* module = [moduleInstance getMyModule]; 
       } 
      } 
     } 
    } 
    return 0; 
} 

der Linker ist jedoch nicht in der Lage zu finden „_OBJC_CLASS _ $ _ MyModule_ObjC“ ... was Sinn macht, da Projekt der Testanwendung nicht Include MyModule_ObjC.mm, nur .h. Wenn Sie das .mm hinzufügen, wird es die statischen Implementierungen von create/destroy nicht finden, da das Modul nicht mehr statisch verknüpft ist. Ich möchte diese create/destroy-Implementierungen jedoch im Plugin/Bundle haben.

Im Prinzip klingt mein Ansatz?
Wenn nicht, welchen Ansatz würden Sie empfehlen, damit diese Plugin-Architektur funktioniert?

Vielen Dank im Voraus und Entschuldigung für die lange Post.

Antwort

1

Nun, Sie sind fast da ;-). Das Problem liegt in der Art, wie Sie die Plugin-Oberfläche behandeln. Im Moment kann der Compiler die tatsächlichen Symbole nicht finden, da sie zur Verknüpfungszeit unbekannt sind. Die Lösung ist ziemlich einfach:

Verwenden Sie ein Protokoll anstelle Ihrer Klassenschnittstelle.

@protocol MyModule_ObjC <NSObject> 

- (MyModule_API*) getMyModule; 

@end 

In Ihren Plugins müssen Sie diese Schnittstelle und die entsprechende Hauptklasse implementieren.

// PluginA.h 

#import <Foundation/Foundation.h> 
#include "MyModule_ObjC.h" 

@interface PluginA : NSObject<MyModule_ObjC> 

@end 


// PluginA.mm 

#import "PluginA.h" 

@implementation PluginA { 

    @private 

    MyModule_API* _myModule; 
} 

- (id)init { 

    self = [super init]; 

    if (self) { 

     _myModule = MyModule_API::create(); 
    } 

    return self; 
} 

- (void)dealloc { 

    MyModule_API::destroy(_myModule); 
} 

- (MyModule_API*) getMyModule { 

    return _myModule; 
} 

@end 

in Ihrer Anwendung müssen Sie die Plug-in gegen das Protokoll laden:

#import <Cocoa/Cocoa.h> 
#import <mach-o/dyld.h> 
#import "MyModule_ObjC.h" 
#include "MyModule_API.h" 

int main(int argc, const char * argv[]) {  
    NSBundle *appBundle = [NSBundle mainBundle]; 
    NSArray *bundlePaths = [appBundle pathsForResourcesOfType:@"bundle" inDirectory:@"PlugIns"]; 
    for (id bundlePath in bundlePaths) { 
     NSBundle* bundle; 
     bundle = [NSBundle bundleWithPath:bundlePath]; 
     [bundle load]; 

     if (bundle) { 
      Class moduleAPIClass = [bundle principalClass]; 
      if (moduleAPIClass && [moduleAPIClass conformsToProtocol:@protocol(MyModule_ObjC)]) { 
       id<MyModule_ObjC> moduleInstance; 
       moduleInstance = [[moduleAPIClass alloc] init]; 
      } 
     } 
    } 
    return 0; 
} 
+0

Danke, Jens! Es scheint so, als ob nur diese zusätzliche Ebene der Indirektion im Objective-C Wrapper fehlte :-) Es ist jetzt erfolgreich verlinkt und ich kann 2 unabhängige Module laden. – SideLobe

+0

Das ist großartig! Freut mich, dass du rennst. –