You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang CShared模式下委托函数调用崩溃问题求助

Go C-Shared 回调崩溃问题解决方案

回调崩溃核心原因

Go 编译为 c-shared 库时,非C兼容类型会被自动转为 void*,若调用方(Dart/C#)未正确做类型映射、或Go侧未规范参数传递,会直接引发内存访问错误导致崩溃。

分步修复方案

1. Go侧规范回调类型与参数传递

必须用C兼容类型定义回调签名,禁止直接使用Go原生类型(如string),示例代码:

package main

/*
typedef void (*CallbackFunc)(const char*);
*/
import "C"
import "unsafe"

var callback C.CallbackFunc

//export SetCallback
func SetCallback(cb C.CallbackFunc) {
    callback = cb
}

//export TriggerCallback
func TriggerCallback() {
    if callback != nil {
        // 转换Go字符串为C字符串,需手动释放内存
        cMsg := C.CString("callback from Go")
        defer C.free(unsafe.Pointer(cMsg))
        callback(cMsg)
    }
}

func main() {} // 必须保留空main函数用于编译

编译命令:

go build -buildmode=c-shared -o libcallback.so libcallback.go

2. Dart/ffigen 侧正确绑定与类型转换

在pubspec.yaml中配置ffigen的类型映射,将void*转为对应C类型:

ffigen:
  output: 'generated_bindings.dart'
  headers:
    entry-points:
      - 'libcallback.h'
  type-map:
    char*: Pointer<Char>
    void*: Pointer<Void>

调用时需将Dart函数转为符合C调用约定的指针:

import 'dart:ffi';
import 'generated_bindings.dart';
import 'package:ffi/ffi.dart';

void main() {
  final dylib = DynamicLibrary.open('libcallback.so');
  final lib = Libcallback(dylib);

  // 定义回调逻辑,正确解析C字符串
  void callbackHandler(Pointer<Char> msg) {
    print("Received: ${msg.toDartString()}");
  }

  // 转换为Go兼容的函数指针
  final callbackPtr = Pointer.fromFunction<Void Function(Pointer<Char>)>(callbackHandler);
  lib.SetCallback(callbackPtr);

  lib.TriggerCallback();
}

3. C# 侧匹配调用约定与参数解析

C#需显式指定cdecl调用约定(Go默认调用约定),并正确转换C指针为字符串:

using System;
using System.Runtime.InteropServices;

class Program
{
    // 匹配Go侧的回调签名与调用约定
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void CallbackFunc(IntPtr msg);

    [DllImport("libcallback.so", CallingConvention = CallingConvention.Cdecl)]
    private static extern void SetCallback(CallbackFunc cb);

    [DllImport("libcallback.so", CallingConvention = CallingConvention.Cdecl)]
    private static extern void TriggerCallback();

    static void Main(string[] args)
    {
        CallbackFunc callback = (IntPtr msgPtr) => {
            string msg = Marshal.PtrToStringAnsi(msgPtr);
            Console.WriteLine($"Received: {msg}");
        };

        SetCallback(callback);
        TriggerCallback();
    }
}

Go与C交互的类型处理规范

字符串处理

  • Go → C:使用C.CString(goStr)将Go字符串转为C字符串,内存分配在C堆,必须用C.free(unsafe.Pointer(cStr))释放,避免泄漏。
  • C → Go:使用C.GoString(cStr)将C字符串转为Go字符串,会自动复制数据到Go内存,无需手动释放C侧内存(若C内存由Go分配仍需free)。

字节数组处理

  • Go切片 → C数组:先分配C内存,再复制数据:
func GoSliceToC(data []byte) (*C.uchar, C.int) {
    cBuf := (*C.uchar)(C.malloc(C.size_t(len(data))))
    copy((*[1 << 30]byte)(unsafe.Pointer(cBuf))[:len(data)], data)
    return cBuf, C.int(len(data))
}
  • C数组 → Go切片:基于C指针创建Go切片,复制数据:
func CArrayToGo(cBuf *C.uchar, length C.int) []byte {
    goSlice := make([]byte, length)
    copy(goSlice, (*[1 << 30]byte)(unsafe.Pointer(cBuf))[:length])
    return goSlice
}

关键注意事项

  • 明确内存所有权:Go分配的C内存(C.CString/C.malloc)需由Go或调用方释放;调用方分配的内存由调用方负责。
  • 统一调用约定:Go默认使用cdecl,所有调用方必须匹配该约定,否则会引发栈错误。
  • 回调中避免复杂Go操作:禁止在回调内启动goroutine或调用Go runtime的复杂API,防止线程安全问题。

内容的提问来源于stack exchange,提问作者Ahmad Asy Syafiq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 19:59:50