djinni简析

djinni是Dropbox开源的一个库,它会根据IDL定义生成相应的接口和很多胶水代码,可以做到Mac、PC、iOS、Android四个端共享接口和C++基础代码。

本文仅从iOS的角度分析一下怎么djinni的基础使用方法。djinni的调用路径是C++->ObjectiveC++->Objective-C,所以从每种语言的层面做一些分析。

整个的关系图如下所示。调用路径是WLALoginSDK->cppRef(WLALoginSDKImpl)->djinni_generated::LoginProvider->ObjcRef(WLALoginProviderImpl)。如果未来有C++实现的OAuth 2.0客户端,调用逻辑可以做很多简化,不需要一个provider去调用AppAuth库。

Paste_Image.png
Paste_Image.png

脚本

从脚本可以看出C++代码的namespace是wla_gen,而Objective-C++代码的namespace是djinni_generated。Objective-C相关的类和协议都会加上objc-type-prefix前缀,也就是脚本里面配置的WLA。记住这些规律,有利于分析每个类的角色。

../djinni_exe/run \
   --java-out ./app/src/main/java/com/aliyun/wla/login \
   --java-package com.aliyun.wla.login \
   --java-cpp-exception java.lang.RuntimeException \
   --ident-java-field mFooBar \
   --cpp-out ./djinni_gen_cpp \
   --cpp-namespace wla_gen \
   --cpp-libexport WLA_LOGIN_EXPORT \
   --jni-out ./djinni_gen_jni \
   --ident-jni-class NativeFooBar \
   --ident-jni-file NativeFooBar \
   --objc-out ./djinni_gen_oc \
   --objc-type-prefix WLA \
   --objcpp-out ./djinni_gen_oc \
   --objcpp-namespace djinni_generated \
   --idl ./all.djinni

IDL

一份IDL定义如下所示。其中LoginSDK的具体逻辑要以C++实现,向Objective-C暴露一个WLALoginSDK类,但是它主要调用LoginProvider干活。LoginProvider通过C++定义了接口,但是通过WLALoginProviderImpl这个Objective-C类来实现具体的逻辑,比如OAuth的认证流程采用OpenID下面的AppAuth-iOS和AppAuth-Android来完成,这些库并不是C++写的。

//+c 表示用C++实现逻辑,暴露给Objective-C使用。
//+c 会在Objective-C生成一个 @interface WLALoginSDK。
LoginSDK = interface +c {
    static sharedInstance(): LoginSDK;
    
    setLoginProvider(provider: LoginProvider);
    
    setClientID(client_id: string);
}

# wrapper of each platform's OAuth SDK
// +o +j 表示要用各端的代码实现逻辑
// 会在Objective-C生成一个 @protocol WLALoginProvider,需要有一个Objective-C实现类。
LoginProvider = interface +o +j {
    setClientID(client_id: string);
}

C++

不管+c还是+o +j,都会生成一个C++纯虚类。

//LoginSDK.hpp
namespace wla_gen {

class WLA_LOGIN_EXPORT LoginSDK {
public:
    virtual ~LoginSDK() {}

    static std::shared_ptr<LoginSDK> sharedInstance();

    virtual void setLoginProvider(const std::shared_ptr<LoginProvider> & provider) = 0;

    virtual void setClientID(const std::string & client_id) = 0;
};

}  // namespace wla_gen

//LoginProvider.hpp
namespace wla_gen {

class LoginProvider {
public:
    virtual ~LoginProvider() {}

    virtual void setClientID(const std::string & client_id) = 0;
};

}  // namespace wla_gen

Objective-C++

不管+c还是+o +j,都会生成一个Objective-C++的类,文件名是xxx+private.hxxx+private.mm。Objective-C++类有两个主要的作用就是toCpp和fromCpp,让C++对象和Objective-C对象可以互相找到彼此。针对+c+o +j生成的代码有所不同。下面分两种情况分析一下。

+c

主要是完善WLALoginSDK类,它持有一个::wla_gen::LoginSDK纯虚类的实现,干活都是这个cppRef

