ฟังก์ชั่นการโทรไปจาก C


150

ฉันกำลังพยายามสร้างวัตถุแบบสแตติกที่เขียนใน Go to อินเตอร์เฟสด้วยโปรแกรม C (เช่นโมดูลเคอร์เนลหรือบางอย่าง)

ฉันได้พบเอกสารเกี่ยวกับการเรียกฟังก์ชั่น C จาก Go แต่ฉันไม่พบวิธีการใช้งานอีกมาก สิ่งที่ฉันพบคือมันเป็นไปได้ แต่ซับซ้อน

นี่คือสิ่งที่ฉันพบ:

บล็อกโพสต์เกี่ยวกับการโทรกลับระหว่าง C และ Go

เอกสาร Cgo

โพสต์รายชื่อผู้รับจดหมาย

ใครบ้างมีประสบการณ์กับสิ่งนี้หรือไม่? ในระยะสั้นฉันพยายามที่จะสร้างโมดูล PAM ที่เขียนทั้งหมดใน Go


11
คุณไม่สามารถอย่างน้อยจากเธรดที่ไม่ได้สร้างจากโก ฉันโหมกระหน่ำหลายครั้งแล้วและหยุดพัฒนาใน Go จนกว่าจะได้รับการแก้ไข
Matt Joiner

ฉันได้ยินมาว่ามันเป็นไปได้ ไม่มีวิธีแก้ปัญหาเหรอ?
beatgammit

Go ใช้ระเบียบการโทรที่แตกต่างและการแบ่งกลุ่ม คุณอาจจะสามารถเชื่อมโยงรหัส Go ที่คอมไพล์ด้วย gccgo กับรหัส C แต่ฉันยังไม่ได้ลองเพราะฉันยังไม่ได้ gccgo เพื่อสร้างในระบบของฉัน
mkb

ตอนนี้ฉันลองใช้งาน SWIG แล้วและฉันก็หวังว่า ... ฉันยังไม่ได้ทำงานอะไรเลยถึงแม้ว่า ... = '(ฉันโพสต์ในรายชื่อผู้รับจดหมายหวังว่าบางคนจะมีความเมตตาต่อฉัน
beatgammit

2
คุณสามารถโทรหารหัส Go จาก C แต่ในตอนนี้คุณไม่สามารถฝังรันไทม์ของ Go ลงในแอพ C ซึ่งเป็นสิ่งที่สำคัญ แต่แตกต่างกันเล็กน้อย
tylerl

คำตอบ:


126

คุณสามารถเรียกรหัส Go จาก C. มันเป็นข้อเสนอที่สับสน

กระบวนการนี้แสดงไว้ในบล็อกโพสต์ที่คุณเชื่อมโยง แต่ฉันสามารถดูว่ามันไม่เป็นประโยชน์มาก นี่เป็นตัวอย่างสั้น ๆ โดยไม่มีบิตที่ไม่จำเป็น ควรทำให้สิ่งต่าง ๆ ชัดเจนขึ้น

package foo

// extern int goCallbackHandler(int, int);
//
// static int doAdd(int a, int b) {
//     return goCallbackHandler(a, b);
// }
import "C"

//export goCallbackHandler
func goCallbackHandler(a, b C.int) C.int {
    return a + b
}

// This is the public function, callable from outside this package.
// It forwards the parameters to C.doAdd(), which in turn forwards
// them back to goCallbackHandler(). This one performs the addition
// and yields the result.
func MyAdd(a, b int) int {
   return int( C.doAdd( C.int(a), C.int(b)) )
}

ลำดับที่ทุกสิ่งถูกเรียกใช้มีดังนี้:

foo.MyAdd(a, b) ->
  C.doAdd(a, b) ->
    C.goCallbackHandler(a, b) ->
      foo.goCallbackHandler(a, b)

กุญแจสำคัญในการจำที่นี่คือฟังก์ชั่นการโทรกลับจะต้องทำเครื่องหมายด้วย//exportความคิดเห็นในด้านไปและเป็นexternด้าน C ซึ่งหมายความว่าการโทรกลับใด ๆ ที่คุณต้องการใช้จะต้องกำหนดไว้ในแพ็คเกจของคุณ

เพื่อให้ผู้ใช้แพคเกจของคุณจัดหาฟังก์ชั่นการโทรกลับที่กำหนดเองเราใช้วิธีการเดียวกันกับข้างต้น แต่เราจัดหาตัวจัดการแบบกำหนดเองของผู้ใช้ (ซึ่งเป็นเพียงฟังก์ชั่น Go ปกติ) เป็นพารามิเตอร์ที่ส่งผ่านไปยัง C void*ด้านข้างเป็น มันจะได้รับจาก callbackhandler ในแพ็คเกจของเราและโทรไป

