Go
Let's build a Go application using the TEN framework.
Creating the TEN App
First, we'll create a basic TEN Go app by integrating several pre-built TEN packages. Follow these steps:
tman install app default_app_go
cd default_app_go
tman install protocol msgpack
tman install extension_group default_extension_group
Installing a Default TEN Extension
Next, install a default TEN extension written in Go:
tman install extension default_extension_go
Declaring the Prebuilt Graph for Auto-Start
Now, we'll modify the property.json
file of the TEN app to include a graph declaration. This will ensure the default extension starts automatically when the TEN app is launched.
"predefined_graphs": [
{
"name": "default",
"auto_start": true,
"nodes": [
{
"type": "extension_group",
"name": "default_extension_group",
"addon": "default_extension_group"
},
{
"type": "extension",
"name": "default_extension_go",
"addon": "default_extension_go",
"extension_group": "default_extension_group"
}
]
}
]
Building the App
Unlike standard Go projects, the TEN Go app uses CGo, so you need to set up certain environment variables before building. A build script is already provided in the TEN runtime Go binding system package, so you can build the app with a single command:
go run ten_packages/system/ten_runtime_go/tools/build/main.go
The compiled binary, main
, will be generated in the /bin
folder.
Starting the App
Since some environment variables need to be set, it is recommended to start the app using the provided script:
./bin/start
Debugging
If you are using Visual Studio Code (VSCode) as your development environment, you can use the following configurations to debug Go/C code.
Debugging Go Code
{
"version": "0.2.0",
"configurations": [
{
"name": "Go app (launch)",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"cwd": "${workspaceFolder}",
"output": "${workspaceFolder}/bin/tmp",
"env": {
"LD_LIBRARY_PATH": "${workspaceFolder}/lib",
"DYLD_LIBRARY_PATH": "${workspaceFolder}/lib",
"CGO_LDFLAGS": "-L${workspaceFolder}/lib -lten_runtime_go -Wl,-rpath,@loader_path/lib"
}
}
]
}
Debugging C Code
{
"version": "0.2.0",
"configurations": [
{
"name": "C (launch)",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/bin/main",
"cwd": "${workspaceFolder}",
"env": {
"LD_LIBRARY_PATH": "${workspaceFolder}/lib",
"DYLD_LIBRARY_PATH": "${workspaceFolder}/lib",
"CGO_LDFLAGS": "-L${workspaceFolder}/lib -lten_runtime_go -Wl,-rpath,@loader_path/lib"
}
}
]
}
CGO
Generated Code
When interfacing Go with C, the cgo tool is crucial. When a C function is called from Go, cgo converts the Go source files into multiple output files, including both Go and C source files. These generated C source files are then compiled using compilers like gcc or clang.
Use the following command to generate these C/Go source files:
mkdir out
go tool cgo -objdir out
Example:
go tool cgo -objdir out cmd.go error.go
The generated files include:
βββ _cgo_export.c
βββ _cgo_export.h
βββ _cgo_flags
βββ _cgo_gotypes.go
βββ _cgo_main.c
βββ _cgo_.o
βββ cmd.cgo1.go
βββ cmd.cgo2.c
βββ error.cgo1.go
βββ error.cgo2.c
_cgo_export.h is key to the interoperability between Go and C within the cgo context. It contains necessary declarations for Go functions accessible from C.
This file is automatically generated based on Go functions explicitly exported using the
//export
directive. It also includes definitions for C types corresponding to the Go types used in these functions, ensuring compatibility between the two languages.Example of type definitions:
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
_cgo_gotypes.go contains the corresponding Go types defined in C and used in Go.
For example, if you define a struct
ten_go_error_t
in a header filecommon.h
and useC.ten_go_error_t
in Go, there will be a corresponding Go type in_cgo_gotypes.go
:
package ten
type _Ctype_ten_go_status_t = _Ctype_struct_ten_go_status_t
type _Ctype_struct_ten_go_status_t struct {
err_no _Ctype_int
msg_size _Ctype_uint8_t
err_msg [256]_Ctype_char
_ [3]byte
}
cmd.cgo1.go is a cgo-generated counterpart to the original Go source file
cmd.go
. It replaces direct calls to C functions and types with calls to the generated Go functions and types provided by cgo.Example:
package ten
func NewCmd(cmdName string) (Cmd, error) {
// ...
cStatus := func() _Ctype_struct_ten_go_status_t {
var _cgo0 _cgo_unsafe.Pointer = unsafe.Pointer(unsafe.StringData(cmdName))
var _cgo1 _Ctype_int = _Ctype_int(len(cmdName))
_cgoBase2 := &bridge
_cgo2 := _cgoBase2
_cgoCheckPointer(_cgoBase2, 0 == 0)
return _Cfunc_ten_go_cmd_create_custom_cmd(_cgo0, _cgo1, _cgo2)
}()
// ...
}
cmd.cgo2.c is a wrapper of the original C function called from Go.
Example:
CGO_NO_SANITIZE_THREAD void _cgo_cb1b98e39356_Cfunc_ten_go_cmd_create_custom_cmd(void *v) {
struct {
const void* p0;
int p1;
char __pad12[4];
ten_go_msg_t** p2;
ten_go_error_t r;
} __attribute__((__packed__, __gcc_struct__)) *_cgo_a = v;
char *_cgo_stktop = _cgo_topofstack();
__typeof__(_cgo_a->r) _cgo_r;
_cgo_tsan_acquire();
_cgo_r = ten_go_cmd_create_cmd(_cgo_a->p0, _cgo_a->p1, _cgo_a->p2);
_cgo_tsan_release();
_cgo_a = (void*)((char*)_cgo_a + (_cgo_topofstack() - _cgo_stktop));
_cgo_a->r = _cgo_r;
_cgo_msan_write(&_cgo_a->r, sizeof(_cgo_a->r));
}
So, the calling sequence of C.ten_go_cmd_create_cmd()
from Go is:
_Cfunc_ten_go_cmd_create_custom_cmd (auto-generated Go)
|
V
_cgo_runtime_cgocall (Go)
|
V
entrysyscall // switch Go stack to C stack
|
V
_cgo_cb1b98e39356_Cfunc_ten_go_cmd_create_custom_cmd (auto-generated C)
|
V
ten_go_cmd_create_cmd (C)
|
V
exitsyscall
Incomplete Type
As mentioned earlier, the cgo tool generates corresponding Go types in _cgo_gotypes.go
based on C header files imported via import "C"
. If a struct is opaque in the C header, the cgo tool generates an incomplete type instead.
Example of an opaque C struct:
typedef struct ten_go_msg_t ten_go_msg_t;
The cgo tool will generate an incomplete type in Go:
type _Ctype_ten_go_msg_t = _Ctype_struct_ten_go_msg_t
type _Ctype_struct_ten_go_msg_t _cgopackage.Incomplete
What happens if you use the incomplete type in Go?
Incomplete types cannot be allocated.
Since
sys.NotInHeap
cannot be allocated on the Go heap, operations likenew
ormake
won't work. Attempting to create a new instance of an opaque struct in Go will result in a compiler error:
msg := new(C.ten_go_msg_t) // Error: can't be allocated in Go; it is incomplete (or unallocatable)
Pointers to incomplete types cannot be passed to C directly.
If you have a C function with a pointer to an opaque struct as a parameter, passing a Go pointer to this incomplete type directly to a C function will not work according to cgo rules. The Go compiler will require the pointer to be "pinned" to ensure it adheres to Go's garbage collector (GC) constraints.
Rules for Using C.uintptr_t Instead of a Pointer to an Opaque Struct
Benefits:
C.uintptr_t
anduintptr
in Go are integers large enough to hold a C pointer, avoiding memory allocation or conversion when passing from Go to C.
Limitations:
uintptr
is an integer, not a pointer. It represents the bit pattern of an address with no pointer arithmetic.uintptr
cannot be dereferenced in Go. Converting it tounsafe.Pointer
can cause issues with Go's GC.Since
uintptr
is an integer,nil
orNULL
cannot be passed to C. Use0
instead ofnil
to represent a null address.
Last updated
Was this helpful?