namespace djinni_generated {

class LoginSDK
{
public:
    using CppType = std::shared_ptr<::wla_gen::LoginSDK>;
    using CppOptType = std::shared_ptr<::wla_gen::LoginSDK>;
    using ObjcType = WLALoginSDK*;

    using Boxed = LoginSDK;

    static CppType toCpp(ObjcType objc);
    static ObjcType fromCppOpt(const CppOptType& cpp);
    static ObjcType fromCpp(const CppType& cpp) { return fromCppOpt(cpp); }

private:
    class ObjcProxy;
};

}  // namespace djinni_generated
@interface WLALoginSDK ()

- (id)initWithCpp:(const std::shared_ptr<::wla_gen::LoginSDK>&)cppRef;

@end

@implementation WLALoginSDK {
    ::djinni::CppProxyCache::Handle<std::shared_ptr<::wla_gen::LoginSDK>> _cppRefHandle;
}

- (id)initWithCpp:(const std::shared_ptr<::wla_gen::LoginSDK>&)cppRef
{
    if (self = [super init]) {
        _cppRefHandle.assign(cppRef);
    }
    return self;
}

+ (nullable WLALoginSDK *)sharedInstance {
    try {
        auto objcpp_result_ = ::wla_gen::LoginSDK::sharedInstance();
        return ::djinni_generated::LoginSDK::fromCpp(objcpp_result_);
    } DJINNI_TRANSLATE_EXCEPTIONS()
}

- (void)setLoginProvider:(nullable id<WLALoginProvider>)provider {
    try {
        _cppRefHandle.get()->setLoginProvider(::djinni_generated::LoginProvider::toCpp(provider));
    } DJINNI_TRANSLATE_EXCEPTIONS()
}

+o +j

而LoginProvider则实现了::wla_gen::LoginProvider::djinni::ObjcProxyCache::Handle<ObjcType>,一看就知道要甩锅给一个Objective-C对象。

namespace djinni_generated {

class LoginProvider
{
public:
    using CppType = std::shared_ptr<::wla_gen::LoginProvider>;
    using CppOptType = std::shared_ptr<::wla_gen::LoginProvider>;
    using ObjcType = id<WLALoginProvider>;

    using Boxed = LoginProvider;

