Heesung Yang
[GO] cgo - GO에서 C언어로 작성된 코드 사용하기
프롤로그
GO
에서 왜 C언어로 작성된 코드를 사용할까?
이유는 여러가지가 있겠지만 보통 C 언어로 작성된 라이브러리를 그대로 GO
에서 사용하기 위해 사용하는 경우가 가장 흔한 것 같다.
필자의 경우, 사내에서 사용하는 상용 소프트웨어의 C 라이브러리를 Wrapping하여 Web 서버를 작성해야 할 일이 있었다.
Web 서버를 C 언어로 작성하자니, 생산성이 너무 떨어져서 기능 하나 추가하는게 너무 더뎠다. (사실 필자가 C 언어를 잘 못해서 그런 탓이 크지만;)
GO
의 net/http
패키지로 Web 서버를 작성해 본 경험과 비교했을 때, 작성해야 하는 코드의 양 자체가 너무 많았다.
그래서 결국 cgo
를 이용해서 상용 C 라이브러리를 GO
코드에서 호출 하는 방식으로 변경하였고, 결과는 너무 만족스러웠다.
C 코드를 GO에서 사용하는 방법과 GO 코드를 C에서 사용하는 방법이 있는데, 이 글에서는 C 코드를 GO에서 사용하는 방법에 대해서만 설명한다. (필자는 아직 GO 코드를 C에서 사용한 적이 없었다. 그래서 잘 모른다.)
C 코드 사용 방법
C 코드를 GO에서 호출하는 방법은 크게 2가지로 나뉜다. GO코드 안에 포함하는 방법과 C 소스코드 파일을 따로 만들어서 이용하는 방법이 있다.
GO 코드에 C 코드 포함
-
main.go
package main /* int add(int a, int b) { return a + b; } */ import "C" import ( "fmt" ) func main() { num1 := 10 num2 := 20 fmt.Printf("%d + %d = %d", num1, num2, C.add(C.int(num1), C.int(num2))) }
-
"C"
패키지를 임포트해야 한다. -
import "C"
라인 상단의 주석은 C 코드로(헤더파일로써) 처리된다. -
C 코드의 함수와 변수는
C.함수명
,C.변수명
으로 참조한다. -
GO
의 int형 변수를 C 함수의 int형 인자로 넘기기 위해서는C.int()
를 사용하여 캐스팅해야 한다.
C 코드 분리
-
main.go
package main // #include "add.h" import "C" import ( "fmt" ) func main() { num1 := 10 num2 := 20 fmt.Printf("%d + %d = %d", num1, num2, C.add(C.int(num1), C.int(num2))) }
-
add.c
#include "add.h" int add(int a, int b) { return a + b; }
-
add.h
int add(int a, int b);
C 라이브러리 참조
소스 코드 없이 헤더와 라이브러리 파일만 있는 경우다. 위 예제를 컴파일하여 static library로 만들어 사용해 보자.
-
static library로 컴파일
~$ gcc -c add.c ~$ ar rcs libadd.a add.o # library 파일과 헤더 파일만 남기자. ~$ rm add.c add.o
# file list . ├── add.h ├── libadd.a ├── main.go
-
go build
-
main.go
package main /* #cgo LDFLAGS: -L. -ladd #include "add.h" */ import "C" import ( "fmt" ) func main() { num1 := 10 num2 := 20 fmt.Printf("%d + %d = %d", num1, num2, C.add(C.int(num1), C.int(num2))) }
-
#cgo
라는 키워드를 이용하여 컴파일 옵션을 지정할 수 있다.- C 코드 컴파일 시 기본적으로 현재 폴더가 library path에 포함되지 않으므로,
-L.
옵션으로 현재 폴더를 추가했다.
LDFLAGS 뿐만 아니라 CFLAGS도 설정 가능하며, 해당 옵션에 대한 자세한 설명은 이 글의 범위를 벗어나므로 생략한다.
문자열 다루기
위 예에서는 int 자료형을 다뤄봤다. 상당히 직관적이라서 어려운 부분은 없었을 거라 생각한다. 그러나 문자열은 조금 까다롭다. C 에서의 문자열과 GO의 string 이 본질적으로 다르기 때문인데 C의 문자열은 ‘\0’ 값으로 끝나는 char 배열이라면 GO의 string은 문자열 시작 주소값과 len 값을 가지고 있다.
C의 printf 함수를 이용한 문자열 출력 예를 보자.
package main
/*
#include <stdio.h>
#include <stdlib.h>
void myprint(const char *msg) {
printf("%s\n", msg);
}
*/
import "C"
import "unsafe"
func main() {
msg := C.CString("Hello, cgo!")
C.myprint(msg)
defer C.free(unsafe.Pointer(msg))
}
C.CString()
함수를 이용하여 C의 char* 타입 변수를 생성한다.- 이 때 해당 변수는
malloc()
을 이용하여 C 의 heap 영역에 생성되므로, 사용이 끝난 후 반드시free()
를 호출해야 한다. (stdlib.h
필요) unsafe.Pointer()
함수를 이용하여 GO에서 생성한 C의 char* 변수 주소를 넘겨준다.
유의사항
C 자료형과 GO 자료형의 차이
GO
의 정수형 타입과 C의 정수형은 다른 타입으로 취급되기 때문에 캐스팅이 필수적이다. 캐스팅할 타입의 이름이 같아도 실제 사이즈는 다르다.
GO에서 사용가능한 숫자형 C 타입은 아래와 같다. (Refer: https://pkg.go.dev/cmd/cgo@go1.17.8#hdr-Go_references_to_C)
C.char
C.schar (signed char)
C.uchar (unsigned char)
C.short
C.ushort (unsigned short)
C.int
C.uint (unsigned int)
C.long
C.ulong (unsigned long)
C.longlong (long long)
C.ulonglong (unsigned long long)
C.float
C.double
C.complexfloat (complex float)
C.complexdouble (complex double)
정수
정수형 타입은 다루고자 하는 데이터의 최대 크기에 유의해야 한다. 다음 예를 보자.
package main
import "C"
import "fmt"
func main() {
num1 := 2147483647
num2 := 1
fmt.Printf("num1 type : %T\n", num1)
fmt.Printf("C.int(num1) type : %T\n", C.int(num1))
fmt.Printf("go : %d + %d = %d\n", num1, num2, num1 + num2)
fmt.Printf("cgo : %d + %d = %d\n", C.int(num1), C.int(num2), C.int(num1 + num2))
}
num1 type : int
C.int(num1) type : main._Ctype_int
go : 2147483647 + 1 = 2147483648
cgo : 2147483647 + 1 = -2147483648
num1
의 타입은int
다.C.int()
로 캐스팅한 변수의 타입은_Ctype_int
다.GO
int 는2147483647
이상의 값을 담을 수 있다._Ctype_int
는2147483647
가 최대값이다. 즉 해당 타입의 size는 32 bits이다. 그래서 +1을 하면 마이너스 값이 된다.
References
- https://gist.github.com/zchee/b9c99695463d8902cd33
- https://developpaper.com/go-type-conversion-and-type-conversion-with-c/
- https://pkg.go.dev/cmd/cgo#hdr-C_references_to_Go
- https://blog.marlin.org/cgo-referencing-c-library-in-go
- https://www.lobaro.com/embedded-development-with-c-and-golang-cgo/
- http://go-lang.cat-v.org/library-bindings
- https://akrennmair.github.io/golang-cgo-slides/#1
- https://zchee.github.io/golang-wiki/cgo/
- https://utcc.utoronto.ca/~cks/space/blog/programming/GoCGoStringFunctions
- https://go101.org/article/unsafe.html
Previous post
[GO] Integer 타입 최소/최대값Next post
[명령어] column