Go 언어에서 C 함수를 부르는 것은 매우 쉽다. 또한 C 함수에서 Go 함수를 호출할수도 있으며 이는 cgo 를 이용해서 사용 가능하다. C++ 코드의 경우 Go 코드에서 직접 연동은 어렵고, C++ 헤더/소스 파일에서 extern "C" 키워드로 감싸주면 Go언어에서 호출 가능하다.
1. Go 소스에서 C 언어 연동: Hello World
Go 소스에 /* */ 주석 부분에 C코드를 넣고 주석 밑에 import "C" 를 하게 되면 사용 가능하다. 주석과 import "C" 사이에 다른 문장이 있으면 안된다. Go 언어에서 파라미터 전달은 C.int, C.CString과 같이 go의 데이터 타입을 C 함수에서 사용할 수 있도록 바꿔주고 전달하면 된다. 다음은 C에서 Char*를 받아서 C의 printf 함수를 이용해서 호출하는 예제이다.
2-7라인이 C 소스 코드가 들어 있는 라인이다. 10라인에서 Go String으로 변수를 생성하고 11 라인에서 CString으로 바꿔서 전달한다. go build하고 실행하면 Hello World가 출력된다.
2. Go와 C의 변수 참조
Go 변수를 C변수로 전환하려면 다음과 같은 함수를 사용한다.
C.char, C.schar(signed char), C.uchar(unsigned char), C.short, C.ushort, C.int, C.uint, C.long, C.ulong, C.longlong, C.ulonglong, C.float, C.double, C.complexfloat, C.complexdouble
void* 은 unsafe.Pointer로 표현되며 __int128_t 와 __uint128_t는 [16]byte 로 표현된다. 다음은 GO와 C를 변형해주는 특별한 함수들이다.
- C.CString : go string을 *C.char로 리턴, CString은 C heap에 생성되며 메모리 해제 필요(free or C.free)
- C.CBytes : []byte를 unsafe.Pointer로 리턴, C array는 C heap에 생성되며 메모리 해제 필요
- C.GoString : *C.char를 go stringㅇ로 리턴
- C.GoStringN : *C.char를 n 만큼 go string으로 변경
- C.GoBytes : unsafe.Pointer를 C.int 만큼 []byte로 변경
위의 함수들 중 C.Cstring, C.Bytes 는 C 관리 메모리 상에 할당되기 때문에(malloc 이용) C코드나 Go 코드에서 해제해 줘야 한다.
다음은 char* 포인터를 어떻게 다루는지에 대한 예제이다.
이 예제는 char* 형을 받는 3가지 함수로 되어 있다. 첫번쨰(5-12라인)는 전달 받은 char* 를 직접 조작하는 경우, 두번째(13-19라인)는 메모리를 할당하고 해당 메모리 포인터를 전달하는 경우, 세번쨰(28-27)는 메모리를 할당하고 입력으로 받은 포인터의 메모리를 해제하는 경우이다.
Case1(36-42라인)은 Go의 string을 CString 변수로 할당하고 이 CString 로 C함수에 전달한 case이다. 이 경우 CString으로 생성된 변수는 내부적으로는 malloc으로 메모리가 할당되고 이 할당된 메모리는 reserverChar C 함수에서 조작된다. 따라서 41의 cMyName은 역순으로 출력된다. 그리고 CString의 경우 메모리가 할당되고 garbage collection이 안되기 떄문에 명시적으로 해제(C.free)해줘야 한다.
Case2(44-47라인)은 go byte를 *C.char 형으로 캐스팅해서 전달한 경우로 이 경우 go byte array가 C 함수에서 직접 조작된다. go byte array는 garbage collection이 되기 때문에 별도의 메모리 해제는 불필요하다.
Case3(49-54라인)은 C함수에서 malloc으로 메모리를 할당한 결과를 Go로 넘겨주는데, 이 경우 53라인처럼 C.free로 메모리를 해제해 줘야 한다.
Case4(56-63라인)은 C함수에서 메모리를 할당하고 할당된 포인터를 리턴하는데, 입력 파라미터 포인터를 C에서 메모리 해제한 케이스이다. 특이한 것은 63라인에 보면 Go 함수에서 이미 해제된 포인터를 해제해도 Segment Fault가 발생하지는 않는다. 61라인에서는 메모리 해제된 포인터를 GoString으로 변환하면 "" 가 된다.
다음은 실행 결과이다.
3. C++ 및 C의 struct 구조체 이용
C++의 경우 extern "C"를 붙인 헤더 파일을 Go의 cgo 영역에 include함으로 사용 가능하다.
다음은 C++코드를 shared library로 만들고 이 라이브러리를 go에서 사용하는 예제이다.
1. C++ Header 파일
extern "C" 를 선언하고 이 블록 내에 코드를 넣는다. 8-11은 C의 구조체 선언이다.
2. C++ 소스 파일
21라인에서는 Go에서 export 된 함수를 호출한다. Go함수를 호출하고 그 결과를 다시 리턴한다. 2라인에서 헤더 파일은 go 함수 참조를 위해서 go에서 생성한 헤더 파일이다.
3. Go 소스 파일
12-17라인은 C에서 호출할 수 있도록 export한 함수이다. 12라인처럼 //export 다음에 C로 표출할 함수 이름을 정의힌다. main.go에 대해서 다음과 같이 C에서 가져올 수 있는 헤더를 생성한다.
go tool cgo main.go
명령을 실행하면 _obj 폴더에 C에서 include 할 수 있는 _cgo_export.h 가 생성된다. 앞의 "2. C++ 소스 파일"을 보면 2라인에 보면 go에서 생성한 헤더 파일을 include하는 것을 확인할 수 있다. 20-26라인에는 Go에서 C구조체를 선언하고 사용하는 방법을 보여준다. 28-36라인은 Go에서 C구조체 포인터를 선언하고 사용하는 방법을 보여준다. 38-40라인은 C에서 Go 함수를 호출하는 함수를 호출하는 것으로 "2. c++소스파일" 에서 25라인을 보면 여기에서 export된 GoFUnction1 을 호출하는 것을 알 수 있다. 호출한 결과는 16라인에서 처럼 C.CString으로, C코드에서는 char* 형태로 전달 받게 되며, 이 char* 포인터의 메모리는 어디에선가 해제해 줘야 한다. (48라인)
실행 결과는 다음과 같다.
go 언어에서 C 함수와 라이브러리를 사용하는 것은 매우 간편하다. 또한 C에서 Go 함수를 호출할 수 있으며 이를 통해서 C 라이브러리를 유기적으로 사용할 수 있게 해준다.