ลองใช้ตัวอย่างขั้นสูงเพิ่มเติมที่ฉันกำลังทำงานด้วย ในกรณีนี้เรามีฟังก์ชั่น C ที่ทำงานหนักมากมันอ่านรายชื่อไฟล์จากอุปกรณ์ USB อาจใช้เวลาสักครู่ดังนั้นเราจึงต้องการให้แอปของเราได้รับแจ้งถึงความคืบหน้า เราสามารถทำได้โดยผ่านตัวชี้ฟังก์ชั่นที่เรากำหนดไว้ในโปรแกรมของเรา มันจะแสดงข้อมูลความคืบหน้าบางอย่างให้กับผู้ใช้เมื่อใดก็ตามที่มันถูกเรียก เนื่องจากมีลายเซ็นที่รู้จักกันดีเราจึงสามารถกำหนดประเภทของมันเอง:

type ProgressHandler func(current, total uint64, userdata interface{}) int

ตัวจัดการนี้ใช้ข้อมูลความคืบหน้าบางส่วน (จำนวนไฟล์ที่ได้รับในปัจจุบันและจำนวนไฟล์ทั้งหมด) พร้อมกับค่าอินเตอร์เฟส {} ซึ่งสามารถเก็บสิ่งที่ผู้ใช้ต้องการเก็บไว้

ตอนนี้เราต้องเขียนตัว C และ Go เพื่อให้เราสามารถใช้ handler นี้ได้ โชคดีที่ฟังก์ชั่น C ที่ฉันต้องการโทรจากvoid*ไลบรารี่ทำให้เราสามารถส่งผ่านโครงสร้างข้อมูลผู้ใช้ ซึ่งหมายความว่ามันสามารถเก็บสิ่งที่เราต้องการเก็บไว้ไม่มีคำถามถามและเราจะนำมันกลับไปสู่โลก Go ตามที่เป็นอยู่ ที่จะทำให้การทำงานทั้งหมดนี้เราไม่ได้เรียกใช้ฟังก์ชันห้องสมุดจากไปโดยตรง แต่เราสร้างเสื้อคลุม C goGetFiles()สำหรับมันที่เราจะตั้งชื่อ มันเป็นเสื้อคลุมที่ให้การโทรกลับไปที่ไลบรารี C ของเราพร้อมกับวัตถุ userdata

package foo

// #include <somelib.h>
// extern int goProgressCB(uint64_t current, uint64_t total, void* userdata);
// 
// static int goGetFiles(some_t* handle, void* userdata) {
//    return somelib_get_files(handle, goProgressCB, userdata);
// }
import "C"
import "unsafe"

โปรดทราบว่าgoGetFiles()ฟังก์ชั่นไม่ได้ใช้ตัวชี้ฟังก์ชั่นใด ๆ สำหรับการเรียกกลับเป็นพารามิเตอร์ แต่การเรียกกลับที่ผู้ใช้ของเราให้มานั้นบรรจุในโครงสร้างแบบกำหนดเองที่เก็บทั้งตัวจัดการนั้นและค่า userdata ของผู้ใช้ เราส่งผ่านสิ่งนี้ให้goGetFiles()เป็นพารามิเตอร์ userdata

// This defines the signature of our user's progress handler,
type ProgressHandler func(current, total uint64, userdata interface{}) int 

// This is an internal type which will pack the users callback function and userdata.
// It is an instance of this type that we will actually be sending to the C code.
type progressRequest struct {
   f ProgressHandler  // The user's function pointer
   d interface{}      // The user's userdata.
}

//export goProgressCB
func goProgressCB(current, total C.uint64_t, userdata unsafe.Pointer) C.int {
    // This is the function called from the C world by our expensive 
    // C.somelib_get_files() function. The userdata value contains an instance
    // of *progressRequest, We unpack it and use it's values to call the
    // actual function that our user supplied.
    req := (*progressRequest)(userdata)

    // Call req.f with our parameters and the user's own userdata value.
    return C.int( req.f( uint64(current), uint64(total), req.d ) )
}

// This is our public function, which is called by the user and
// takes a handle to something our C lib needs, a function pointer
// and optionally some user defined data structure. Whatever it may be.
func GetFiles(h *Handle, pf ProgressFunc, userdata interface{}) int {
   // Instead of calling the external C library directly, we call our C wrapper.
   // We pass it the handle and an instance of progressRequest.

   req := unsafe.Pointer(&progressequest{ pf, userdata })
   return int(C.goGetFiles( (*C.some_t)(h), req ))
}

นั่นคือการผูก C ของเรา รหัสของผู้ใช้อยู่ตรงไปตรงมามาก:

package main

import (
    "foo"
    "fmt"
)

func main() {
    handle := SomeInitStuff()

    // We call GetFiles. Pass it our progress handler and some
    // arbitrary userdata (could just as well be nil).
    ret := foo.GetFiles( handle, myProgress, "Callbacks rock!" )

    ....
}

