Go로 작성된 정적 객체를 C 프로그램과의 인터페이스 (예 : 커널 모듈 또는 기타)로 작성하려고합니다.
Go에서 C 함수 호출에 대한 문서를 찾았지만 다른 방법으로 이동하는 방법에 대해서는 많이 찾지 못했습니다. 내가 찾은 것은 가능하지만 복잡하다는 것입니다.
내가 찾은 것은 다음과 같습니다.
누구든지 이것에 경험이 있습니까? 요컨대, Go로 완전히 작성된 PAM 모듈을 만들려고합니다.
Go로 작성된 정적 객체를 C 프로그램과의 인터페이스 (예 : 커널 모듈 또는 기타)로 작성하려고합니다.
Go에서 C 함수 호출에 대한 문서를 찾았지만 다른 방법으로 이동하는 방법에 대해서는 많이 찾지 못했습니다. 내가 찾은 것은 가능하지만 복잡하다는 것입니다.
내가 찾은 것은 다음과 같습니다.
누구든지 이것에 경험이 있습니까? 요컨대, Go로 완전히 작성된 PAM 모듈을 만들려고합니다.
답변:
C에서 Go 코드를 호출 할 수 있습니다. 혼동되는 제안입니다.
프로세스는 링크 된 블로그 게시물에 요약되어 있습니다. 그러나 그것이 그다지 도움이되지 않는 방법을 볼 수 있습니다. 여기 불필요한 비트가없는 짧은 스 니펫이 있습니다. 좀 더 명확하게 만들어야합니다.
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
에 Go 쪽과 extern
C쪽에 주석 이 표시되어야한다는 것 입니다. 즉, 사용하려는 모든 콜백이 패키지 내부에 정의되어 있어야합니다.
패키지 사용자가 커스텀 콜백 함수를 제공 할 수 있도록 위와 똑같은 접근 방식을 사용하지만 사용자의 커스텀 핸들러 (일반 Go 함수)를 C로 전달되는 매개 변수로 제공합니다. 측면 void*
. 그런 다음 패키지의 콜백 핸들러에 의해 수신되어 호출됩니다.
현재 작업중인 고급 예제를 사용하십시오. 이 경우 꽤 무거운 작업을 수행하는 C 함수가 있습니다. USB 장치에서 파일 목록을 읽습니다. 다소 시간이 걸릴 수 있으므로 앱에 진행 상황을 알리기를 원합니다. 프로그램에서 정의한 함수 포인터를 전달하면됩니다. 호출 될 때마다 사용자에게 진행 정보를 표시합니다. 잘 알려진 서명이 있으므로 고유 한 유형을 지정할 수 있습니다.
type ProgressHandler func(current, total uint64, userdata interface{}) int
이 핸들러는 진행 정보 (현재 수신 된 파일 수 및 총 파일 수)와 사용자가 보유해야하는 모든 것을 보유 할 수있는 interface {} 값을 가져옵니다.
이제이 핸들러를 사용할 수 있도록 C 및 Go 배관을 작성해야합니다. 다행히도 라이브러리에서 호출하려는 C 함수를 사용하면 type의 userdata 구조체를 전달할 수 있습니다 void*
. 즉, 우리가 원하는 것은 무엇이든 가질 수 있고, 질문은 없으며, 그대로 Go 세계로 되돌릴 수 있습니다. 이 모든 작업을 수행하기 위해 Go에서 직접 라이브러리 함수를 호출하지는 않지만 이름을 지정할 C 래퍼를 만듭니다 goGetFiles()
. 실제로는 Go 콜백을 userdata 객체와 함께 C 라이브러리에 제공하는 것이이 래퍼입니다.
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(...)
gccgo를 사용하면 혼란스러운 제안이 아닙니다. 이것은 여기에서 작동합니다 :
package main
func Add(a, b int) int {
return a + b
}
#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);
}
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
go package main func Add(a, b string) int { return a + b }
하면 오류 "정의되지 않은 _go_string_plus"가 발생합니다
cgo
및 을 사용 하고 싶을 go
것입니다 gccgo
. golang.org/cmd/cgo를 참조하십시오 . 그렇게 말하면 .go 파일에서 "문자열"유형을 사용하고 __go_string_plus
함수 를 포함하도록 .c 파일을 변경하는 것이 가능 합니다. 작동 : ix.io/dZB
내가 아는 한 불가능합니다.
참고 : 내보내기를 사용하는 경우 프리앰블에서 C 함수를 정의 할 수 없습니다.