遷移一批老文章到掘金html
最近有點炒冷飯的嫌疑,不過確實之前沒有Git Or Blog的習慣,因此不少工做上的技術分享就存留在了電腦的文檔裏,如今仍是想從新整理一下,再分享出來。java
混編C++也是之前工做中須要用到的,因而被我炒冷飯翻了出來,不過確實有從新整理了一下android
各個平臺,各類語言,都會有不少開源的工具和庫文件方便你們學習和使用,但如C與C++這版經典的語言,不少底層的,算法型的庫都是用C++實現的,尤爲是不少人臉識別,圖形濾鏡算法,視頻處理算法,甚至底層圖形渲染OpenGLios
你們都知道C++的執行效率快,因此在高複雜度高算法層面的開發內容裏,大多都選擇使用C++來完成,我是作客戶端的,雖然不像作機器學習,大數據處理等在工做中須要普遍運用高效算法,但上面提到的人臉識別,圖形濾鏡算法,甚至視頻處理,還有不少遊戲內部須要的遊戲AI,都是有可能運用在咱們熟知的客戶端開發之中c++
C++是編譯型跨平臺的,C++的代碼編譯出來的二進制文件能夠在android,iOS,甚至WP上均可以正常運行,可謂是真·跨平臺web
說到跨平臺,確定很多人提起H5跨平臺呀,ReactNative跨平臺呀,這類一般屬於解釋型跨平臺,約定好一種腳本語言,底層輔助以各平臺的基礎實現,甚至底層就是藉助C++實現,經過底層解讀腳本語言,在運行時進行解釋實現邏輯,就比如webkit做爲瀏覽器的核心,JavaScriptCore做爲RN的核心,雖然開發中使用了js進行寫代碼,可是究其本質仍是在運行時解釋js在進行native執行的。js代碼並不參與編譯,這類跨平臺在編譯時參與編譯的,正是那套語法解釋器+NA底層代碼,他們或多或少仍是經過C++實現的objective-c
咱們作客戶端,核心模塊使用C++的緣由其實就是出自(2)(3)兩點,由於咱們的業務涉及極其複雜的文字排版,而不管是iOS平臺仍是安卓平臺,基礎排版是很難知足中文甚至我大天朝獨有政治要求的,想要實現勢必要在每一個平臺上分別封裝一套極度複雜的排版策略控制,所以咱們放棄了使用CoreText的基礎排版API(安卓上用啥排版不知道),而選擇用C++實現一套通用於兩個平臺的排版策略,固然在排版速度效率上也是要很高要求的算法
在iOS開發之中,OC代碼與C++代碼能夠完美的融合在一塊,何謂完美?你甚至能夠上一行剛敲完[NSArray objectAtIndex:xx]
(OC代碼)下一行就使用STL構建一個C++的List數組(C++代碼),他們之間能夠完美編譯,生成正常的程序,而且還能夠單步debug,隨時跟進一層一層的方法,剛剛單步跳出一個OC的messageSend,立刻就能夠單步跟進一個C++ Class的function,這之間沒有一點障礙,全部變量,指針,結構體,數據,均可以任意查看,兩者之間暢通無阻數組
爲何會這樣?由於C++與OC都徹底向下兼容C 全部的OC對象,雖然有ARC輔助內存管理,但他本質上仍是一個void *
,同理C++也同樣是void *
,OC之因此調用函數叫作發送消息,是由於封裝了層獨有的runtime機制(這機制仍是C的),但歸根結底每一個函數實體依然是一個IMP,依然是一個函數指針,這一點和C++也同樣,因此他們之間的銜接纔會如此通暢xcode
android混編C++,恩很麻煩,只能先編譯成so,兩個環境若是要交互,要先手寫一套JNI,把C++環境下的數據和java環境下的數據進行手動轉換,而且斷點調試無法斷點進入so內,想要debug調試,必須靠fwrite寫log到本地磁盤調試╮(╯_╰)╭
我之前搞過遊戲,作過C++內混編lua腳本,這倆互通更蛋疼,雖然lua的解釋器底層是用C寫的,可是全部的內存都是lua解釋器(或者叫虛擬機)內的數據,所以若是兩者要互通,也要寫一個通道來交換數據,這個交換數據,就是經過超級煩瑣的數據對齊,壓棧,出棧來互通。
前一陣子也學習了一些JSPatch,他其實能夠看作是js代碼混編Oc的模範工程,同lua同樣,整個js的運行環境也是依賴於JavaScriptCore提供的一套JS虛擬機來執行,他有着本身的上下文JSContext,雖然說簡單的通用數據,字符串,數組,字典,被JavaScriptCore自動的執行完了轉換,但一旦須要兩個環境交換獨有數據類型,例如js裏面的function,例如oc裏面的自定義NSObject,那麼就須要JSValue這個對象起到轉換和傳遞的做用
想要建立一個純C++類,你只須要建立.h開頭和.cpp開頭的文件,直接導入工程就好,若是須要使用一些C++的標準庫,能夠直接從Xcode導入libstdC++
若是你想建立一個能即識別C++又識別OC的對象,只須要照常建立一個.h 文件和.m文件,而後將.m文件重命名成.mm文件,就是告訴編譯器,這個文件能夠進行混編 — ObjectiveC++(名字是否是有點酷)
若是你想建立一個純OC類,那這還須要用我說麼?
如今你的工程裏,能夠有這三種文件存在,基本上就能夠知足咱們的混編需求了。
怎麼樣是否是很想趕快試試了?
個人例子會一步一步來,甚至有的步驟中多是錯誤的代碼,給你們展現完錯誤的代碼後,進行說明,再放上正確的代碼,
代碼也不全是完整代碼
CppObject.h C++的頭文件 .cpp文件留空,先不寫邏輯
#include <string>
class CppObject {
public:
void ExampleMethod(const std::string& str){};
// constructor, destructor, other members, etc.
};
複製代碼
OCObject.h OC的頭文件 .m文件先改成.mm,但先不寫邏輯
#import <Foundation/Foundation.h>
//#import "CppObject.h"
@interface OcObject : NSObject {
CppObject* wrapped;
}
@property CppObject* wrapped2;
- (void)exampleMethodWithString:(NSString*)str;
// other wrapped methods and properties
@end
複製代碼
頭文件準備完畢,實現文件,我先不寫邏輯,先跑一下看看會有什麼問題?
跑完了之後會編譯報錯,報錯的緣由很簡單,你在OCObject.h
中引用了C++的頭文件,xcode不認識,沒法編譯經過。
咦?剛剛不是說好了C++和OC無縫互通了麼,這咋又不認識了?緣由很簡單,咱們經過修改.m爲.mm文件,能讓編譯器xcode知道這是一個混編文件,可是我可沒說修改.h爲.hh文件喲,是這樣的,對於xcode來講,能夠認識.mm的混編語法,可是不認識.h文件中的混編語法,若是.h全都是C++的寫法,沒有問題,若是.h全都是OC的寫法,沒有問題,若是.h裏面有C++又有OC?那妥妥的有問題(.h中引入的其餘頭文件也算在內)
怎麼處理呢?兩個辦法
不在.h裏寫混編了,那我移到.mm裏唄~~~
不讓我寫c++?ok,我寫C,反正寫C是沒錯的,因此老子寫void *
寫id
這裏的例子我先寫到.mm文件裏
#import "OcObject.h"
#import "CppObject.h"
@interface OcObject () {
CppObject* wrapped;
}
@end
@implementation OcObject
- (void)exampleMethodWithString:(NSString*)str
{
// NOTE: if str is nil this will produce an empty C++ string
// instead of dereferencing the NULL pointer from UTF8String.
std::string cpp_str([str UTF8String], [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
wrapped->ExampleMethod(cpp_str);
}
複製代碼
這不~妥了沒問題了~,咱們再去補上CPP文件中的函數實現,隨便寫個printf(),輸出個string,例子就完成了
首先咱們要打造一個C++的環境
--AntiCppObject.h
#include <iostream>
class AntiCppObject {
public:
AntiCppObject();
void ExampleMethod(const std::string& str){};
// constructor, destructor, other members, etc.
};
--AntiCppObject.cpp
#include "AntiCppObject.h"
AntiCppObject::AntiCppObject()
{
}
複製代碼
而後咱們再準備一個OC類接入C++,m文件我就不補充完了,隨便寫個NSLog就好
--AntiOcObject.h
#import <Foundation/Foundation.h>
@interface AntiOcObject : NSObject
- (void)function;
@end
複製代碼
如今打算接入C++環境了,首先先把.CPP改爲.mm文件,妥妥噠
而後修改頭文件
--AntiCppObject.h
#import "AntiOcObject.h"
class AntiCppObject {
AntiOcObject* AntiOc;
public:
AntiCppObject();
void ExampleMethod(const std::string& str){};
// constructor, destructor, other members, etc.
};
複製代碼
通過了剛纔的例子,看到這應該立馬反應過來,這不對,頭文件不能混編,會報錯的。那應該怎麼作呢?
作法仍是上面提到的,要麼void *
,要麼想辦法把定義寫在.mm文件裏,老規矩,void *
先不提,咱們先在.h中寫個結構體,藏起來那個oc的對象,在mm文件中進行聲明
--AntiCppObject.h
#include <iostream>
struct sthStruct;
class AntiCppObject {
sthStruct* sth;
public:
AntiCppObject();
void function();
// constructor, destructor, other members, etc.
};
---AntiCppObject.cpp
#include "AntiCppObject.h"
#import "AntiOcObject.h"
struct sthStruct {
AntiOcObject* oc;
};
AntiCppObject::AntiCppObject()
{
AntiOcObject* t =[[AntiOcObject alloc]init];
sth = new sthStruct;
sth->oc = t;
}
void AntiCppObject::function()
{
[this->sth->oc function];
}
複製代碼
你看這樣就實現了在C++中調用OC
void *
指針,當作橋樑,來回在兩個環境間傳遞(上面的例子沒有體現)--OcObject.mm
-(id)init
{
self = [super init];
if (self) {
wrapped = new CppObject();
}
return self;
}
-(void)dealloc
{
delete wrapped;
}
--AntiCppObject.mm
AntiCppObject::AntiCppObject()
{
AntiOcObject* t =[[AntiOcObject alloc]init];
sth = new sthStruct;
sth->oc = t;
}
AntiCppObject::~AntiCppObject()
{
if (sth) {
[sth->oc release];//arc的話,忽略掉這句話不寫
}
delete sth;
}
複製代碼
這個例子告訴咱們什麼?
若是咱們經過oc的方式建立出來的,他的內存天然歸OC管理,若是是mrc,請使用release,若是是arc,只要置空,天然會自動釋放
若是咱們經過C++的方式,構造函數new出來的,那咱們就要手動的使用析構函數就釋放他
其實不少事情原理是同樣的
剛纔的例子中,我雖然頻繁提到void *
可是並無詳細加以說明,神奇的東西應該放在最後
首先說一下id這個很特殊的東西
前面第二個例子,咱們是藉助一個結構體struct把oc代碼隱藏到.mm文件裏,那麼咱們能夠不借助struct麼?固然能夠
--AntiCppObject.h
#include <iostream>
struct sthStruct;
class AntiCppObject
{
id sthoc;
sthStruct* sth;
public:
AntiCppObject();
~AntiCppObject();
void function();
// constructor, destructor, other members, etc.
};
--AntiCppObject.cpp
#include "AntiCppObject.h"
#import "AntiOcObject.h"
struct sthStruct
{
AntiOcObject* oc;
};
AntiCppObject::AntiCppObject()
{
AntiOcObject* t =[[AntiOcObject alloc]init];
sth = new sthStruct;
sth->oc = t;
sthoc = [[AntiOcObject alloc]init];
}
AntiCppObject::~AntiCppObject()
{
if (sth) {
[sth->oc release];
[sthoc release];
}
delete sth;
}
void AntiCppObject::function()
{
[this->sth->oc function];
[this->sthoc function];
}
複製代碼
能夠看到這個例子中,那個struct還在,舊的方案仍然保留,可是咱們在頭文件裏寫了一個id類型,xcode編譯器在全都是C++代碼的.h文件裏雖然不認識oc對象,可是實際上是認識id的,咱們藉助這個id,就能夠不借助struct隱藏oc對象聲明瞭
終於說到這個void *
了,首先咱們寫個oc對象,能夠持有void *
,寫個C++也能夠持有,甚至咱們不寫任何對象,在寫一個static的C代碼,也能夠在一個全局控件保存一個void *
對象,正是這個void *
對象,能夠靈活的組合出各類混編用法
void *
是什麼?就是指針的最本來形態,利用它咱們能夠各類花式的進行混編OC與C++
惟一須要注意的就是id
(即oc對象)與void *
的轉換,要知道arc是有內存管理的,而C++是沒有的,若是都一股腦的隨便兩者轉來轉去,那內存管理到底該如何自動進行釋放?(mrc下兩者轉換是不須要特別處理的)
所以Arc下兩者進行轉換常常伴隨着一些強轉關鍵字
實際上是從內存安全性上作的轉換修飾符,相關搜索id與void *
轉換能夠自行查閱,並且在iOS的core fundation開發中很是常見,簡單的說就是bridgeretained會把內存全部權同時歸原對象和變換後的對象持有(只對變換後的對象作一次reatain),bridgetransfer會把內存全部權完全移交變換後的對象持有(retain變換後的對象,release變換前的對象)
這裏面我會貼一段代碼,這段代碼只爲展現一些使用,所以,設計上可能有點繞,和扯淡,只爲展現混編
--TrickInterface.h
typedef void (*interface)(void* caller, void *parameter);
--TrickOC.h
#import <Foundation/Foundation.h>
#import "TrickInterface.h"
@interface TrickOC : NSObject
{
int abc;
}
-(int)dosthing:(void*)param;
@property interface call;
@end
--TrickOC.m
#import "TrickOC.h"
#import "TrickInterface.h"
void MyObjectDoSomethingWith(void * obj, void *aParameter)
{
[(__bridge id) obj dosthing:aParameter];
}
@implementation TrickOC
-(id)init
{
self = [super init];
if (self) {
self.call = MyObjectDoSomethingWith;
}
return self;
}
-(int)dosthing:(void *)param
{
NSLog(@"111111");
return 0;
}
@end
--TrickCpp.cpp
#include "TrickCpp.h"
#include "TrickInterface.h"
TrickCpp::TrickCpp(void* oc,interface call)
{
myoc = oc;
mycall = call;
}
void TrickCpp::function()
{
mycall(myoc,NULL);
}
--TrickCpp.h
#include <iostream>
#include "TrickInterface.h"
class TrickCpp
{
void* myoc;
interface mycall;
public:
TrickCpp();
TrickCpp(void* oc,interface call);
~TrickCpp();
void function();
// constructor, destructor, other members, etc.
};
--使用樣例
TrickOC* trickoc = [[TrickOC alloc]init];
void* pointer = (__bridge void*)trickoc;
TrickCpp * trick = new TrickCpp(pointer,trickoc.call);
trick->function();
複製代碼
這段代碼中首先在全局區域聲明瞭一個全局的cfunctioninterface
,起名叫接口顧名思義是打算把它當作C++傳遞OC的通道,全部跨C++回調OC都經過這個通道來通訊
在TrickOc.m文件中也實現了這一個全局的cfunctionMyObjectDoSomethingWith
,這個cfunction實體就是咱們的接口通道
當建立TrickCpp的時候,將以建立好的TrickOc和這個cfunction一併傳入,當Cpp須要調用Oc的時候,直接使用cfunction與TrickOc的對象