需求來(lái)源
Open-IM 是由前微信技術(shù)專(zhuān)家打造的全開(kāi)源、永久免費(fèi)、無(wú)限制的即時(shí)通訊組件。Open-IM 包括 IM 服務(wù)端和客戶(hù)端 SDK,實(shí)現(xiàn)了高性能、輕量級(jí)、易擴(kuò)展等重要特性。開(kāi)發(fā)者通過(guò)集成 Open-IM 組件,并私有化部署服務(wù)端,可以將即時(shí)通訊、實(shí)時(shí)網(wǎng)絡(luò)能力免費(fèi)、快速集成到自身應(yīng)用中,并確保業(yè)務(wù)數(shù)據(jù)的安全性和私密性。
OpenIM包括Server和SDK,兩者都是采用golang實(shí)現(xiàn)的,移動(dòng)端通過(guò)gomobile生成代碼,再加上對(duì)應(yīng)的插件,這樣能適應(yīng)多個(gè)前端開(kāi)發(fā)框架,無(wú)論是原生的iOS、Android還是跨端開(kāi)發(fā)的Flutter、uniapp、react native、cordova等。OpenIM SDK 要用在pc端electron框架中,先解決C調(diào)用golang的問(wèn)題,再打通nodejs調(diào)用C /C++,當(dāng)然這里還涉及到各種回調(diào)函數(shù)。

網(wǎng)上有很多例子告訴你怎么從Go語(yǔ)言調(diào)用C /C++語(yǔ)言的函數(shù),但少文章有告訴你,如何從C /C++語(yǔ)言函數(shù)中調(diào)用Golang語(yǔ)言寫(xiě)的函數(shù)。本文通過(guò)實(shí)際代碼,來(lái)展示兩個(gè)能力:(1)golang如何編譯成動(dòng)態(tài)庫(kù)so (2)C /C++如何調(diào)用golang函數(shù) (3)golang如何調(diào)用C /C++的回調(diào)函數(shù)。
goland代碼及注意事項(xiàng)
a.go代碼:
package main
/*
#cgo CFLAGS: -I .
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef void (*callback)(void *,int);
extern void c_callback (void *,int);
extern callback _cb;
*/
import "C"
import (
?? "sync"
?? "unsafe"
?? "time"
?? "fmt"
?? "encoding/json"
)
var mutex? sync.Mutex
type HelloWorld? interface {
? ?? OnSuccessCallback(result string)
}
func doSomething(worker HelloWorld, input string){
?? //模擬異步
?? go func() {
? ? ?? fmt.Println("func doSomething start...")
? ? ?? time.Sleep(time.Duration(2)*time.Second)
? ? ?? fmt.Println("func doSomething end...")
? ? ?? result := "do something successful"
? ? ?? worker.OnSuccessCallback(result)
?? }()
}
type SomeHelloWorld struct {
?? cb C.callback
?? input string
?? passBackData string
}
type CallbackOutput struct {
?? Data ? ? string ? `json:"data"`
?? Output ? string `json:"output"`
}
func(t *SomeHelloWorld) OnSuccessCallback(result string){
?? var callbackOutput CallbackOutput
?? callbackOutput.Data = t.passBackData
?? callbackOutput.Output = result
?? jsonStr, err := json.Marshal(callbackOutput)
?? if err != nil {
? ? ?? fmt.Println("err: ", err.Error())
?? }
?? fmt.Println("json: ", string(jsonStr))
?? var cmsg *C.char = C.CString(string(jsonStr))
?? var ivalue C.int = C.int(len(jsonStr))
?? defer C.free(unsafe.Pointer(cmsg))
?? //C._cb全局變量,再回調(diào)時(shí)加鎖互斥
?? mutex.Lock()
?? defer mutex.Unlock()
?? C._cb = t.cb
?? C.c_callback(unsafe.Pointer(cmsg), ivalue)
}
//export? doSomethingCallback
func doSomethingCallback(p C.callback, input *C.char, data *C.char){
?? var one SomeHelloWorld
?? one.cb = p
?? one.passBackData = C.GoString(data)
?? one.input = C.GoString(input)
?? fmt.Println("one: ", one)
?? doSomething(&one, one.input)
}
func main() {
}
在代碼塊,有幾個(gè)點(diǎn)需要注意:
(1)package main? 這個(gè)必須是main
(2)這個(gè)注釋不能少,原封不動(dòng)復(fù)制粘貼即可
/* #cgo CFLAGS: -I . #include <stdio.h> #include <string.h> #include <stdlib.h> typedef void (*callback)(void *,int); extern void c_callback (void *,int); extern callback _cb; */
(3)對(duì)于提供給C調(diào)用的函數(shù),在函數(shù)上一行加上export? 例如://export? doSomethingCallback
(4) main函數(shù)保留
func main() { }
b.go代碼
package main
/*
#include <stdio.h>
typedef void (*callback)(void *,int);
callback _cb;
void c_callback(void* p,int i)
{
?? _cb(p,i);
}
*/
import "C"
原封不動(dòng)保存就可以了。
編譯成動(dòng)態(tài)庫(kù)
go build -o libcallback.so? -buildmode=c-shared a.go b.go

