日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

混合编程黑科技:跨语言编程问题迎刃而解的3个要点

發(fā)布時間:2025/3/8 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 混合编程黑科技:跨语言编程问题迎刃而解的3个要点 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

首先,混合編程是什么鬼?


這個世界上編程語言真不少,光常用就有:C、C++、Java、C#、Objective-C、Javascript、Python、Lua、Swift等等等,遑論一些專業(yè)性比較強的DSL了。而且軟件的應(yīng)用場景也數(shù)不勝數(shù):嵌入式設(shè)備、后端服務(wù)器、桌面程序/GUI、移動端平臺、Web、并行計算……


那么,如果某個場景下光靠一種語言無法滿足業(yè)務(wù)需求該怎么辦;亦或是某個依賴的庫早已有其他語言編寫的成熟可靠版本,重寫完全劃不來;再者有可能每一個開發(fā)者有自己偏好的開發(fā)語言,但卻不得不一起協(xié)作?

我想,解決這些問題的最好方式,就是采用混合編程,也就是使用不止一種程序設(shè)計語言來開發(fā)應(yīng)用程序這種方案。

那么,混合編程背后的原理是什么?


編程語言,以我淺薄的認識來說,大概本質(zhì)上是對機器語言(1和0)的高度抽象,是基于不同思維方向和設(shè)計模式的抽象,所以才會被人為區(qū)分成過程式、面向?qū)ο蟆⒑瘮?shù)式等等不同類別。那么,這種甚至是基于不同思維層面而發(fā)明的語言,看似應(yīng)該老死不相往來,如何才能相互調(diào)用呢?

幸好,計算機科學(xué)領(lǐng)域中有一個超重要又很實用的概念,那就是“分層”!有一句話怎么說來著?“計算機科學(xué)領(lǐng)域的任何問題都可以通過增加一個間接的中間層來解決”。


以上面這張粗糙的圖來說,假設(shè)存在語言1和語言2基于重要的分層概念,對于這兩種語言的支持,肯定是從硬件、驅(qū)動、操作系統(tǒng)、編譯器+運行時等等一層層疊加上來的,那么,如果想要跨這兩種語言進行調(diào)用,要怎么辦呢?

這時候,可以把每一種語言的運行支持比作一棟樓房(原諒我這不甚恰當?shù)念惐?#xff0c;但有沒有發(fā)現(xiàn)其實二者架構(gòu)神似?),那么現(xiàn)在想要達到的目標好比就是,把一條消息從一號樓的頂樓送到二號樓的頂樓,這完全可以用我們?nèi)粘I钪械暮唵纬WR搞定對不對?很顯然,要做的是,確定這兩棟樓之間有沒有天橋連接(一樓也可以被認為是0高度的天橋……),如果一號樓的樓層x與二號樓的樓層y之間存在一條路徑的話,信使就可以首先從一號樓的頂樓下到樓層x,然后通過天橋到達二號樓的樓層y,最終上到二號樓的頂樓就達成了我們的目標!

上述這通看似廢話的介紹暗示了什么呢?不就等同于,任意兩種語言,若是在其運行環(huán)境支持的某一層上可以通過某種渠道某種技術(shù)可以進行無礙溝通的話,從原理上來說,就實現(xiàn)了二者相互調(diào)用的目的嗎(似乎發(fā)現(xiàn)了什么了不得的東西……)

然后,來看看混合編程的具體案例

閑話休表,介紹一下本文的主角,在iOS開發(fā)中遇到的,混合編程的兩種典型案例——Objective-C與javascript,以及Objective-c與lua。

關(guān)于Javascript(后面均簡稱JS)本文不再贅言,主要講一講iOS端有什么方式可以執(zhí)行JS代碼呢,相信從事iOS開發(fā)的諸位都了解UIWebView有如下方法:

  • (nullable NSString?)stringByEvaluatingJavaScriptFromString:(NSString?)script;?或者WKWebView的

  • (void)evaluateJavaScript:(NSString?)javaScriptString completionHandler:(void (^?nullable)(nullable id, NSError?__nullable error))completionHandler; `` 都可以來執(zhí)行一段JS腳本代碼字符串。當然,這是單向的調(diào)用,如果想要實現(xiàn)反向的調(diào)用怎么辦呢? 為此在iOS 7中正式引入JavaScriptCore框架,可以大幅度簡化Objective-C與JS之間的相互調(diào)用過程,幾段簡單的示例代碼如下

