C에서 통화 이동 기능


150

Go로 작성된 정적 객체를 C 프로그램과의 인터페이스 (예 : 커널 모듈 또는 기타)로 작성하려고합니다.

Go에서 C 함수 호출에 대한 문서를 찾았지만 다른 방법으로 이동하는 방법에 대해서는 많이 찾지 못했습니다. 내가 찾은 것은 가능하지만 복잡하다는 것입니다.

내가 찾은 것은 다음과 같습니다.

C와 Go 간의 콜백에 대한 블로그 게시물

Cgo 문서

골랑 메일 링리스트 포스트

누구든지 이것에 경험이 있습니까? 요컨대, Go로 완전히 작성된 PAM 모듈을 만들려고합니다.


11
적어도 Go에서 생성되지 않은 스레드에서는 할 수 없습니다. 나는 이것에 대해 여러 번 격노했고, 이것이 고칠 때까지 Go에서 개발을 중단했다.
Matt Joiner

가능하다고 들었습니다. 해결책이 없습니까?
beatgammit

Go는 다른 호출 규칙과 세그먼트 스택을 사용합니다. gccgo로 컴파일 된 Go 코드를 C 코드와 링크 할 수는 있지만 gccgo를 시스템에 빌드하지 않았으므로 시도하지 않았습니다.
mkb

. 지금은 SWIG를 사용하여 노력하고있어 나는 희망 해요 ... 나는 '= ... 아직 불구하고 작업에 아무것도 못 했어 (I 메일 링리스트에 게시 희망 누군가가 나에게 자비를 가지고 있습니다.
beatgammit

2
C에서 Go 코드를 호출 할 수 있지만 현재 Go 런타임을 C 앱에 포함시킬 수는 없습니다 . 이는 중요하지만 미묘한 차이입니다.
tylerl

답변:


126

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 쪽과 externC쪽에 주석 이 표시되어야한다는 것 입니다. 즉, 사용하려는 모든 콜백이 패키지 내부에 정의되어 있어야합니다.

패키지 사용자가 커스텀 콜백 함수를 제공 할 수 있도록 위와 똑같은 접근 방식을 사용하지만 사용자의 커스텀 핸들러 (일반 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(...)

네, 별도의 실이 나올 때이 모든 것이 우리 얼굴에 터질 수 있다는 것을 알고 있습니다. 특히 Go에 의해 생성되지 않은 것들. 불행히도 이것이 현재 상황입니다.
jimt

17
이것은 정말 좋은 대답이며 철저합니다. 질문에 직접 대답하지는 않지만 답이 없기 때문입니다. 여러 출처에 따르면 진입 점은 Go이고 C가 될 수 없어야합니다. 이것이 실제로 나를 정리했기 때문에 이것을 올바른 것으로 표시하고 있습니다. 감사!
beatgammit

@jimt 가비지 수집기와 어떻게 통합됩니까? 특히 private progressRequest 인스턴스는 언제 수집됩니까? (New to Go 및 안전하지 않은 포인터). 또한 void * userdata를 사용하는 SQLite3와 같은 API는 물론 userdata를위한 선택적 "deleter"함수는 어떻습니까? GC와 상호 작용하는 데 사용될 수 있습니까? "이제 Go 측에서 더 이상 참조하지 않는 한 해당 사용자 데이터를 회수해도 괜찮습니까?"
ddevienne 2016 년

6
Go 1.5부터는 C에서 Go를 호출하는 것이 더 좋습니다.보다 간단한 기술을 보여주는 답변을 찾고 있다면이 질문을 참조하십시오. stackoverflow.com/questions/32215509/…
Gabriel Southern

2
Go 1.6부터는이 방법이 작동하지 않습니다. "C 코드는 호출이 반환 된 후 Go 포인터의 사본을 보관하지 않을 수 있습니다." 규칙 및 런타임에 "공황 : 런타임 오류 : cgo 인수에 이동 포인터로 이동 포인터가 있습니다"오류가 발생합니다
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);
}

메이크 파일

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"가 발생합니다
TruongSinh

2
TruongSinh, 대신 cgo및 을 사용 하고 싶을 go것입니다 gccgo. golang.org/cmd/cgo를 참조하십시오 . 그렇게 말하면 .go 파일에서 "문자열"유형을 사용하고 __go_string_plus함수 를 포함하도록 .c 파일을 변경하는 것이 가능 합니다. 작동 : ix.io/dZB
Alexander


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.