2024-07-29 19:50:23 +08:00
How to support a C/C++ Library
=====
2024-07-18 14:52:29 +08:00
2024-07-29 19:50:23 +08:00
## Support a C Library
### Install a C Library
2024-07-18 14:52:29 +08:00
2024-07-18 23:35:39 +08:00
We recommend using a package manager (such as brew, apt-get, winget, etc.) to install a C library. For example:
2024-07-18 14:52:29 +08:00
```bash
brew install inih
```
2024-07-29 19:50:23 +08:00
### Writing Go Files to Link Library Functions
2024-07-18 14:52:29 +08:00
1. On macOS, use `nm -gU libbar.dylib` to parse C-style symbols
2024-07-26 16:14:41 +08:00
```bash
2024-07-18 23:35:39 +08:00
0000000000003e55 T _ini_parse
```
2024-07-18 14:52:29 +08:00
2. Find the function prototype you want to convert in the corresponding .h file
2024-07-18 23:35:39 +08:00
```c
int ini_parse(const char* filename, ini_handler handler, void* user);
```
2024-07-18 14:52:29 +08:00
3. Create the corresponding Go file
2024-07-31 15:13:30 +08:00
```bash
2024-07-26 16:14:41 +08:00
inih/
├── _demo
├── inih_demo
├──inih_demo.go
└── inih.go
```
2024-07-18 14:52:29 +08:00
4. In `inih.go` , use LLGoPackage to specify the location of the third-party library so that llgo can link to the third-party library. Both `pkg-config --libs inih` and `linih` are used to specify the location of the third-party library.
2024-07-18 23:35:39 +08:00
```go
package inih
2024-07-18 14:52:29 +08:00
2024-07-18 23:35:39 +08:00
import (
_ "unsafe" // unsafe is necessary when using go:linkname
)
2024-07-18 14:52:29 +08:00
2024-07-18 23:35:39 +08:00
const (
LLGoPackage = "link: $(pkg-config --libs inih); -linih"
)
```
2024-07-18 14:52:29 +08:00
5. Write the corresponding function in `inih.go`
2024-07-26 16:14:41 +08:00
Note that the basic C function type mapping to Go function type can be found at [https://github.com/goplus/llgo/blob/main/doc/Type-Mapping-between-C-and-Go.md ](https://github.com/goplus/llgo/blob/main/doc/Type-Mapping-between-C-and-Go.md ). Some types requiring special handling are listed at the end of this document for reference.
2024-07-18 14:52:29 +08:00
2024-07-26 16:14:41 +08:00
```go
//go:linkname Parse C.ini_parse
func Parse(filename *c.Char, handler func(user c.Pointer, section *c.Char, name *c.Char, value *c.Char) c.Int, user c.Pointer) c.Int
```
2024-07-18 23:35:39 +08:00
2024-07-18 14:52:29 +08:00
6. Write the function call in `inih_demo.go`
2024-07-26 16:14:41 +08:00
```go
package main
2024-07-18 14:52:29 +08:00
2024-07-26 16:14:41 +08:00
import (
"github.com/goplus/llgo/c"
"github.com/goplus/llgo/cpp/inih"
)
2024-07-18 14:52:29 +08:00
2024-07-26 16:14:41 +08:00
func main() {
filename := c.Str("path/to/yourIniFile")
2024-07-18 14:52:29 +08:00
2024-07-26 16:14:41 +08:00
if inih.Parse(filename, func(user c.Pointer, section *c.Char, name *c.Char, value *c.Char) c.Int {
println("section:", c.GoString(section), "name:", c.GoString(name), "value:", c.GoString(value))
return 1
}, nil) < 0 {
println("Error parsing config file")
return
}
}
```
2024-07-18 14:52:29 +08:00
7. Use llgo to run the demo
2024-07-26 16:14:41 +08:00
```bash
cd inih/_demo/inih_demo
llgo run .
```
2024-07-18 14:52:29 +08:00
2024-07-29 19:50:23 +08:00
### Handling Special Types
2024-07-18 14:52:29 +08:00
2024-07-29 19:50:23 +08:00
#### Handling Enum Values in C
2024-07-18 14:52:29 +08:00
Use const to implement enum values
```go
/*
typedef enum {
BLEND_ALPHA = 0, // Blend textures considering alpha (default)
BLEND_ADDITIVE, // Blend textures adding colors
BLEND_MULTIPLIED, // Blend textures multiplying colors
BLEND_ADD_COLORS, // Blend textures adding colors (alternative)
BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative)
BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha
BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors())
BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate())
} BlendMode;
2024-07-18 17:02:30 +08:00
*/
2024-07-26 16:14:41 +08:00
type BlendMode c.Int
2024-07-18 17:02:30 +08:00
const (
2024-07-26 16:14:41 +08:00
BLEND_ALPHA BlendMode = iota // Blend textures considering alpha (default)
BLEND_ADDITIVE // Blend textures adding colors
BLEND_MULTIPLIED // Blend textures multiplying colors
BLEND_ADD_COLORS // Blend textures adding colors (alternative)
BLEND_SUBTRACT_COLORS // Blend textures subtracting colors (alternative)
BLEND_ALPHA_PREMULTIPLY // Blend premultiplied textures considering alpha
BLEND_CUSTOM // Blend textures using custom src/dst factors (use rlSetBlendFactors())
BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate())
2024-07-18 17:02:30 +08:00
)
2024-07-18 14:52:29 +08:00
```
2024-07-29 19:50:23 +08:00
#### Handling Structs in C
2024-07-18 14:52:29 +08:00
```go
// If you need to use class member variables, like llgo/c/raylib
/*
//Vector4, 4 components
typedef struct Vector4 {
float x; // Vector x component
float y; // Vector y component
float z; // Vector z component
float w; // Vector w component
} Vector4;
*/
type Vector4 struct {
2024-07-26 16:14:41 +08:00
X float32 // Vector x component
Y float32 // Vector y component
Z float32 // Vector z component
W float32 // Vector w component
2024-07-18 14:52:29 +08:00
}
// If class member variables don't need to be exposed, like llgo/c/cjson, wrap functions that use these member variables as methods of the class. Example:
2024-07-26 16:14:41 +08:00
2024-07-18 14:52:29 +08:00
/*
typedef struct cJSON
{
// next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem
struct cJSON *next;
struct cJSON *prev;
// An array or object item will have a child pointer pointing to a chain of the items in the array/object.
struct cJSON *child;
// The type of the item, as above.
int type;
// The item's string, if type==cJSON_String and type == cJSON_Raw
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead
int valueint;
// The item's number, if type==cJSON_Number
double valuedouble;
// The item's name string, if this item is the child of, or is in the list of subitems of an object.
char *string;
} cJSON;
*/
// llgo:type C
type JSON struct {
2024-07-26 16:14:41 +08:00
Unused [0]byte
2024-07-18 14:52:29 +08:00
}
// llgo:link (*JSON).AddItem C.cJSON_AddItemToArray
func (o *JSON) AddItem(item *JSON) c.Int { return 0 }
```
For the size of Unused, if the methods bound to the structure do not need to create objects, i.e., the receiver of the Go methods bound to this structure is of pointer type, you can declare `Unused [0]byte` . Otherwise, you need to write a simple C file using the `sizeof` operator to calculate the size of the structure. Assuming the structure size is 38 bytes, then declare `Unused [38]byte` .
2024-07-29 19:50:23 +08:00
#### Handling Function Pointers in C
2024-07-18 14:52:29 +08:00
```go
// Convert function pointers to Go style and then declare function pointer types using aliases
//llgo:type C
type Comp func(a c.Int)
```
2024-07-29 19:50:23 +08:00
#### Handling char ** Type in C
2024-07-18 14:52:29 +08:00
Handle char * * as `[]*c.Char`
```go
// void printStrings(const char * strings[], int size);
//
//llgo:link PrintStrings C.printStrings
func PrintStrings(strings **c.Char, size c.Int) {}
```
```go
package main
import (
"unsafe"
"github.com/goplus/llgo/c"
"github.com/goplus/llgo/c/ptrtest"
)
func main() {
2024-07-26 16:14:41 +08:00
strings := make([]*c.Char, 4)
strings[0] = c.Str("hello")
strings[1] = c.Str("world")
strings[2] = c.Str("ni")
strings[3] = c.Str("hao")
ptrtest.PrintStrings(unsafe.SliceData(strings), c.Int(4))
2024-07-18 14:52:29 +08:00
}
```
2024-07-29 19:50:23 +08:00
## LLGO for C++ Third-Party Libraries
2024-07-18 14:52:29 +08:00
Using the C++ part of the inih library as an example
2024-07-29 19:50:23 +08:00
### Installation
2024-07-18 14:52:29 +08:00
Same as installing C libraries
2024-07-29 19:50:23 +08:00
### File Structure
2024-07-18 14:52:29 +08:00
After migrating the C part of the inih library, just continue creating files in the same directory.
```
inih/
├── _demo
├── inih_demo
├──inih_demo.go
├── INIReader_demo
├──reader_demo.go
├── _wrap/cpp_wrap.cpp (optional)
└── inih.go
└── reader.go
```
2024-07-29 19:50:23 +08:00
### Writing Go Files to Link Library Functions
2024-07-18 14:52:29 +08:00
2024-07-29 19:50:23 +08:00
#### Migrating Ordinary Functions
2024-07-18 14:52:29 +08:00
Since the inih library does not have C++ style ordinary functions, we'll use an ordinary method of a class as an example. The specific process is the same.
Ordinary functions can be directly linked using the corresponding symbol in the dylib. Translate C++ symbols to readable form in the dylib directory.
```bash
2024-07-18 16:27:49 +08:00
nm -gU $(brew --prefix inih)/lib/libINIReader.dylib > output.txt
2024-07-18 14:52:29 +08:00
c++filt <output.txt> symbol.txt
```
Function prototype
```cpp
int ParseError() const;
```
Example of `symbol.txt`
```bash
0000000000002992 T INIReader::ParseError() const
```
Example of `output.txt`
```bash
0000000000002992 T __ZNK9INIReader10ParseErrorEv
```
Find the offset of the function you want to use in `symbol.txt` , then go back to `output.txt` and find the symbol corresponding to that offset.
For functions, generally use `go:linkname` to link. Here, refer to the migration method of C library functions, but bind the symbol to the C++ style symbol. Note that since C. represents a `_` , `__ZNK9INIReader10ParseErrorEv` should be written as `C._ZNK9INIReader10ParseErrorEv` .
```go
// The inih library currently does not involve ordinary functions, this is for demonstration purposes only and is not needed for migrating inih
//go:linkname ParseError C.__ZNK9INIReader10ParseErrorEv
func ParseError() c.Int
```
2024-07-29 19:50:23 +08:00
#### Migrating Classes
2024-07-18 14:52:29 +08:00
- Use a struct to map the class. The writing method is the same as migrating a struct in the C library migration:
```go
// llgo:type C
type Reader struct {
Unused [32]byte
}
```
2024-07-31 20:34:04 +08:00
- Class Methods
For general methods of the class, directly use `llgo:link` to link:
```go
// llgo:link (*Reader).GetInteger C._ZNK9INIReader10GetIntegerERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_l
func (r *Reader) GetInteger(section *std.String, name *std.String, defaultValue c.Long) c.Long {
return 0
}
```
2024-07-18 14:52:29 +08:00
- Constructor
- Constructor is explicitly declared in the class (can find the corresponding symbol in the dynamic library):
2024-07-31 15:13:30 +08:00
```cpp
class INIReader {
public:
// Construct INIReader and parse given filename.
INI_API explicit INIReader(const std::string &filename);
}
```
Bind to the `InitFromFile` method of the struct and call it in the `NewReaderFile` function to initialize the class and return the class for Go to use.
2024-07-18 14:52:29 +08:00
2024-07-31 15:13:30 +08:00
The following long string starting with `_ZN9INI` is the corresponding function prototype in the symbol table for `INIReader(const std::string &filename)`
2024-07-18 14:52:29 +08:00
```go
2024-07-31 15:13:30 +08:00
// llgo:link (*Reader).InitFromFile C._ZN9INIReaderC1ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE
func (r *Reader) InitFromFile(fileName *std.String) {}
2024-07-18 14:52:29 +08:00
// NewReaderFile creates a new INIReader instance.
func NewReaderFile(fileName *std.String) (ret Reader) {
ret.InitFromFile(fileName)
return
}
```
- Constructor is not explicitly declared in the class (cannot find the corresponding symbol in the dynamic library)
2024-07-31 15:13:30 +08:00
In typical implementations of the inih library, directly invoking implicit constructors to instantiate reader objects is not recommended. For detailed examples of how bindings effectively handle non-exported symbols, please refer to the [Templates and Inlines ](#templates-and-inlines ) section.
2024-07-31 20:34:04 +08:00
2024-07-31 12:01:29 +08:00
- Destructor
2024-07-31 20:34:04 +08:00
The accessibility of destructors in dynamic libraries depends on their definition method. Destructors that are explicitly declared in header files and implemented as non-inline functions in .cpp files typically appear in the dynamic library's symbol table. These destructors can be directly linked, consistent with the linking method of general class methods (see "Class Methods" section).
For destructors that do not meet these conditions (such as those explicitly declared in header files but implemented inline in .cpp files) and consequently do not appear in the dynamic library's symbol table, a wrapper layer implementation is required. This wrapper in the C++ wrapper file (e.g., cppWrap.cpp) looks like:
```cpp
2024-07-31 15:13:30 +08:00
extern "C" {
void INIReaderDispose(INIReader* r) {
r->~INIReader();
}
} // extern "C"
2024-07-31 20:34:04 +08:00
```
2024-07-31 12:01:29 +08:00
This wrapper function explicitly calls the object's destructor. By using extern "C", we ensure that this function can be called by C code, allowing Go to link to it.
In the Go file:
2024-07-31 20:34:04 +08:00
```go
2024-07-31 12:01:29 +08:00
// llgo:link (*Reader).Dispose C.INIReaderDispose
func (r *Reader) Dispose() {}
2024-07-31 20:34:04 +08:00
```
2024-07-31 12:01:29 +08:00
Here we link the Go Dispose method to the C++ wrapped INIReaderDispose function.
In actual usage:
2024-07-31 20:34:04 +08:00
We use defer to ensure that the Dispose method is called when the reader object goes out of scope, thus properly releasing resources.
```go
2024-07-31 12:01:29 +08:00
reader := inih.NewReader(c.Str(buf), uintptr(len(buf)))
defer reader.Dispose()
2024-07-31 20:34:04 +08:00
```
This situation is analogous to the handling of inline functions and templates described in the following section.
2024-07-18 14:52:29 +08:00
2024-07-29 19:50:23 +08:00
#### Templates and Inlines
2024-07-18 14:52:29 +08:00
Templates or inlines do not generate symbols in dynamic libraries (dylib) (default constructors and destructors). To ensure that you can use C style symbols to link template or inline functions, create a C++ file and wrap it with `extern "C"` , then bind the functions directly in Go.
2024-07-31 15:13:30 +08:00
```cpp
2024-07-18 14:52:29 +08:00
// Using std::string as an example, not needed for migrating inih
extern "C" void stdStringInitFromCStrLen(std::string* s, const char* cstr, size_t len) {
new(s) std::string(cstr, len);
}
```
Then use LLGoFiles to link in Go: the writing of standard library's `LLGoFiles` and `LLGoPackage` is slightly different from third-party libraries. Using `std::string` and `spdlog` library as examples, inih does not involve this step:
```go
// std::string
const (
LLGoFiles = "_wrap/string.cpp"
LLGoPackage = "link: c++"
)
// llgo:link (*String).InitFromCStrLen C.stdStringInitFromCStrLen
func (s *String) InitFromCStrLen(cstr *c.Char, n uintptr) {}
// If it is a third-party library, specify pkg-config, using spdlog as an example:
const (
LLGoFiles = "$(pkg-config --cflags spdlog): cppWrap/cppWrap.cpp"
LLGoPackage = "link: $(pkg-config --libs spdlog); -lspdlog -pthread -lfmt")
```
2024-07-29 19:50:23 +08:00
### Writing and Running the Demo
2024-07-18 14:52:29 +08:00
```go
package main
import (
"github.com/goplus/llgo/c"
"github.com/goplus/llgo/cpp/inih"
"github.com/goplus/llgo/cpp/std"
)
func demoFromBuffer() {
buf := `[settings]
2024-07-26 16:14:41 +08:00
username=admin
timeout=100`
2024-07-18 14:52:29 +08:00
reader := inih.NewReader(c.Str(buf), uintptr(len(buf)))
defer reader.Dispose()
println(reader.ParseError())
sec := std.Str("settings")
name := std.Str("timeout")
value := reader.GetInteger(sec, name, 0)
println("value:", value)
}
func demoFromFile() {
reader := inih.NewReaderFile(std.Str("config.ini"))
defer reader.Dispose()
if ret := reader.ParseError(); ret != 0 {
println("Error parsing config file:", ret)
return
}
isDatabaseEnabled := reader.GetBoolean(std.Str("database"), std.Str("enabled"), false)
port := reader.GetInteger(std.NewString("database"), std.Str("port"), 0)
s := reader.GetString(std.Str("database"), std.Str("server"), std.Str("unknown"))
println("s:", s.Str())
println("isDatabaseEnabled:", isDatabaseEnabled, "port:", port)
}
func main() {
demoFromBuffer()
demoFromFile()
}
```
2024-07-26 16:14:41 +08:00
Use `llgo run .` to run in the directory where the demo is written.