//數(shù)值傳遞JSContext *context = [[JSContext alloc] init]; JSValue *jsVal = [context evaluateScript:@"21+7"]; int iVal = [jsVal toInt32]; NSLog(@"JSValue int: %d", iVal); //輸出28//數(shù)據(jù)結(jié)構(gòu)傳遞JSContext *context = [[JSContext alloc] init]; [context evaluateScript:@"var arr = [21, 7 , 'www.163.com'];"]; JSValue *jsArr = context[@"arr"]; // Get array from JSContext NSLog(@"JS Array: %@; Length: %@", jsArr, jsArr[@"length"]); //輸出為 JS Array: 21,7,www.163.com Length: 3jsArr[1] = @"blog"; // Use JSValue as array jsArr[7] = @7; NSLog(@"JS Array: %@; Length: %d", jsArr, [jsArr[@"length"] toInt32]); //輸出 JS Array: 21,blog,www.163.com,,,,,7; Length: 8NSArray *nsArr = [jsArr toArray]; NSLog(@"NSArray: %@", nsArr);/* 輸出NSArray: (21,blog,"www.163.com","<null>","<null>","<null>","<null>",7 ) *///方法引入JSContext *context = [[JSContext alloc] init]; context[@"log"] = ^() { ? ?NSLog(@"+++++++Begin Log+++++++"); ? ?NSArray *args = [JSContext currentArguments]; ? ?for (JSValue *jsVal in args) { ? ? ? ?NSLog(@"%@", jsVal);}JSValue *this = [JSContext currentThis]; ? ?NSLog(@"this: %@",this); ? ?NSLog(@"-------End Log-------"); };[context evaluateScript:@"log('wayne', [7, 21], { hello:'world', js:100 });"];/*輸出為 +++++++Begin Log+++++++ wayne 7,21 [object Object] this: [object GlobalObject] -------End Log------- */

喔!看起來真的很簡單,所有JS相關(guān)的變量對象都被封裝成了JSContext和JSValue類型,看起來既清爽又簡約。但這好比是Apple在剛才那兩棟樓的樓頂上各搭建了一個黑黝黝的通道入口,開發(fā)者可以把東西扔進去,在另一個樓頂?shù)耐ǖ莱隹诰涂梢暂p輕松松收到了,簡直是魔法般的存在!但這種黑盒背后隱藏了大量的細節(jié),而那句話怎么說來著?細節(jié)是魔鬼!

這種黑科技背后的實現(xiàn)原理是什么呢


既然不太甘心,那么不妨也自己動手嘗試下如何實現(xiàn)從基礎(chǔ)這一套神奇的通道,在此我選擇的就是Objective-C和Lua這對組合了。首先簡要介紹下主角Lua,它是一種優(yōu)雅又簡單易學(xué)的編程語言,支持自動內(nèi)存管理、詞法作用域、閉包、迭代器、協(xié)程(coroutine)、尾調(diào)用等特性,其變量引用規(guī)則采用詞法作用域或者說靜態(tài)作用域方式,而且數(shù)據(jù)結(jié)構(gòu)特別簡潔明了(只有table這一種,兼任數(shù)組和哈希表二種角色)。

下面簡單的代碼可以幫助大家熟悉一下Lua:

local saySomeThing = function()print("holy shit!")endlocal t = {10, "hello world!"} t["func"] = saySomeThing local b = 100function add(one, another) ? ?return one + anotherendlocal result = add(t[1], b) print(" t[1] + b = ", result)//輸出110t["func"]() //輸出 holy shit!

另外,Lua具有很好的可擴展性和可嵌入特性,所以被稱為膠水語言(glue language)。這么贊譽它是因為原始提供了設(shè)計良好的C API,可供開發(fā)者自行搭建C和Lua世界的通道。

那么,開始動手吧。 首先,假設(shè)我們需要把3個C函數(shù)導(dǎo)入到Lua去被調(diào)用,那么就得到了如下所示代碼。他們似乎看起來有共同之處,那么就是除了方法名之外的函數(shù)簽名是完全一樣的,即傳入?yún)?shù)一定是lua_State?類型,返回值一定是static int類型。沒錯,這就是Lua世界給出的一個強制約定,也就是所有滿足如下簽名要求的C函數(shù)才有被Lua的運行時接納的資格。當然這種約束也是有目的的:傳入的lua_State?類型變量即是Lua的運行上下文對象,可以從中提取從lua世界傳入的各種參數(shù);而返回的static int類型數(shù)值則是代表從C的世界中往Lua的世界中傳入了多少個數(shù)據(jù)。