// This is our progress handler. Do something useful like display.
// progress percentage.
func myProgress(current, total uint64, userdata interface{}) int {
    fc := float64(current)
    ft := float64(total) * 0.01

    // print how far along we are.
    // eg: 500 / 1000 (50.00%)
    // For good measure, prefix it with our userdata value, which
    // we supplied as "Callbacks rock!".
    fmt.Printf("%s: %d / %d (%3.2f%%)\n", userdata.(string), current, total, fc / ft)
    return 0
}

ทั้งหมดนี้ดูซับซ้อนกว่ามาก คำสั่งโทรไม่ได้เปลี่ยนไปเมื่อเทียบกับตัวอย่างก่อนหน้าของเรา แต่เราได้รับการโทรเพิ่มอีกสองสายในตอนท้ายของเครือข่าย:

คำสั่งซื้อมีดังนี้:

foo.GetFiles(....) ->
  C.goGetFiles(...) ->
    C.somelib_get_files(..) ->
      C.goProgressCB(...) ->
        foo.goProgressCB(...) ->
           main.myProgress(...)

ใช่ฉันรู้ว่าทั้งหมดนี้อาจระเบิดในหน้าของเราเมื่อมีการแยกกระทู้เข้ามาเล่น เฉพาะที่ไม่ได้สร้างโดย Go น่าเสียดายที่นี่เป็นสิ่งที่เกิดขึ้นในจุดนี้
jimt

17
นี่เป็นคำตอบที่ดีมากและทั่วถึง ไม่ได้ตอบคำถามโดยตรง แต่เป็นเพราะไม่มีคำตอบ จากแหล่งอ้างอิงหลายแห่งจุดเข้าต้องเป็น Go และไม่สามารถเป็น C. ฉันกำลังทำเครื่องหมายสิ่งนี้ว่าถูกต้องเพราะนี่เป็นการเคลียร์ข้อมูลสำหรับฉันจริงๆ ขอบคุณ!
beatgammit

@jimt วิธีการรวมเข้ากับที่เก็บขยะ? โดยเฉพาะอย่างยิ่งเมื่อใดอินสแตนซ์ความคืบหน้าการร้องขอส่วนตัวจะถูกเก็บรวบรวมถ้าเคย? (ใหม่ไปและทำให้ไม่ปลอดภัยสำหรับผู้ใช้) นอกจากนี้แล้ว API เช่น SQLite3 ที่ใช้โมฆะ * userdata แต่ยังมีฟังก์ชั่น "deleter" เพิ่มเติมสำหรับ userdata? สามารถใช้เพื่อโต้ตอบกับ GC เพื่อบอกได้หรือไม่ "ในตอนนี้มันก็โอเคที่จะเรียกคืนข้อมูลผู้ใช้นั้นถ้าด้าน Go ไม่ได้อ้างอิงอีกต่อไป"
ddevienne

6
ในฐานะของ Go 1.5 มีการสนับสนุนที่ดีกว่าสำหรับการโทร Go จาก C. ดูคำถามนี้หากคุณกำลังมองหาคำตอบที่แสดงให้เห็นถึงเทคนิคที่ง่ายกว่า: stackoverflow.com/questions/32215509/…
Gabriel Southern

2
ขณะที่ Go 1.6 วิธีนี้ใช้ไม่ได้จะหยุด "รหัส C อาจไม่เก็บสำเนาของตัวชี้ Go หลังจากการโทรกลับมา" กฎและปัญหาข้อผิดพลาด "panic: runtime: อาร์กิวเมนต์ cgo มีตัวชี้" Go to to Go "ที่ runtime
kaspersky

56

ไม่ใช่ข้อเสนอที่สับสนหากคุณใช้ gccgo สิ่งนี้ใช้ได้ที่นี่:

foo.go

package main

func Add(a, b int) int {
    return a + b
}

bar.c

#include <stdio.h>

extern int go_add(int, int) __asm__ ("example.main.Add");

int main() {
  int x = go_add(2, 3);
  printf("Result: %d\n", x);
}

Makefile

all: main

main: foo.o bar.c
    gcc foo.o bar.c -o main

foo.o: foo.go
    gccgo -c foo.go -o foo.o -fgo-prefix=example

clean:
    rm -f main *.o

เมื่อฉันมีรหัสไปทำ sth กับสตริงgo package main func Add(a, b string) int { return a + b }ฉันได้รับข้อผิดพลาด "undefined _go_string_plus"
TruongSinh

2
TruongSinh คุณอาจต้องการที่จะใช้cgoและแทนgo gccgoดูgolang.org/cmd/cgo เมื่อมีการกล่าวว่าเป็นไปได้อย่างเต็มที่ที่จะใช้ "string" ในไฟล์. go และเปลี่ยนไฟล์. c ของคุณเพื่อรวม__go_string_plusฟังก์ชั่น ใช้งานได้: ix.io/dZB
Alexander


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.