C and C++
A Gizlang program calls C and C++ code in its own process. You write no glue code and no build file: clang reads the
C or C++ file, and the giz command makes the module, compiles the code and links it.
| You have | Do this |
|---|---|
| A C or C++ source file | Import the file as a module |
| A C library and its header | Import a header of two lines |
| One function of a library | Declare the function |
giz run, giz check, giz test and giz build all work with C and C++ files. clang must be installed when you
build from a source checkout; an installed toolchain has its own clang.
Import a C file
Put the C file beside the program and import it as you import a module.
#include <stddef.h>
#include <stdint.h>
#define STATS_VERSION 2
typedef struct { int32_t low; int32_t high; } Range;
/* The lowest and the highest byte of DATA. */
Range span(const uint8_t *data, size_t length) {
Range r = { 255, 0 };
for (size_t i = 0; i < length; i++) {
if (data[i] < r.low) r.low = data[i];
if (data[i] > r.high) r.high = data[i];
}
return r;
}
/* The count of BYTE in DATA. FIRST gets the index of the first one, or -1. */
size_t count(const uint8_t *data, size_t length, uint8_t byte, int64_t *first) {
size_t n = 0;
*first = -1;
for (size_t i = 0; i < length; i++) {
if (data[i] == byte) {
if (n == 0) *first = (int64_t)i;
n++;
}
}
return n;
}import {STATS_VERSION, Range, span, count} from "./stats.c";
function report(data: bytes): void {
const r: Range = span(data, u64(data.length));
let first = 0;
const n = count(data, u64(data.length), 97u, first);
console.log(STATS_VERSION, r.low, r.high, n, first);
}
report(b"banana");2 97 110 3 1- The functions, structs, enums and simple macros of the file are the exports of the module. So are those of each
file that it includes with
#include "...". A file that it includes with#include <...>is only read. Rangeis a record with the layout of the C struct. A struct goes in and out by value.int64_t *firstis an out-parameter: pass aletlocal, and C writes it.- A change of the C file, or of a file that it includes in quotes, compiles it again. Otherwise the object comes from the cache.
- A C error stops
giz checkwith clang's message and the line in the C file.
Use a C library
Write a header of two lines in the program's folder. #pragma comment(lib, "NAME") links the library: with the flags
of pkg-config when pkg-config knows NAME, else with -lNAME. #include "..." in quotes makes the functions of the
library's header the exports of the module.
#pragma comment(lib, "zlib")
#include "zlib.h"With that file saved as zip.h, the program calls zlib:
import {crc32, zlibVersion} from "./zip.h";
console.log(crc32(0u, b"hello", 5u), zlibVersion());Use C++
A C++ file (.cc, .cpp, .cxx, .hh, .hpp, .hxx) is imported in the same way. It also links the C++ library.
#include <stdexcept>
#include <string>
namespace demo {
std::string greet(const std::string &name) { return "hello, " + name; }
int divide(int a, int b) {
if (b == 0) throw std::invalid_argument("divide by zero");
return a / b;
}
class Counter {
public:
explicit Counter(int start) : count(start) {}
void add(int n) { count += n; }
int value() const { return count; }
private:
int count;
};
}import * as Demo from "./counter.cpp";
const c = new Demo.Counter(5);
c.add(2);
console.log(Demo.greet("C++"), c.value());
c.close();
try {
Demo.divide(1, 0);
} catch (e) {
console.log(`caught ${e}`);
}hello, C++ 7
caught divide by zerostd::stringandstd::string_viewarestring.- A class with members other than scalar and pointer fields is a record
{ handle: u64 }, the address of the object.new Name(...)runs its first constructor, and its public methods are methods of the record. A static method is the functionName_method.close()deletes the object; call it once. - A function that is not
noexceptcatches a C++ exception and throws its message as astring. - The names of a namespace are the exports, without the namespace.
Declare one C function
To call one function of a library, declare it. from "LIBRARY" links -lLIBRARY; without it, the function comes from
the C library.
declare function hypot(x: f64, y: f64): f64 effects {} from "m";
console.log(hypot(3.0, 4.0));5effects {} says that the function has no effects that Gizlang can see. Without a row, the effects are
{io, fail}. Gizlang cannot see into C, so the row is your promise.
giz bind HEADER LIBRARY NAME... writes these declarations from a C header
(the tools).
Types
The types of an imported file and of a declaration are the same:
| Gizlang | C |
|---|---|
i8 to i64, u8 to u64, f32, f64, bool | The integer, float or bool of that width. A value that does not fit the parameter fails (R003). |
string | A const char * to a copy that ends with NUL. A string that contains NUL fails (R003). As the result, the returned text is copied. NULL is "". Invalid UTF-8 fails. |
bytes | A pointer to the first byte, or NULL when empty. Pass the length as a separate argument. |
| A record of the scalars above | A pointer to a C struct with those fields in order. An imported file also passes it by value. |
mut scalar | A pointer to it: an out-parameter of C. It is read back into the local after the call. |
mut bytes, mut record | C writes into the buffer or struct of the local. |
u64 | Any other pointer, for example a handle that C returns (sqlite3 *). |
(a: T, ...) => R | A C function pointer. The argument names a top-level function whose types are the value types of the signature (i64 for i32). A maximum of 8 scalar parameters. R is a scalar or void. |
In an imported file, these C types also map:
int64_t,size_tand the other names of<stdint.h>and<stddef.h>are their scalars.void *is au64. A pointer to bytes (char *,uint8_t *) isbytes. A pointer to another scalar is amutargument.- The names of an enum, and a macro of an integer, a float or a string, are constants.
These are not exported, and a comment in the generated module says why: variadic functions, templates, operators,
unions, structs with fields that are not scalars, and long double. To use one, write a small C or C++ function in
your file that calls it with the types above.
Calls and callbacks
- A
mutargument is aletlocal. Module state cannot be passed asmut. - A callback runs on the calling task. After a fault inside a callback, later calls return 0 to C without running. The fault fails the caller of the C function when C returns.
- A callback result that does not fit its C type fails (R003).
- A callback receives pointers as
u64. To read through one, declare a C function such asmemcpy. - A C call that can fail (R003, a string with NUL, a failed callback) makes the function that calls it fail, as any other fault does.
Authority
A C function has all the authority of the process, and the checker cannot see into it.
- Only the application can declare a C function: its entry module, or a package module whose manifest entry grants
the library (
"native": ["m"], or"c"for functions withoutfrom). Any other declaration isE_MODULE. - The manifest of a dependency can grant only what its parent granted to it (the same
"native"on its entry independencies). If not, the package isE_PACKAGE. - The CLI grants an imported C or C++ file the object that it built, so its functions run. An
"imports"limit decides which modules may import it (packages). - Symbols that start with
gizlang_are runtime services. Only the standard library declares them.
How the CLI reads, compiles and caches the files is in the system internals.