//C functionsstatic int saySomething(lua_State *L){ ? ?if (!L) { ? ? ? ?return 0;}lua_pushstring(L, "Now Native Talking"); ? ?return 1; }static int add(lua_State *L){ ? ?double first = lua_tonumber(L, 1); ? ?double second = lua_tonumber(L, 2);lua_pushnumber(L, first + second); ? ?return 1; }static int transformToUpper(lua_State *L){ ? ?const char *str = lua_tostring(L, 1); ? ?if (!str || strlen(str) == 0) {lua_pushstring(L, ""); ? ? ? ?return 1;} ? ?char *transformed = NULL;size_t len = strlen(str);transformed = (char*)malloc(sizeof(char)*len + 1); ? ?for (size_t i = 0 ; i < len; i ++) {transformed[i] = toupper(str[i]);}transformed[len] = '\0';lua_pushstring(L, transformed);lua_pushstring(L, str); ? ?return 2; }

然后讓我們把這3個方法導(dǎo)入到Lua的世界中去。

//C 代碼static const struct luaL_reg customLib[] = {{"add", add},{"saySomething", saySomething},{"transformToUpper", transformToUpper} };.....self.state = luaL_newstate();luaL_openlibs(self.state); luaL_register(self.state, "native", customLib);

而通過這個lua上下文對象執(zhí)行的lua代碼中就可以這么寫:

-- lua代碼,測試導(dǎo)入lua世界的native functions -- call function without returnsnative.saySomething() -- call function with a single number-typed returnlocal a = 10local b = 100local result = native.add(a, b)print(" a + b = ", result) --輸出 ?a + b = 110-- call function with two string-typed returns ? ? local str = "hello world!"local transformed, original = native.transformToUpper(str)print("original:", original, " transformed:", transformed) --輸出original: ? ?hello world! ? ? transformed: ? ?HELLO WORLD!

看吧,現(xiàn)在已經(jīng)成功地在C這棟“樓”和lua這棟“樓”之間搭建了一座(簡陋的)通道了!當然,Objective-C由于本身是C語言的一個超集,所以可以利用C的中間層,實現(xiàn)Objective-C實現(xiàn)的類成員方法和Lua的相互調(diào)用,具體步驟對諸君而言都沒什么難度,故在此不再贅言。

靜態(tài)binding,還是動態(tài)binding?


上述這種通過明確的接口列表注冊要導(dǎo)入Lua世界的C函數(shù)的方式,我們稱其為靜態(tài)binding。既然有靜態(tài)的,那當然也會有動態(tài)的binding——而這仰賴于Objective-C強大的runtime特性,最典型的一個框架就是Wax。引用Wax之后,就避免了Objective-C的方法都必須預(yù)先導(dǎo)入才能從Lua中調(diào)用的繁瑣,而是直接可以在lua腳本中編寫如下代碼就可以為Wax正確解析并執(zhí)行:

-- lua code view = UIView:initWithFrame(CGRect(0, 0, 320, 100))-- all methods available to a UIView object can be accessed this way view:setBackgroundColor(UIColor:redColor())

亦或

-- Created in "MyController.lua"-- -- Creates an Objective-C class called MyController with UIViewController-- as the parent. This is a real Objective-C object, you could even -- reference it from Objective-C code if you wanted to. waxClass{"MyController", UIViewController}function init()-- to call a method on super, simply use self.superself.super:initWithNibName_bundle("MyControllerView.xib", nil) ?return selfendfunction viewDidLoad()-- Do all your other stuff hereend

甚至支持block:

-- lua code UIView:animateWithDuration_animations_completion(1, toblock( ? ? ? ?function()label:setCenter(CGPoint(300, 300))end), ? ?toblock(function(finished)print('lua animations completion ' .. tostring(finished))end,{"void", "BOOL"}))

是不是和Objective-C代碼在函數(shù)傳遞、普通和匿名方法寫法上存在一些區(qū)別之外,幾乎一模一樣?這就是將Lua的元方法(Meta Method)特性和Objective-C強大的運行時能力組合起來得到的。

元方法+運行時調(diào)用這對組合好像很厲害


