cgo의 C 및 Go를 사용한 링크의 뒷면(2)

14275 단어 Go
앞기사에서 Go 코드에서 C 함수를 호출하는 상황을 보았습니다.
본문에서 반대로 C의 함수에서 Go를 호출하는 코드, 즉 Go의 함수 export를 C로 호출하는 상황을 처리한다.
하지만 여기서 말하는 것은 결국'Go의 일부 포장을 C로 구현할 때 C코드는 Go의 기능을 활용할 수 있다'는 것이다.C 프로그램에 Go 라이브러리를 삽입하면-buildmode=c-archive,-buildmode=c-shared와 기타는 별도로 처리한다.
이번 토크에서 전체 프로그램은 시종일관 Go를 전제로 지난번과 마찬가지로 그 중 일부만 C로 쓰겠다고 구상했다.

샘플 코드


이번에는 다음 4개 문서로 구성된 포장github.com/yugui/cgo-explained/example2을 고려한다.//export C가 디렉토리goVersion로 export를 진행하도록 지시합니다.
export_example.go
package main

import (
    "C"
    "runtime"
)

//export goVersion
func goVersion() string {
    return runtime.Version()
}
use_exported.h
void print_go_version(void);
use_exported.c
#include <stdio.h>
#include "_cgo_export.h"

void print_go_version(void) {
  const GoString version = goVersion();
  printf("%.*s\n", (int)version.n, version.p);
}
main.go
package main

// #include "use_exported.h"
import "C"

func main() {
    C.print_go_version()
}
exported.go의 기능은 주로 use_exported.c에 사용된다.그러나 프로그램 자체가 고로 써야 하기 때문에 고의func main가 어느 곳에서importuse_exported.c의 기능이 더 이상 없으면 존재use_exported.c의 의미가 없다.그래서 main.go는 이렇다.
원본 코드의 의존 관계는 아래 그림과 같다.

구축 프로세스


