C++跨平臺SDK開發(fā)指北

上半年一直忙于主機(jī)平臺支付功能的接入骗污,原來基于android開發(fā)的sdk并不適用主機(jī)平臺。為了滿足業(yè)務(wù)需求躏精,先是重新開發(fā)了一套u(yù)nity的sdk李丰,用于unity引擎游戲業(yè)務(wù)集成。然而該unity sdk并不能滿足ue4引擎游戲業(yè)務(wù)的需求叠纹,考慮到后續(xù)sdk的兼容和通用季研,選擇重新開發(fā)一套c++ sdk,實(shí)現(xiàn)跨主機(jī)平臺接入誉察。

雖然很早之前也接觸過c++開發(fā)与涡,但工作以來主要還是做android平臺開發(fā),c++很多東西早就忘的一干二凈了持偏。相對于早前自己開發(fā)的unity sdk驼卖,c#語言更類似java,同樣采用虛擬機(jī)實(shí)現(xiàn)鸿秆,平臺工具都比較齊全酌畜。而c++自身的語言特性和平臺實(shí)現(xiàn)差異,決定了重新開發(fā)一套c++ sdk的難度卿叽。

外部接口


c++ sdk的目標(biāo)是能支持跨平臺調(diào)用桥胞,實(shí)現(xiàn)多平臺支付接入。如:既能支持switch考婴、ps4贩虾、xbox等不同平臺支付功能的接入,又能支持unity跨平臺調(diào)用該sdk沥阱。因此缎罢,對外暴露的sdk接口入?yún)⒒卣{(diào)等最好是統(tǒng)一格式,最小化的個性定制。

C++接口

c++對外接口如下所示:

        //Midas初始化 
        void Initialize(const MidasInitRequest& req, MCallback callbck);

        //Midas支付
        void Pay(const MidasGameRequest& req, MCallback callback);
        void Pay(const MidasGoodsRequest& req, MCallback callback);
        void Pay(const MidasSubscribeRequest& req, MCallback callback);
        ......      

        //函數(shù)指針回調(diào)
        typedef void(*MCallback)(const char*);
  • req:為Midas自身請求參數(shù)類策精,各接口req存在繼承組合關(guān)系舰始。
    • 外部傳入引用,而不是指針
      • sdk內(nèi)部會統(tǒng)一將引用轉(zhuǎn)成指針咽袜,外部不需要關(guān)注指針的釋放回收丸卷。
    • Pay接口通過傳入req的不同實(shí)現(xiàn)來實(shí)現(xiàn)多態(tài)
      • 由于c++不能像java支持父類到子類的動態(tài)轉(zhuǎn)換,通過三個接口的方式询刹,實(shí)現(xiàn)不同業(yè)務(wù)邏輯的封裝及老。
  • callback:函數(shù)指針回調(diào),各接口統(tǒng)一回調(diào)json字符串
    • 字符串方便unity跨平臺調(diào)用

Unity跨平臺調(diào)用接口

為了實(shí)現(xiàn)unity跨平臺調(diào)用該c++ sdk范抓,需要再封裝一層協(xié)議接口層骄恶,實(shí)現(xiàn)unity到c++的轉(zhuǎn)換。并通過dllexport匕垫,將接口暴露到unity層僧鲁。

以初始化接口Initialize為例:

#define PRX_EXPORT extern "C" __declspec (dllexport)

/*
 * jInitRequest:unity層傳入string
 * callback: unity層傳入函數(shù)指針回調(diào)
 */
PRX_EXPORT void Initialize(const char* jInitRequest, MCallback callback)
{
    Document doc;
    if (!doc.Parse(jInitRequest).HasParseError())
    {
        MidasInitRequest _initReq;
        if (doc.HasMember("idc"))
            _initReq.idc = doc["idc"].GetString();
        if (doc.HasMember("env"))
            _initReq.env = doc["env"].GetString();
        ...

        //初始化
        MidasPayService::instance()->Initialize(_initReq, callback);
    }
}
  • jInitRequest:unity層傳入string,c++層轉(zhuǎn)換成對應(yīng)的類MidasInitRequest象泵。
  • callback:unity層傳入對應(yīng)的函數(shù)指針回調(diào)

unity層調(diào)用

unity層調(diào)用c++ dll庫中接口的方式如下:

    //1寞秃、引入c++ dll庫中接口
    [DllImport("PS4Channel")]
    public static extern void Initialize(string jInitRequest,DllcallBack callback);

    //2、定義委托偶惠,用于接收c++層回調(diào)
    public delegate void DllcallBack(string response);
    //委托實(shí)現(xiàn)春寿,處理c++初始化回調(diào)結(jié)果
    [MonoPInvokeCallback(typeof(DllcallBack))]
    public static void CSharpCallbackInit(string response){}
    
    //3、調(diào)用
    Initialize(jInitReq.ToString(), CSharpCallbackInit);

邏輯層


這部分內(nèi)容主要分析c++ sdk的邏輯設(shè)計思路忽孽。c++ sdk對外提供初始化绑改、支付、補(bǔ)發(fā)兄一、查物品信息和查營銷信息等功能厘线,對內(nèi)需要維護(hù)復(fù)用一套框架,實(shí)現(xiàn)不同平臺不同支付渠道的接入出革。

訂單中心

由于對外提供的接口存在相似性(入?yún)equest造壮、回調(diào)callback),抽象出訂單中心骂束。

  • 每次外部接口調(diào)用耳璧,生成一筆內(nèi)部訂單(key、參數(shù)展箱、回調(diào))
  • 根據(jù)key查找獲取外部參數(shù)旨枯、回調(diào)外部

