Go
Let's create a Go application using the NYRA framework.
Creating the NYRA App
To begin, we will construct a Nyra Go application by integrating several pre-configured Nyra packages. Proceed with the following steps:
tman install app default_app_go
cd default_app_go
tman install protocol msgpack
tman install extension_group default_extension_groupInstalling a Default NYRA Extension
Next, install a default NYRA extension written in Go:
tman install extension default_extension_goDeclaring the Prebuilt Graph for Auto-Start
Next, we will update the property.json file of the Nyra application to incorporate a graph declaration. This configuration ensures that the default extension initializes automatically upon launching the Nyra application.
"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 conventional Go projects, the Nyra Go application leverages CGo, requiring the configuration of specific environment variables prior to the build process. To simplify this, the Nyra runtime Go binding system package includes a pre-configured build script, enabling the application to be compiled with a single command.
go run nyra_packages/system/nyra_runtime_go/tools/build/main.goThe 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/startDebugging
If Visual Studio Code (VSCode) is your chosen development environment, you can apply the following configurations to facilitate debugging for Go and 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 -lnyra_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 -lnyra_runtime_go -Wl,-rpath,@loader_path/lib"
}
}
]
}CGO
Generated Code
When integrating Go with C, the cgo tool plays a pivotal role. It facilitates the interaction by translating Go source files into multiple output files, encompassing both Go and C source code. The resulting C source files are subsequently compiled using tools such as gcc or clang.
To generate these C and Go source files, execute the following command:
mkdir out
go tool cgo -objdir outExample:
go tool cgo -objdir out cmd.go error.goThe generated files will 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 serves as a critical component in facilitating interoperability between Go and C within the cgo framework. This header file contains the requisite declarations for Go functions that are accessible from C, acting as a bridge between the two languages.
The file is automatically generated based on Go functions explicitly marked with the //export directive. It also incorporates type definitions that map C types to their corresponding Go types, ensuring seamless compatibility and communication between Go and C during function calls.
Example of type definitions:
typedef signed har 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 nyra_go_error_t in a header file common.h and use C.nyra_go_error_t in Go, there will be a corresponding Go type in _cgo_gotypes.go:
package nyra
type _Ctype_nyra_go_status_t = _Ctype_struct_nyra_go_status_t
type _Ctype_struct_nyra_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 derivative of the original Go source file, cmd.go. This file replaces direct invocations of C functions and types with intermediary Go functions and types generated by cgo. These abstractions streamline the interaction between Go and C, ensuring proper type safety and function mapping.
Example:
package nyra
func NewCmd(cmdName string) (Cmd, error) {
// ...
cStatus := func() _Ctype_struct_nyra_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_nyra_go_cmd_create_custom_cmd(void *v) {
struct {
const void* p0;
int p1;
char __pad12[4];
nyra_go_msg_t** p2;
nyra_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 = nyra_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.nyra_go_cmd_create_cmd() from Go is:
_Cfunc_nyra_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_nyra_go_cmd_create_custom_cmd (auto-generated C)
|
V
nyra_go_cmd_create_cmd (C)
|
V
exitsyscallIncomplete Type
As previously outlined, the cgo tool generates corresponding Go type definitions in the _cgo_gotypes.go file by interpreting the C header files imported using import "C". For C structures declared as opaque within the header files, cgo produces an incomplete type representation in the generated Go code to maintain consistency with the abstraction.
Example of an opaque C structure:
typedef struct nyra_go_msg_t nyra_go_msg_t;The cgo tool will generate an incomplete type in Go:
type _Ctype_nyra_go_msg_t = _Ctype_struct_nyra_go_msg_t
type _Ctype_struct_nyra_go_msg_t _cgopackage.IncompleteWhat happens if you use the incomplete type in Go?
Incomplete types are inherently non-allocatable.
As a result, structures such as sys.NotInHeap cannot reside on the Go heap, rendering operations like new or makeinapplicable. Any attempt to instantiate an opaque struct within Go will trigger a compiler error:
msg := new(C.nyra_go_msg_t) // Error: can't be allocated in Go; it is incomplete (or unallocatable)Pointers to incomplete types cannot be directly passed to C functions.
When a C function accepts a pointer to an opaque struct as a parameter, cgo prohibits passing a Go pointer to the corresponding incomplete type directly. This restriction arises from the need to comply with Go's garbage collection (GC) rules. The Go compiler mandates that such pointers be "pinned" to ensure their memory locations remain stable during interaction with C code.
Rules for Using C.uintptr_t Instead of a Pointer to an Opaque Struct
The use of C.uintptr_t in C anduintptr in Go provides a mechanism to represent integers large enough to store a C pointer. This eliminates the need for memory allocation or complex conversions when passing data between Go and C, ensuring efficient interoperability.
Constraints:
Lack of Pointer Semantics:
uintptrin Go is an integer type that represents the raw bit pattern of an address but does not exhibit pointer semantics. Consequently, pointer arithmetic or operations associated with true pointers are not supported.Non-Dereferenceable in Go:
uintptrcannot be directly dereferenced in Go. Converting it to anunsafe.Pointerintroduces potential risks, particularly concerning Goβs garbage collector (GC), which may relocate or reclaim memory, leading to undefined behavior.Null Representation: Since
uintptris treated as an integer, it cannot directly represent a null pointer (e.g.,nilorNULL). Instead, the value0must be used explicitly to denote a null address when interfacing with C code.
These characteristics necessitate caution when using uintptr, especially in scenarios involving Goβs memory management and type safety constraints.
Last updated