구축 과정 자체는import의 경우와 큰 차이가 없다.
  • 코드 생성
  • go tool cgo export_example.go
  • go tool cgo main.go
  • C 코드의 컴파일
  • gcc -c SOURCES
  • C 코드 링크
  • gcc -o _cgo_.o OBJS
  • 가져오기 성명의 생성
  • go tool cgo -dynimport ....
  • Go 코드의 컴파일
  • go tool compile -o example2.a -pack GO_FILES
  • C 코드 재링크
  • gcc -o _all.o OBJS
  • 아카이브에 C 객체 추가
  • go tool pack r example2.a _all.o
  • 아카이브의 객체 링크
  • go tool link -o example2 example2.a
  • 데이터 흐름은 다음 그림과 같다.

    이것도 import의 상황과 대체적으로 같은 구조이지만 다음 두 가지는 다르다.
  • go tool cgo에 마운트된 소스 파일이 2개로 늘어남에 따라 생성된 파일이 복잡해짐
  • use_exported.c 에서 온 대상 파일 use_exported.o 이 추가되었습니다.이 파일을 컴파일할 때 go tool cgo 생성된 _cgo_export.h#include 이다.
  • 특히 후자는 더욱 상세하다.

    use_exported.c


    우선 export의 Go 기능을 이용한 C소스use_exported.c는 아래와 같다(재대출).
    use_exported.c
    #include <stdio.h>
    #include "_cgo_export.h"
    
    void print_go_version(void) {
      const GoString version = goVersion();
      printf("%.*s\n", (int)version.n, version.p);
    }
    
    여기#include에서 만든 것_cgo_export.hgo tool cgoexport_example.go에서 생성된 것이다.
    _cgo_export.h
    /* Created by "go tool cgo" - DO NOT EDIT. */
    
    /* (中略) */
    
    /* Start of preamble from import "C" comments.  */
    /* End of preamble from import "C" comments.  */
    
    /* Start of boilerplate cgo prologue.  */
    /* (中略) */
    typedef signed char GoInt8;
    /* (中略) */
    typedef struct { const char *p; GoInt n; } GoString;
    typedef void *GoMap;
    typedef void *GoChan;
    typedef struct { void *t; void *v; } GoInterface;
    typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
    
    /* (中略) */
    
    extern GoString goVersion();
    
    이 문서는 주로 세 부분으로 구성되어 있다.
  • main.go 앞의 댓글에 적힌 preamble부의 복사본
    preamble에 import "C" 같은 것을 쓰면 //#include <stdio.h> 이 부분으로 복사됩니다.
  • Go의 기본형(boilerplate cgo proologue)
  • 을 C로 나타냅니다.
    선고
  • #include <stdio.h>의 Go의 함수에 대응하는 함수 원형
  • 이것//export을 이용하면 use_exported.c내의 export_example.go를 호출할 수 있다.그러나 정확히 말하면 함수 원형 중의 func goVersion() stringfunc goVersion() string는 같지 않다.그건 다음 절에서 봐요.

    그룹 코드


    먼저 C에서 볼 수 있듯이 goVersion() 생성된 goVersion()에서 볼 수 있다.매개변수를 적절하게 채워 Go의 세계로 넘기기 위한 그룹 코드다.
    _cgo_export.c
    /* Created by cgo - DO NOT EDIT. */
    #include "_cgo_export.h"
    
    extern void crosscall2(void (*fn)(void *, int), void *, int);
    extern void _cgo_wait_runtime_init_done();
    
    extern void _cgoexp_5e3e4b09c83e_goVersion(void *, int);
    
    GoString goVersion()
    {
        _cgo_wait_runtime_init_done();
        struct {
            GoString r0;
        } __attribute__((__packed__)) a;
        crosscall2(_cgoexp_5e3e4b09c83e_goVersion, &a, 16);
        return a.r0;
    }
    
    _cgo_export.c의 실질은 crosscall2 레지스터 회피 등이다.runtime.cgo.crosscall2 이름과 같이 _cgo_wait_runtime_init_done 봉인된 초기화가 끝나기 전에 블록을 진행한다.
    그리고 여기 호출된runtime_cgoexp_5e3e4b09c83e_goVersion에 있습니다.
    _cgo_gotypes.go
    /* (略) */
    
    //go:linkname _cgo_runtime_cgocallback runtime.cgocallback
    func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr)
    
    /* (略) */
    
    //go:cgo_export_dynamic goVersion
    //go:linkname _cgoexp_5e3e4b09c83e_goVersion _cgoexp_5e3e4b09c83e_goVersion
    //go:cgo_export_static _cgoexp_5e3e4b09c83e_goVersion
    //go:nosplit
    //go:norace
    func _cgoexp_5e3e4b09c83e_goVersion(a unsafe.Pointer, n int32) {
        fn := _cgoexpwrap_5e3e4b09c83e_goVersion
        _cgo_runtime_cgocallback(**(**unsafe.Pointer)(unsafe.Pointer(&fn)), a, uintptr(n));
    }
    
    func _cgoexpwrap_5e3e4b09c83e_goVersion() (r0 string) {
        defer func() {
            _cgoCheckResult(r0)
        }()
        return goVersion()
    }
    
    
    _cgo_gotypes.go 창고 주위를 조정하는 동시에 최종 호출runtime.cgocallback.
    C의 함수goVersion에서 상기 그룹 코드 Go가 정의한 함수goVersion를 호출할 수 있습니다.

    총결산


    export의 경우 import의 상황과 구축 가설도 큰 차이가 없다.import의 경우 할애print_go_version만 중요해진다.
    실행할 때 _cgo_export.[ch]_cgo_export.c를 통해 각각 정의된 두 단계의 그룹 코드를 C의 세계에서 Go의 세계로 옮기는 것을 제어한다.

    좋은 웹페이지 즐겨찾기