每次各外部接口調(diào)用開始到結(jié)束,復(fù)用同一套訂單邏輯析藕。以支付接口調(diào)用為例:

1 每次支付生成訂單
    //支付
    void MPayManager::pay(MidasBasePayRequest* req, MCallback _callback)
    {
        //1召廷、生成支付渠道
        ChannelBasePay* payChannel = ChannelFactory::createPayChannel(req->_base.payChannel);
        if (payChannel)
        {
            //2、創(chuàng)建訂單
            int orderKey = generateOrderKey();
            MidasOrder order;
            order.request = req;        
            order.callback = _callback;
            addOrder(orderKey, order);

            //3账胧、支付竞慢,傳入訂單key
            payChannel->setOrderKey(orderKey);
            ...
2 根據(jù)訂單key回調(diào)外部
//根據(jù)訂單key回調(diào)外部
void MPayManager::callback(int key, MidasResponse& response, ChannelBase* instance)
    {
        //1、根據(jù)key查找訂單
        MidasOrder order = getOrder(key);
        if (order.callback)
        {
            ...
            //2治泥、回調(diào)外部
            order.callback(response.toJStr().c_str());
            //3筹煮、移除訂單
            removeOrder(key);
        ...

渠道

sdk是以渠道為粒度,提供了一套框架居夹,負(fù)責(zé)實(shí)現(xiàn)各渠道的流程調(diào)度败潦。而每個渠道通常存在差異,對外提供的功能不一准脂,從而將渠道的每項(xiàng)功能抽象為具體的渠道實(shí)例劫扒,如:支付渠道、補(bǔ)發(fā)渠道狸膏、查物品信息渠道和查營銷活動渠道沟饥。而sdk再通過基類,抽象出各功能渠道的流程框架湾戳,具體實(shí)現(xiàn)交由不同子類渠道完成贤旷。

仍以支付為例:

    //支付基類
    class ChannelBasePay : public ChannelBase
    {
        ...
        //1、子類通過該方法實(shí)現(xiàn)流程流轉(zhuǎn)
        void notifyAsyncFinish(int type);//異步操作完成
        //2砾脑、基類模板方法
        virtual void init();
        virtual void prePay();
        virtual void pay();
        virtual void postPay() {}
        virtual bool needOrder(){return false;}
        virtual bool needProvide(){return false;}
        virtual void dispose() {}
    ...

由于c++不支持反射幼驶,不能像java實(shí)現(xiàn)實(shí)例的動態(tài)創(chuàng)建。c++ sdk通過渠道工廠類韧衣,負(fù)責(zé)渠道實(shí)例的創(chuàng)建回收盅藻。

    class ChannelFactory
    {
    public:
        static ChannelBasePay* createPayChannel(string channel);
        static ChannelBaseReprovide* createReprovideChannel(string channel);
        static list<ChannelBase*> gc_list;  //用于存放分配的對象,方便釋放

網(wǎng)絡(luò)

網(wǎng)絡(luò)層設(shè)計的目標(biāo)是既能滿足sdk自身業(yè)務(wù)需要畅铭,又能跨平臺調(diào)用萧求。于是將網(wǎng)絡(luò)抽象成3層:

  • 業(yè)務(wù)層:滿足sdk自身請求加密等需要。
  • 功能層:網(wǎng)絡(luò)基礎(chǔ)邏輯功能顶瞒,封裝https夸政、post等網(wǎng)絡(luò)請求邏輯这难。
  • 跨平臺層:不同平臺具體網(wǎng)絡(luò)數(shù)據(jù)收發(fā)實(shí)現(xiàn)粱胜。

這里講下跨平臺層實(shí)現(xiàn)邏輯财剖。

由于sdk目標(biāo)是能在多平臺上運(yùn)行立由,而不同平臺如windows舌镶、linux和unix中網(wǎng)絡(luò)庫不同哑梳;要實(shí)現(xiàn)多平臺統(tǒng)一另玖,可以采用的方式有:

  • 方式一:自己基于socket忠售,用c手?jǐn)]一套網(wǎng)絡(luò)代碼
    • 難度大袱贮、易出錯仿便,手?jǐn)]https基本絕望
  • 方式二:采用第三方庫,如libcurl,再編譯成各平臺響應(yīng)的庫
    • 前期在x64上通過該方式實(shí)現(xiàn)的網(wǎng)絡(luò)請求嗽仪,但在移植到主機(jī)平臺時荒勇,各種天坑。
    • 由于主機(jī)平臺的小眾性闻坚,libcurl的主機(jī)平臺編譯幾乎沒見人做過沽翔,自己編譯時各種問題,修改源碼才行窿凤。
    • 考慮到sdk還用了openssl等庫仅偎,采用這種方式適配,工作量巨大雳殊。
  • 方式三:sdk封裝網(wǎng)絡(luò)框架橘沥,基礎(chǔ)的網(wǎng)絡(luò)收發(fā)交由各平臺實(shí)現(xiàn)
    • 該方式雖需要做不同平臺的適配,但由于各平臺文檔健全夯秃,工作量反而最小威恼,也較容易實(shí)現(xiàn)。

    • 選擇該方式實(shí)現(xiàn)

      class NetReqBase
      {
      public:
          ...
          //發(fā)起請求
          void start()
          {
              //1寝并、生成平臺相關(guān)網(wǎng)絡(luò)類
              PlatformNet* netPlatform = PlatformFactory::createNetPlatform();
              if (netPlatform)
              {
                  //2箫措、具體平臺收發(fā)網(wǎng)絡(luò)數(shù)據(jù)
                  string response = netPlatform->https_post(getUrl(), getParams());
                  if (_observer)
                      _observer->parse(getExtra(), response);
                  delete netPlatform;
              ...
      

跨平臺


c++ sdk的目標(biāo)是跨平臺,但如何實(shí)現(xiàn)跨平臺衬潦?

綜合sdk實(shí)現(xiàn)過程中斤蔓,需要跨平臺實(shí)現(xiàn)的主要是sdk的基礎(chǔ)功能,如:網(wǎng)絡(luò)收發(fā)镀岛、AES加/解密弦牡、gzip壓縮。正如在上面提到的一樣漂羊,開始的方案是通過引入第三方平臺無關(guān)的庫來實(shí)現(xiàn)各基礎(chǔ)功能驾锰,再通過編譯成不同平臺的庫來實(shí)現(xiàn)跨平臺。而在x64上走越,自己也通過這種方式實(shí)現(xiàn)了整套流程椭豫,在x64上采用的庫為:

  • 網(wǎng)絡(luò)收發(fā):libcurl
  • AES加/解密:openssl
  • gzip壓縮:zlib

而在移植到ps4平臺時,花了大量時間做libcurl的編譯旨指,而openssl編譯適配更是毫無頭緒赏酥。于是放棄了這種實(shí)現(xiàn),改而通過將基礎(chǔ)功能下沉到具體平臺實(shí)現(xiàn)谆构,sdk抽象出基類實(shí)現(xiàn)跨平臺框架裸扶。最終在ps4的移植后,采用的方式:

  • 網(wǎng)絡(luò)收發(fā)搬素、AES加解密
    • 下沉到調(diào)用ps4平臺api實(shí)現(xiàn)
  • gzip壓縮
    • ps4沒有g(shù)zip壓縮庫呵晨,通過zlib編譯ps4平臺包實(shí)現(xiàn)
通過條件編譯生成不同平臺類
    PlatformNet* PlatformFactory::createNetPlatform()
    {
#ifdef PS4
        return new PS4PfNet();
#endif // ps4
        return new PlatformNet();
    }

    PlatformTool* PlatformFactory::createToolPlatform()
    {
#ifdef PS4
        return new PS4PfTool();
#endif //ps4
        return new PlatformTool();
    }
基礎(chǔ)功能具體平臺實(shí)現(xiàn)
#ifdef PS4
...
namespace Midas
{
    class PS4PfNet : public PlatformNet
    {
    public:
        //ps4實(shí)現(xiàn)https+post
        string https_post(string url, string params);
    ...

#endif //PS4

#ifdef PS4
namespace Midas
{
    //ps4實(shí)現(xiàn)AES加/解密
    class PS4PfTool : public PlatformTool
    {
    public:
        string aes_encrypt(string input, string key);
        string aes_decrypt(string input, string key);
    ...

#endif // PS4

總結(jié)


c++ sdk斷斷續(xù)續(xù)開發(fā)了一個多月魏保,重新?lián)炱餭++到完成ps4的移植測試,還是有點(diǎn)成就感的摸屠。之前雖然也重新寫了一套u(yù)nity sdk谓罗,但相對c++ sdk來說,難度還是低了很多餐塘。雖然也是重復(fù)造輪子,但在整個項(xiàng)目的過程中皂吮,碰到的問題遠(yuǎn)比預(yù)期的多戒傻,解決問題的快樂才是繼續(xù)的源動力。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜂筹,一起剝皮案震驚了整個濱河市需纳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌艺挪,老刑警劉巖不翩,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異麻裳,居然都是意外死亡口蝠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門津坑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妙蔗,“玉大人,你說我怎么就攤上這事疆瑰∶挤矗” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵穆役,是天一觀的道長寸五。 經(jīng)常有香客問我,道長耿币,這世上最難降的妖魔是什么梳杏? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮淹接,結(jié)果婚禮上秘狞,老公的妹妹穿的比我還像新娘。我一直安慰自己蹈集,他們只是感情好烁试,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拢肆,像睡著了一般减响。 火紅的嫁衣襯著肌膚如雪靖诗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天支示,我揣著相機(jī)與錄音刊橘,去河邊找鬼。 笑死颂鸿,一個胖子當(dāng)著我的面吹牛促绵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嘴纺,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼败晴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了栽渴?” 一聲冷哼從身側(cè)響起尖坤,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闲擦,沒想到半個月后慢味,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡墅冷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年纯路,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寞忿。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡感昼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出罐脊,到底是詐尸還是另有隱情定嗓,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布萍桌,位于F島的核電站宵溅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏上炎。R本人自食惡果不足惜恃逻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藕施。 院中可真熱鬧寇损,春花似錦、人聲如沸裳食。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诲祸。三九已至浊吏,卻和暖如春而昨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背找田。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工歌憨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人墩衙。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓务嫡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親漆改。 傳聞我的和親對象是個殘疾皇子心铃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 介紹 最近出現(xiàn)的 React Native 再次讓跨平臺移動端開發(fā)這個話題火起來了,曾經(jīng)大家以為在手機(jī)上可以像桌面...
    cosWriter閱讀 2,317評論 0 12
  • 顧雨周: 你用半生故事 釀一壺濁酒 深藏 發(fā)酵 敬遠(yuǎn)方人 在歲月如歌的長河里 懷揣故人的消息 仗劍走天涯路 不問來...
    啞鯨i閱讀 540評論 1 7
  • 不管是否閉上眼睛氛魁,黑暗總會時不時向他們扯開嗓子暮顺,嘶啞著喊出“i am here!”的宣言秀存; 盲道上的石子也挑釁著他...
    風(fēng)記扣閱讀 218評論 0 2
  • 《錦繡未央》終于迎來了大結(jié)局捶码,也讓多少未央迷們松了一口氣。在未央與南安王大婚的殿堂之上或链,所有人的愛恨糾葛也終于此處...
    余安1987閱讀 261評論 0 0
  • Unordered HTML List 用項(xiàng)目符號表示 An unordered list starts with...
    春暖花開奇奇樂樂閱讀 628評論 0 0