從名字來看,“元”這個字似乎是很強力的一種修飾,譬如元帥、元首、元氣幾個詞莫不如是,那么元方法呢,其實也是同樣。元方法其實是為為Lua中table對象設(shè)置的元表中關(guān)聯(lián)方法的總稱(基于lua5.1版本),其中很重要的兩個就是?index 以及?newindex兩個方法。通過key從table對象中獲取關(guān)聯(lián)的value失敗時,index方法會被觸發(fā);而newindex則是在向table對象首次設(shè)置某key-value的鍵值對的時候會被觸發(fā)。自然,可以設(shè)想在lua代碼執(zhí)行的時候,碰到不存在的變量譬如UIView時可以觸發(fā)全局的元方法,而如果其元方法乃是native的C 函數(shù),則可以將"UIView"這個key傳入到native中區(qū)。 然后利用runtime方法譬如 NSClassFromString就可以獲取的真正的UIView類;而調(diào)用的方法,可以通過同樣的方式傳入native,再利用 instanceMethodForSelector/methodForSelector/methodSignatureForSelector等獲得相應(yīng)的實現(xiàn),并發(fā)起調(diào)用(其實為了兼容64位系統(tǒng),最終是把函數(shù)調(diào)用封裝為NSInvocation調(diào)用并去派發(fā)的,此處暫不多做介紹)。

在native中完成了類實例方法/類方法的調(diào)用獲得了返回值之后,要怎樣遞送回lua的世界中呢?通常不同語言對于基礎(chǔ)數(shù)值類型(primitive value)都有相應(yīng)支持,以之前自定義的3個C函數(shù)為例,會發(fā)現(xiàn)其中有這樣的邏輯:

lua_pushstring(L, "Now Native Talking");?lua_pushnumber(L, first + second);

很明顯,是通過為不同基礎(chǔ)類型變量提供顯式傳遞方法以便將值傳入到lua的世界中去。如果是Objective-C的類實例對象呢,為此,lua單獨提供了userdata類型,可以將native對象封裝為可以在lua世界中存在變量。

現(xiàn)在,我們可以實現(xiàn)在lua中觸發(fā)native對象的生成,并將其封裝成userdata傳遞會lua的世界,是不是就天下太平了?很明顯,這里還存在一個問題,那就是同一個對象,可能同時在native和lua都被引用著,那么它的生命周期該如何控制呢?畢竟兩個世界是遵循著不同的內(nèi)存管理策略——Objective采用的是引用計數(shù)(ARC/MRC),而lua則是GC。解決這個問題同樣是利用了lua元方法gc,若為userdata類型變量關(guān)聯(lián)該方法,那么在其所有引用都已斷開的時候,即lua將要對其進行對象銷毀和內(nèi)存回收時,會觸發(fā)gc的調(diào)用;如此一來,可以在__gc的C函數(shù)實現(xiàn)中將native對象的引用計數(shù)做相應(yīng)遞減,代表lua世界也已不再引用該對象;可以按照Objective-C的正常規(guī)范,在其引用計數(shù)歸零時觸發(fā)native的回收機制。

總的來說,其簡略架構(gòu)圖如下:?



靜態(tài)、動態(tài)binding對比


同樣是是實現(xiàn)不同語言之間的相互調(diào)用,但靜態(tài)binding和動態(tài)binding采用完全不同的設(shè)計思想,設(shè)計上的差異同樣會帶來使用上的區(qū)別。

首先很明顯的是,如果是靜態(tài)binding,那么每一個導(dǎo)入另一種語言世界的方法都需要手動實現(xiàn)(但有些工具可以極大簡化這種繁瑣,例如SWIG);而動態(tài)binding則利用了Objective-C強大的runtime特性和lua的元方法特性,可以省卻方法手動導(dǎo)入這一步驟。但動態(tài)binding這種便利總是有代價的,最大的問題就是性能消耗比靜態(tài)binding要高出不少,這也是ColorTouch移動端UI開發(fā)框架采用靜態(tài)binding為主,動態(tài)binding為輔的設(shè)計方案的主要原因。

混合編程的核心問題

上面啰啰嗦嗦說了好多,其實歸結(jié)起來,若要實現(xiàn)混合編程,最主要的幾個核心問題就是:

  • 不同語言世界之間數(shù)值、對象、變量的轉(zhuǎn)化;

  • 為另一個語言函數(shù)傳入的參數(shù),以及從另一個語言世界獲得方法返回值的處理;

  • 跨語言世界存在的對象,其生命周期管理。

  • 總的來說,這三點就是我個人認為,如果要處理跨語言的編程問題,要最為關(guān)注的要點。把握、解決了這些要點,其他的問題相信就會迎刃而解!

    ·?EDN?·

    作者:網(wǎng)易杭州研究院 ·?魏煒

    網(wǎng)易云信|IM快速開發(fā)黑科技

    ID:neteaseim ?長按識別,關(guān)注精彩

    與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖

    總結(jié)

    以上是生活随笔為你收集整理的混合编程黑科技:跨语言编程问题迎刃而解的3个要点的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。