【OpenIM原創】C/C++調用golang函數,golang回調C/C++函數

需求來源

Open-IM 是由前微信技術專家打造的全開源、永久免費、無限制的即時通訊組件。Open-IM 包括 IM 服務端和客戶端 SDK,實現了高性能、輕量級、易擴展等重要特性。開發者通過集成 Open-IM 組件,并私有化部署服務端,可以將即時通訊、實時網絡能力免費、快速集成到自身應用中,并確保業務數據的安全性和私密性。

OpenIM包括Server和SDK,兩者都是采用golang實現的,移動端通過gomobile生成代碼,再加上對應的插件,這樣能適應多個前端開發框架,無論是原生的iOS、Android還是跨端開發的Flutter、uniapp、react native、cordova等。OpenIM SDK 要用在pc端electron框架中,先解決C調用golang的問題,再打通nodejs調用C /C++,當然這里還涉及到各種回調函數。

網上有很多例子告訴你怎么從Go語言調用C /C++語言的函數,但少文章有告訴你,如何從C /C++語言函數中調用Golang語言寫的函數。本文通過實際代碼,來展示兩個能力:(1)golang如何編譯成動態庫so (2)C /C++如何調用golang函數 (3)golang如何調用C /C++的回調函數。

goland代碼及注意事項

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全局變量,再回調時加鎖互斥

?? 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() {

}

在代碼塊,有幾個點需要注意:

(1)package main? 這個必須是main

(2)這個注釋不能少,原封不動復制粘貼即可

/* #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)對于提供給C調用的函數,在函數上一行加上export? 例如://export? doSomethingCallback

(4) main函數保留

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"

原封不動保存就可以了。

編譯成動態庫

go build -o libcallback.so? -buildmode=c-shared a.go b.go


生成libcallback.h 和libcallback.so

C代碼調用

#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

生成可執行程序 m


輸入./m 執行,C調用golang的doSomethingCallback函數,并在此函數回調C的gocallback函數,完成了C->golang->C

小節

github源代碼下載

OpemIM開源IM項目

OpenIM官網

C和golang互調能力打通,這樣,對于采用C/C++開發的項目,如果某些業務特性不追求性能上的機制,可以通過golang實現,這樣達到了開發效率和執行效率的平衡,對業務開發非常有幫助。

通過深度調用機制分析,無論是Go調用C,還是C調用Go,其需要解決的核心問題其實都是提供一個C/Go的運行環境來執行相應的代碼。Go的代碼執行環境就是goroutine以及Go的runtime,而C的執行環境需要一個不使用分段的棧,并且執行C代碼的goroutine需要暫時地脫離調度器的管理。

要達到這些要求,運行時提供的支持就是切換棧,以及runtime.entersyscall。在Go中調用C函數時,runtime.cgocall中調用entersyscall脫離調度器管理。runtime.asmcgocall切換到m的g0棧,于是得到C的運行環境。在C中調用Go函數時,crosscall2解決gcc編譯到6c編譯之間的調用協議問題。cgocallback切換回goroutine棧。runtime.cgocallbackg中調用exitsyscall恢復Go的運行環境

OpenIMgithub開源地址:

https://github.com/OpenIMSDK/Open-IM-Server

OpenIM官網 : https://www.rentsoft.cn

OpenIM官方論壇: https://forum.rentsoft.cn/

更多技術文章:

開源OpenIM:高性能、可伸縮、易擴展的即時通訊架構https://forum.rentsoft.cn/thread/3

【OpenIM原創】簡單輕松入門 一文講解WebRTC實現1對1音視頻通信原理https://forum.rentsoft.cn/thread/4

【OpenIM原創】開源OpenIM:輕量、高效、實時、可靠、低成本的消息模型https://forum.rentsoft.cn/thread/1

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其...
    chjxidian閱讀 1,435評論 0 0
  • golang調用c動態庫 簡介 golang調用c語言動態庫,動態方式調用,可指定動態庫路徑,無需系統目錄下 核心...
    笑吧小鳥閱讀 1,852評論 0 0
  • 【轉載】C&C++——C函數與C++函數相互調用問題 C C++相互調用 在項目中融合C和C++有時是不可避免的,...
    天之道天知道閱讀 3,499評論 2 19
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,886評論 18 139
  • 函數指針是一個存著某個函數地址的變量。這個函數之后可以通用這個變量來調用。為什么需要函數指針呢?這邊舉個例子說明下...
    雨幻逐光閱讀 305評論 0 0