ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CGO ( Golang With C++ ) 케이스별 설명 및 예시
    Language/Golang 2023. 2. 7. 21:27

    C/C++ 과의 연동 (CGO)

    Go 에서 C/C++ 관련 기능을 호출할 때는 기본적으로 "C." 로 시작하게 됩니다.

     

    Go 에서 C/C++ Header 사용시 Header의 주의점

    1. 구조체의 Pack 사이즈는 8byte 일것
      만약 8byte 가 아닌 경우, Go 컴파일러에서 구조체를 잘못읽어들여서
      일부 필드를 찾지못하거나, 데이터가 깨지는 현상 발생할 수 있습니다.
      8byte 가 아니어도 우연히 규격에 맞으면 발생하지 않을 수 있습니다.
    2. 구조체 내에 함수가 정의되어 있지 않을것
    //if Go Include This Struct Error Ocurred.
    typedef struct _TEST {
    
        int nTemp;
        char sTemp;
    
        void InitStruct()
        {
            memset(this, 0x00, sizeof(*this));
        }    
    }TEST;
    1. 함수 파라미터에 기본값 명시가 되어 있지 않을것
    //if Go Include This Function.
    // Error b's default value
    void Test(int a, int b=0);
    1. stl 등을 활용하지 않을것
      Go 컴파일러에서는 stl 관련 헤더 및 라이브러리를 정상인식하지 못합니다. 따라서 map 이나 vector 등을 Go에서 사용할 수 없습니다.
    // if Go Include, Not Find map Error
    #include <map>

     

    CGO 는 GoPlayground 에서 테스트가 불가능합니다.

    CString 과 CBytes 로 생성한 변수는 C.Free 를 이용한 메모리 Release 작업을 해주어야 합니다.

    Go 에서 C/C++ Library 및 Header 참조 설정

    기본 라이브러리의 경우 별도의 경로 참조 없이 사용가능합니다.

    CFLAGS 를 이용한 Header 경로참조와 LDFLAGS 를 이용한 Library 참조는 아래와 같은 형식으로 사용할 수 있습니다.

    package main
    
    /*
    #cgo CFLAGS: -I../../../LIB/inc
    #cgo LDFLAGS: -L../../../LIB/lib/ -ldl -lstdc++ -lpthread -lrt
    #include <stdlib.h>
    */
    import "C"

     

    간단 예제

    CType 의 int 와 string 을 만듭니다.

    CString 으로 만든 문자열은 char* 임으로, 출력시 해당 포인터 값이 출력됩니다.

    import "C" 위의 주석은 주석으로 동작하는 것이 아닌 Header를 Include 하는 명령으로써 동작합니다.

    package main
    
    import "fmt"
    
    //#include "stdlib.h"
    import "C"
    
    func main() {
        i := C.int(10)
        s := C.CString("Str")
    
        fmt.Println(i,s)
    
        C.free(unsafe.Pointer(s))
    }
    10 0xa2d2c0

     

    C++ <-> Go 타입

    Call Method 는 Go 의 데이터를 C/C++ Type 으로 변환할때, 혹은 C/C++ Type 의 변수를 선언할때 사용됩니다.

    C/C++에서 포인터로 할당된 변수를 Go 에서 변환하여 사용할 경우, 메모리에서 free 된 후 접근시 panic 이 발생되게 됩니다.

    Type C Call method Go type Bytes (byte)
    char C.char byte 1
    signed char C.schar int8 1
    unsigned char C.uchar uint8 1
    short int C.short int16 2
    short unsigned int C.ushort uint16 2
    int C.int int 4
    unsigned int C.uint uint32 4
    long int C.long int32 or int64 4
    long unsigned int C.ulong uint32 or uint64 4
    long long int C.longlong int64 8
    long long unsigned int C.ulonglong uint64 8
    float C.float float32 4
    double C.double float64 8
    wchar_t C.wchar_t wchar_t 2
    void * unsafe.Pointer    
    int8_t C.int8_t int8  
    uint8_t C.uint8_t uint8  
    int16_t C.int16_t int16  
    uint16_t C.uint16_t uint16  
    int32_t C.int32_t int32  
    uint32_t C.uint32_t uint32  
    int64_t C.int64_t int64  
    uint64_t C.uint64_t uint64  

     

    C/C++ 타입을 Go 형식으로 치환해주는 기능은 다음과 같습니다.

    GoString의 결과값은 파라미터가 되는 포인터의 값이 유효하지 않은 상태가 될 경우 동일한 시점에 유효하지 않을 수 있습니다.

    이외의 Numberic 계열의 타입은 형변환을 통해 가능합니다.

    // C string to Go string
    func C.GoString(*C.char) string
    
    // C data with explicit length to Go string
    func C.GoStringN(*C.char, C.int) string
    
    // C data with explicit length to Go []byte
    func C.GoBytes(unsafe.Pointer, C.int) []byte

     

    각 타입에 맞게 변환을 해서 사용하면 되며 간단한 예시로 아래와 같습니다.

    package main
    
    import "C"
    import "fmt"
    
    func main() {
        //C++ int <-> Go int
        a := C.int(10) // a is C++ int
        b := int(a) // b is Go int
    
        fmt.Println(a,b)
    
        c := C.CString("abc") // c is C++ char*
        d := C.GoString(c) // d is Go string
    
        fmt.Println(c,d)
    }
    10 10
    0x10aa050 abc

     

    Go 에서 C/C++ 함수 구현

    Go 에서 C++ 을 구현하여 사용할 수 있습니다.

    package main
    
    /*
    #include <stdio.h>
    int test(int a, int b)
    {
        printf("function Call : %d", a+b);
    }
    */
    import "C"
    
    func main() {
        C.test(1,2)
    }
    function Call : 3

    Go 에서 C/C++ 라이브러리 함수호출

    Go 에서 C/C++ 의 Library 모듈을 참조하고 있다면 해당 모듈의 기능을 불러와 사용할 수 있습니다.

    //test.h
    #pragma once
    
    #ifdef __cplusplus
    extern "C"{
    #endif
    
    int test(int a, const char* b);
    
    #ifdef __cplusplus
    }
    #endif
    
    
    //test.cpp
    #include "test.h"
    #include <stdio.h>
    
    int test(int a, const char* b)
    {
            printf("test function Call : %d, %s\n", a, b);
            return 0;
    }
    package main
    
    import "fmt"
    import "unsafe"
    
    /*
    #cgo LDFLAGS: -L./ -ltest
    #include "test.h"
    #include <stdlib.h>
    */
    import "C"
    
    func main() {
        s := C.CString("teststring")
        result := C.test(C.int(1), s)
        fmt.Println(result)
    
        C.free(unsafe.Pointer(s))
    }
    test function Call : 1, teststring
    0

    Go 에서 C/C++ 라이브러리 함수호출 - dlopen

    so를 활용하여 include 하지 않고 호출할 수 있습니다.

    각 함수에 대한 정의가 필요하며, 호출에는 cgo 영역의 브릿지가 필요합니다.

    Windows 의 LoadLibrary 와 동일한 역활을 합니다.

    C++ 의 dlopen 과 동일합니다.

    package main
    
    /*
    #cgo LDFLAGS: -ldl
    
    #include <stdlib.h>
    #include <dlfcn.h>
    
    typedef int (*CloseServer)(int nHandle);
    
    int bridge_closeserver(CloseServer f, int handle) {
        return f(handle);
    }
    */
    import "C"
    import (
        "fmt"
        "os"
        "unsafe"
    )
    
    func main() {
        var handle int = -1
    
        libpath := C.CString("./libicapi_easd.so")
        imglib := C.dlopen(libpath, C.RTLD_LAZY)
    
        if imglib != nil {
            funcName := C.CString("ICCloseServer")
    
            fp := C.dlsym(imglib, funcName)
    
            if fp != nil {
                handle = int(C.bridge_closeserver(C.CloseServer(fp), C.int(5)))
            }
    
            C.dlclose(imglib)
            C.free(unsafe.Pointer(funcName))
        }
    
        fmt.Println(handle)
    
        C.free(unsafe.Pointer(libpath))
    }
    

    C/C++ -> Go Callback

    C++ Library 내에서 동작하는 Callback에 대하여 Go 에서 응답 받아 처리하기 위해서는 아래와 같은 방법을 사용할 수 있습니다.

    Callback 뿐만이 아닌 C/C++ 모듈단에서 Go 함수를 호출해야 하는 경우 사용할 수 있습니다.

    //test.h
    #pragma once
    
    #ifdef __cplusplus
    extern "C"{
    #endif
    
    int test(int a, const char* b);
    int CallbackFn(int a, char* b);
    
    #ifdef __cplusplus
    }
    #endif
    
    
    //test.cpp
    #include "test.h"
    #include <stdio.h>
    
    int test(int a, char* b)
    {
            printf("function Call : CallbackFn\n");
    
            CallbackFn(a,b);
    
            printf("function Call After : CallbackFn\n");
    
            return 0;
    }
    package main
    
    import "fmt"
    import "unsafe"
    
    /*
    #cgo LDFLAGS: -L./ -ltest
    #include "test.h"
    #include <stdlib.h>
    */
    import "C"
    
    //export CallbackFn
    func CallbackFn(a C.int, b *C.char) C.int {
        GoB := C.GoString(b)
        fmt.Println(int(a),GoB)
    
        return C.int(1)
    }
    
    func main() {
        s := C.CString("teststring")
        result := C.test(C.int(1), s)
        fmt.Println(result)
    
        C.free(unsafe.Pointer(s))
    }
    function Call : CallbackFn
    1 teststring
    function Call After : CallbackFn
    0

    Go 소스에 "//export" 로 시작하는 주석을 추가하고 Library 에 명시되어 있는 함수의 정의부를 구현함에 따라서 Library 에서 해당 함수를 호출할시 Go 에 정의된 함수가 동작하게 됩니다. 따라서 중복된 함수 구현이 발생될 경우 모듈 링크에 실패하고 에러가 발생되게 됩니다.

    Go export 에 대한 동작방식은 Go 공식 홈페이지에서 간략히 아래와 같이 명시하고 있습니다.

    Using //export in a file places a restriction on the preamble: since it is copied into two different C output files, it must not contain any definitions, only declarations. If a file contains both definitions and declarations, then the two output files will produce duplicate symbols and the linker will fail. To avoid this, definitions must be placed in preambles in other files, or in C source files.

     

    Go 에서 활용되는 변수 타입 및 함수를 C/C++ 단으로 가지고 내려와 활용할 수도 있습니다.

    이 케이스는 타입변환이 헷갈릴 경우를 방지할 수 있습니다.

    "go tool cgo {go소스파일}" 을 통해 "_cgo_export.h" 라는 참조될 정의를 구성합니다. 명령을 사용시 "_obj" 라는 폴더가 생성되고 하위에 정의파일이 생성됩니다.

    Go 에서 활용된 멀티 리턴을 C/C++ 에서 define 하여 호출하는 경우 아래와 같이 명시할 수 있습니다.

    //test.h
    #pragma once
    
    #ifdef __cplusplus
    extern "C"{
    #endif
    
    int test(int a, char* b);
    
    #ifdef __cplusplus
    }
    #endif
    
    //test.cpp
    #include "test.h"
    #include <stdio.h>
    
    #include "./_obj/_cgo_export.h"
    
    int test(int a, char* b)
    {
        GoString msg = {"Hello from C!", 13};
    
        MyFunction(30,40,msg);
    
        MyFunction2_return x = MyFunction2(10,20,msg);
    
        printf("%d, %s\n", x.return1, x.return2);
    
        return 0;
    }
    
    package main
    
    /*
    #cgo LDFLAGS: -L./ -ltest
    #include "test.h"
    #include <stdlib.h>
    */
    import "C"
    
    import "fmt"
    import "unsafe"
    
    //export MyFunction
    func MyFunction (arg1, arg2 int, arg3 string) int64 {
            fmt.Println(arg1, arg2, arg3)
            return 10
    }
    
    //export MyFunction2
    func MyFunction2 (arg1, arg2 int, arg3 string) (int64, * C.char) {
            fmt.Println(arg1, arg2, arg3)
    
            b := C.CString("MyFunction2 Call")
            return 10, b
    }
    
    func main() {
            b := C.CString("123")
            C.test(10, b)
            C.free(unsafe.Pointer(b))
    }
    30 40 Hello from C!
    10 20 Hello from C!
    10, MyFunction2 Call

     

    위 예제에 대한 _cgo_export.h 내용 안에 함수에 대한 내용은 아래와 같이 정의되어 있습니다.

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    extern GoInt64 MyFunction(GoInt arg1, GoInt arg2, GoString arg3);
    
    /* Return type for MyFunction2 */
    struct MyFunction2_return {
            GoInt64 r0;
            char* r1;
    };
    extern struct MyFunction2_return MyFunction2(GoInt arg1, GoInt arg2, GoString arg3);
    
    #ifdef __cplusplus
    }
    #endif

    위 예제에서 호출에 대한 순서는 Go Main -> C/C++ test Function -> Go MyFunction -> Go MyFunction2 -> Go Main End 순으로 진행됩니다.

     

    'Language > Golang' 카테고리의 다른 글

    Kafka Golang 예제  (0) 2023.02.12
    Golang에서 Json 사용예시  (0) 2023.02.07
    문법 및 사용법 예시  (0) 2023.02.07
    자료형 및 키워드  (0) 2023.02.07
    Golang 설치 및 IDE  (0) 2023.02.07
Designed by Tistory.