คุณสามารถเรียกรหัส 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(...)