programing

C로부터의 Call Go 함수

firstcheck 2022. 8. 2. 22:35
반응형

C로부터의 Call Go 함수

C 프로그램(커널 모듈 등)과 인터페이스하기 위해 Go로 작성된 정적 객체를 작성하려고 합니다.

Go에서 C 함수를 호출하는 문서는 찾았지만, 그 반대로 가는 방법은 찾지 못했습니다.가능하지만 복잡하다는 것을 알게 되었습니다.

다음은 제가 발견한 것입니다.

C와 Go 사이의 콜백에 관한 블로그 투고

Cgo 문서

골랑 메일링 리스트 투고

이거 해본 사람 있어요?한마디로 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과 바둑을 extern C c c 하는 콜백은 합니다.즉, 사용하는 콜백은 패키지 내에서 정의해야 합니다.

할 수 의 커스텀핸들러 Go일 뿐)를이됩니다.void*그 후, 콜백 핸들러에 의해서 패키지로 수신되어 호출됩니다.

현재 사용하고 있는 보다 고도의 예를 사용해 보겠습니다.이 경우 상당히 부하가 높은 작업을 수행하는 C 함수가 있습니다.USB 장치에서 파일 목록을 읽습니다.이 작업은 시간이 걸릴 수 있으므로 진행 상황을 앱에 알려주시기 바랍니다.프로그램에서 정의한 함수 포인터를 전달함으로써 이를 수행할 수 있습니다.호출될 때마다 사용자에게 몇 가지 진행률 정보를 표시할 뿐입니다.잘 알려진 시그니처가 있기 때문에 독자적인 타입을 할당할 수 있습니다.

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

이 핸들러는 일부 진행 정보(현재 수신된 파일 수 및 총 파일 수)와 사용자가 원하는 모든 정보를 유지할 수 있는 인터페이스{} 값을 가져옵니다.

이제 이 핸들러를 사용할 수 있도록 C와 Go 배관을 작성해야 합니다.다행히 내가 도서관에서 호출하고 싶은 C 함수는 우리가 유형의 사용자 데이터 구조를 전달할 수 있게 해준다.void*즉, 원하는 것은 무엇이든 담을 수 있습니다.질문 없이 그대로 바둑으로 되돌릴 수 있습니다.이 모든 작업을 수행하기 위해 Go에서 라이브러리 함수를 직접 호출하지는 않지만 이름을 붙일 C 래퍼를 만듭니다.goGetFiles()이 래퍼에서는 실제로 사용자 데이터 오브젝트와 함께 Go 콜백을 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()함수는 콜백의 함수 포인터를 파라미터로 사용하지 않습니다.대신 사용자가 제공한 콜백은 해당 핸들러와 사용자 자신의 사용자 데이터 값을 모두 유지하는 커스텀 구조로 패킹됩니다.이것을 다음에 전달합니다.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
}

이 모든 것이 실제보다 훨씬 더 복잡해 보입니다.콜 순서는 이전 예와 달리 변경되지 않았지만 체인의 마지막에2개의 추가 콜이 수신됩니다.

순서는 다음과 같습니다.

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

Go 1.5 릴리즈에 따라 답변이 변경되었습니다.

얼마 전에 질문한 SO 질문은 1.5의 추가 기능에 비추어 다시 문제를 해결합니다.

기존 C 프로젝트에서 Go 코드 사용

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

내가 생각하는 한, 그것은 불가능하다.

주의: 내보내기를 사용하는 경우 프리암블에 C 함수를 정의할 수 없습니다.

출처 : https://github.com/golang/go/wiki/cgo

언급URL : https://stackoverflow.com/questions/6125683/call-go-functions-from-c

반응형