生成libcallback.h 和libcallback.so
C代碼調(diào)用
#include <stdio.h>
#include <unistd.h>
#include "libcallback.h"
void gocallback(void* s,int len) {
?? printf("%s\n", (char*)s);
}
int main() {
?? const char* a = "cstring input";
?? doSomethingCallback(gocallback, (char*)"cstring hello", (char*)a);
?? pause();
}
編譯
gcc -v? m.cpp? -o m ./libcallback.so
生成可執(zhí)行程序 m

輸入./m 執(zhí)行,C調(diào)用golang的doSomethingCallback函數(shù),并在此函數(shù)回調(diào)C的gocallback函數(shù),完成了C->golang->C

小節(jié)
C和golang互調(diào)能力打通,這樣,對(duì)于采用C/C++開(kāi)發(fā)的項(xiàng)目,如果某些業(yè)務(wù)特性不追求性能上的機(jī)制,可以通過(guò)golang實(shí)現(xiàn),這樣達(dá)到了開(kāi)發(fā)效率和執(zhí)行效率的平衡,對(duì)業(yè)務(wù)開(kāi)發(fā)非常有幫助。
通過(guò)深度調(diào)用機(jī)制分析,無(wú)論是Go調(diào)用C,還是C調(diào)用Go,其需要解決的核心問(wèn)題其實(shí)都是提供一個(gè)C/Go的運(yùn)行環(huán)境來(lái)執(zhí)行相應(yīng)的代碼。Go的代碼執(zhí)行環(huán)境就是goroutine以及Go的runtime,而C的執(zhí)行環(huán)境需要一個(gè)不使用分段的棧,并且執(zhí)行C代碼的goroutine需要暫時(shí)地脫離調(diào)度器的管理。

要達(dá)到這些要求,運(yùn)行時(shí)提供的支持就是切換棧,以及runtime.entersyscall。在Go中調(diào)用C函數(shù)時(shí),runtime.cgocall中調(diào)用entersyscall脫離調(diào)度器管理。runtime.asmcgocall切換到m的g0棧,于是得到C的運(yùn)行環(huán)境。在C中調(diào)用Go函數(shù)時(shí),crosscall2解決gcc編譯到6c編譯之間的調(diào)用協(xié)議問(wèn)題。cgocallback切換回goroutine棧。runtime.cgocallbackg中調(diào)用exitsyscall恢復(fù)Go的運(yùn)行環(huán)境
OpenIMgithub開(kāi)源地址:
https://github.com/OpenIMSDK/Open-IM-Server
OpenIM官網(wǎng) : https://www.rentsoft.cn
OpenIM官方論壇: https://forum.rentsoft.cn/
更多技術(shù)文章:
開(kāi)源OpenIM:高性能、可伸縮、易擴(kuò)展的即時(shí)通訊架構(gòu)https://forum.rentsoft.cn/thread/3
【OpenIM原創(chuàng)】簡(jiǎn)單輕松入門(mén) 一文講解WebRTC實(shí)現(xiàn)1對(duì)1音視頻通信原理https://forum.rentsoft.cn/thread/4
【OpenIM原創(chuàng)】開(kāi)源OpenIM:輕量、高效、實(shí)時(shí)、可靠、低成本的消息模型https://forum.rentsoft.cn/thread/1