Cocoa 封装 C++ 代码的包的动态运行时加载
Dynamic runtime loading of Cocoa Bundles that wrap C++ Code
我想使用类似插件的架构来将模块加载到基于 Cocoa 的应用程序中。所有模块都使用相同的 API,但模块的名称和数量可能会有所不同,并且在构建应用程序时是未知的。
目前,我正在使用静态库,但这需要我在每次添加或删除模块时重新编译应用程序。我希望能够动态地执行此操作 - 即重新启动我的应用程序以更新作为文件添加的模块列表。
我正在考虑两种方法:
- 使用动态库(.dylib 文件)并在运行时使用 dlopen() 和 dlsym() 加载它们
- 使用捆绑包(.bundle 文件)并在运行时使用 Cocoa 函数加载它们
更复杂的是,模块的代码是 C++(遗留代码),接口如下:
// 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;
}
一个当前的静态模块将这样定义(该模块实现了静态 create/destroy 函数和 API 的其余部分):
#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;
}
}
由于 C++ 中的名称修改,使用 dylib 方法似乎不可行,因为如果不对符号进行硬编码,应用程序将无法通过名称定位符号。
因此,我正在尝试对模块使用 Objective-C 包装器,然后将它们作为 NSBundles 从 .app.Resources/Plugins 文件夹中导入。
// 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
有了这个,我可以成功地构建一个.bundle 文件。
然后我尝试将这个包导入到基于 Cocoa 的测试应用程序中:
#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;
}
但是,链接器无法找到“_OBJC_CLASS_$_MyModule_ObjC”...这是有道理的,因为测试应用程序项目不包含 MyModule_ObjC.mm,仅包含 .h .如果添加 .mm,它将找不到 create/destroy 的静态实现,因为该模块不再是静态链接的。但是,我希望这些 create/destroy 实现在 plugin/bundle.
原则上,我的方法合理吗?
如果没有,您会推荐什么方法来让这个插件架构工作?
在此先感谢您,很抱歉 post。
好吧,你快到了 ;-)。问题在于您对待插件界面的方式。目前编译器无法找到实际的符号,因为它们在链接时是未知的。解决方法很简单:
使用协议代替您的 class 界面。
@protocol MyModule_ObjC <NSObject>
- (MyModule_API*) getMyModule;
@end
在你的插件中你需要实现这个接口和相应的主体class。
// 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
在您的应用程序中,您需要根据协议加载插件:
#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;
}
我想使用类似插件的架构来将模块加载到基于 Cocoa 的应用程序中。所有模块都使用相同的 API,但模块的名称和数量可能会有所不同,并且在构建应用程序时是未知的。
目前,我正在使用静态库,但这需要我在每次添加或删除模块时重新编译应用程序。我希望能够动态地执行此操作 - 即重新启动我的应用程序以更新作为文件添加的模块列表。
我正在考虑两种方法:
- 使用动态库(.dylib 文件)并在运行时使用 dlopen() 和 dlsym() 加载它们
- 使用捆绑包(.bundle 文件)并在运行时使用 Cocoa 函数加载它们
更复杂的是,模块的代码是 C++(遗留代码),接口如下:
// 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;
}
一个当前的静态模块将这样定义(该模块实现了静态 create/destroy 函数和 API 的其余部分):
#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;
}
}
由于 C++ 中的名称修改,使用 dylib 方法似乎不可行,因为如果不对符号进行硬编码,应用程序将无法通过名称定位符号。
因此,我正在尝试对模块使用 Objective-C 包装器,然后将它们作为 NSBundles 从 .app.Resources/Plugins 文件夹中导入。
// 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
有了这个,我可以成功地构建一个.bundle 文件。 然后我尝试将这个包导入到基于 Cocoa 的测试应用程序中:
#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;
}
但是,链接器无法找到“_OBJC_CLASS_$_MyModule_ObjC”...这是有道理的,因为测试应用程序项目不包含 MyModule_ObjC.mm,仅包含 .h .如果添加 .mm,它将找不到 create/destroy 的静态实现,因为该模块不再是静态链接的。但是,我希望这些 create/destroy 实现在 plugin/bundle.
原则上,我的方法合理吗?
如果没有,您会推荐什么方法来让这个插件架构工作?
在此先感谢您,很抱歉 post。
好吧,你快到了 ;-)。问题在于您对待插件界面的方式。目前编译器无法找到实际的符号,因为它们在链接时是未知的。解决方法很简单:
使用协议代替您的 class 界面。
@protocol MyModule_ObjC <NSObject>
- (MyModule_API*) getMyModule;
@end
在你的插件中你需要实现这个接口和相应的主体class。
// 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
在您的应用程序中,您需要根据协议加载插件:
#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;
}