    static CppType toCpp(ObjcType objc);
    static ObjcType fromCppOpt(const CppOptType& cpp);
    static ObjcType fromCpp(const CppType& cpp) { return fromCppOpt(cpp); }

private:
    class ObjcProxy;
};

}  // namespace djinni_generated
//隐藏在WLALoginProvider+Private.mm文件中
class LoginProvider::ObjcProxy final
: public ::wla_gen::LoginProvider
, public ::djinni::ObjcProxyCache::Handle<ObjcType>
{
public:
    using Handle::Handle;
    void setClientID(const std::string & c_client_id) override
    {
        @autoreleasepool {
            [Handle::get() setClientID:(::djinni::String::fromCpp(c_client_id))];
        }
    }

LoginSDKImpl的逻辑主要是C++写的,主要是调LoginProvider做各种事情。这个地方可以写一些平台同样的逻辑,比如token的存取等。

void LoginSDKImp::setLoginProvider(const std::shared_ptr<wla_gen::LoginProvider> & provider)
{
    _provider = provider;
}

void LoginSDKImp::setClientID(const std::string & client_id)
{
    if (_provider)
        _provider->setClientID(client_id);
}

Objective-C

LoginProvider要Objective-C来实现具体的逻辑,所以要有一个实现WLALoginProvider协议的类。

@interface WLALoginProviderImpl : NSObject <WLALoginProvider>

@end

@interface WLALoginProviderImpl ()

@property(nonatomic, strong, nullable) id<OIDAuthorizationFlowSession> currentAuthorizationFlow;

@end

@implementation WLALoginProviderImpl

- (void)login:(nullable id<WLALoginCallback>)callback {
    //使用AppAuth-iOS发起OAuth 2.0认证
}

djinni不足之处

djinni无法表达每个端专有的特性,具体到iOS端,有如下不足。

  1. 无法表达nonullnullable。对于普通属性统统使用nonull,对于callback则使用nullable
  2. 无法表达协议的@optional。由于IDL往往是各端接口的超集,iOS只会实现其中某些接口,这样会导致很多警告。
  3. 跟所有IDL一样,无法使用各端特有的数据类型,无法使用泛型。
  4. 以上缺点都能忍,callback的问题实在是很难忍受,Objective-C和C++的Callback写起来都很麻烦。

Objective-C callback

所有+o +j的callback都会变成协议,需要一个Objective-C包装类,使用起来非常之麻烦。

@interface WLALoginCallbackImpl : NSObject <WLALoginCallback>

- (instancetype)initWithBlock:(void(^)(WLAUser *, WLAError *))block;

@end

@interface WLALoginCallbackImpl ()

@property (nonatomic, copy) void(^block)(WLAUser *user, WLAError *error);

@end

@implementation WLALoginCallbackImpl

- (instancetype)initWithBlock:(void (^)(WLAUser *, WLAError *))block
{
    self = [super init];
    if (self) {
        self.block = block;
    }

    return self;
}

- (void)call:(nonnull WLAError *)error
        user:(nonnull WLAUser *)user {
    if (self.block) {
        self.block(user, error);
    }
}

@end

//使用起来一点都没有block那种行云流水的感觉
[[WLALoginSDK sharedInstance] login: [[WLALoginCallbackImpl alloc]
                                      initWithBlock:^(WLAUser *user, WLAError *error) {
}]];

C++ callback

C++层的block需要使用makeCallback构造出来,不算方便,但是比起Objective-C还是要好一些的。

void LoginSDKImp::login(const std::shared_ptr<wla_gen::LoginCallback> & callback)
{
    if (_provider) {
        std::weak_ptr<LoginSDKImp> weak_this = shared_from_this();
        auto provider_callback = wla::makeCallback<wla_gen::ProviderLoginCallback>([weak_this, callback](const wla_gen::Error &err, const string &response) {
            auto strong_this = weak_this.lock();
            if (!strong_this)
                return;

            strong_this->handleLoginResponse(err, response, callback);
        });

        _provider->login(provider_callback);
    }
}

make_callBack包装了一个lambda闭包。反正现在C++ 11的代码鬼都看不懂,贴一个lambda闭包的例子吧。

#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>

int main()
{
    std::vector<int> c = {1, 2, 3, 4, 5, 6, 7};
    int x = 5;
    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end());

    std::cout << "c: ";
    std::for_each(c.begin(), c.end(), [](int n){ std::cout << n << ' '; });
    std::cout << '\n';

    auto func1 = [](int n) { return n + 4; };
    std::cout << "func1: " << func1(6) << '\n';

    std::function<int(int)> func2 = [](int n) { return n + 4; };
    std::cout << "func2: " << func2(6) << '\n';
}
$ clang -std=c++11 1.cpp -lstdc++ && ./a.out 
c: 5 6 7 
func1: 10
func2: 10

参考资料。

  1. djinni helloworld
  2. Dropbox经验谈:iOS和Android的C++跨平台开发
  3. Facebook应用Moments使用C++实现跨平台代码共享
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • Swift版本点击这里欢迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh阅读 25,275评论 7 249
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 端口 netstat -anp|grep 80 #查看端口占用(进程)netstat -anp|more #查看...
    刘计计计阅读 403评论 0 0
  • 是誰 像晨曦的陽光般 給我帶來光亮 是誰 像烈日的太陽 把我曬傷 溫柔的力量 強大 足以喚醒 所有沉睡的黑暗 烈日...
    蔡振源阅读 183评论 0 2
  • 架构师之路--服务器集群搭建、管理、与快速部署 什么是集群? 集群,是一组独立的计算机系统构成一个松耦合的多处理器...
    刘宇龙阅读 201评论 0 0