V and C

Calling C from V

V currently does not have a parser for C code. That means that even though it allows you to #include existing C header and source files, it will not know anything about the declarations in them. The #include statement will only appear in the generated C code, to be used by the C compiler backend itself.

Example of #include

#include <stdio.h>

After this statement, V will not know anything about the functions and structs declared in stdio.h, but if you try to compile the .v file, it will add the include in the generated C code, so that if that header file is missing, you will get a C error (you will not in this specific case, if you have a proper C compiler setup, since <stdio.h> is part of the standard C library).

To overcome that limitation (that V does not have a C parser), V needs you to redeclare the C functions and structs, on the V side, in your .c.v files. Note that such redeclarations only need to have enough details about the functions/structs that you want to use. Note also that they do not have to be complete, unlike the ones in the .h files.

C. struct redeclarations For example, if a struct has 3 fields on the C side, but you want to only refer to 1 of them, you can declare it like this:

Example of C struct redeclaration

struct C.NameOfTheStruct { a_field int }

Another feature, that is very frequently needed for C interoperability, is the @[typedef] attribute. It is used for marking C. structs, that are defined with typedef struct SomeName { ..... } TypeName; in the C headers.

For that case, you will have to write something like this in your .c.v file:

@[typedef] pub struct C.TypeName { }

Note that the name of the C. struct in V, is the one after the struct SomeName {...}.

C. function redeclarations The situation is similar for C. functions. If you are going to call just 1 function in a library, but its .h header declares dozens of them, you will only need to declare that single function, for example:

Example of C function redeclaration

fn C.name_of_the_C_function(param1 int, const_param2 &char, param3 f32) f64

... and then later, you will be able to call the same way you would V function:

f := C.name_of_the_C_function(123, c'here is some C style string', 1.23) dump(f)

Example of using a C function from stdio, by redeclaring it on the V side

#include <stdio.h> // int dprintf(int fd, const char *format, ...) fn C.dprintf(fd int, const_format &char, ...voidptr) int value := 12345 x := C.dprintf(0, c'Hello world, value: %d\n', value) dump(x)

If your C backend compiler is properly setup, you should see something like this, when you try to run it:

#0 10:42:32 /v/examples> v run a.v
Hello world, value: 12345
[a.v:8] x: 26
#0 10:42:33 /v/examples>

Note, that the C function redeclarations look very simillar to the V ones, with some differences:

  1. They lack a body (they are defined on the C side) .
  2. Their names start with C. .
  3. Their names can have capital letters (unlike V ones, that are required to use snake_case) .

Note also the second parameter const char *format, which was redeclared as const_format &char . The const_ prefix in that redeclaration may seem arbitrary, but it is important, if you want to compile your code with -cstrict or thirdparty C static analysis tools. V currently does not have another way to express that this parameter is a const (this will probably change in V 1.0).

For some C functions, that use variadics (...) as parameters, V supports a special syntax for the parameters - ...voidptr, that is not available for ordinary V functions (V's variadics are required to have the same exact type). Usually those are functions of the printf/scanf family i.e for printf, fprintf, scanf, sscanf, etc, and other formatting/parsing/logging functions.

Example

#flag freebsd -I/usr/local/include -L/usr/local/lib #flag -lsqlite3 #include "sqlite3.h" // See also the example from https://www.sqlite.org/quickstart.html pub struct C.sqlite3 { } pub struct C.sqlite3_stmt { } type FnSqlite3Callback = fn (voidptr, int, &&char, &&char) int fn C.sqlite3_open(&char, &&C.sqlite3) int fn C.sqlite3_close(&C.sqlite3) int fn C.sqlite3_column_int(stmt &C.sqlite3_stmt, n int) int // ... you can also just define the type of parameter and leave out the C. prefix fn C.sqlite3_prepare_v2(&C.sqlite3, &char, int, &&C.sqlite3_stmt, &&char) int fn C.sqlite3_step(&C.sqlite3_stmt) fn C.sqlite3_finalize(&C.sqlite3_stmt) fn C.sqlite3_exec(db &C.sqlite3, sql &char, cb FnSqlite3Callback, cb_arg voidptr, emsg &&char) int fn C.sqlite3_free(voidptr) fn my_callback(arg voidptr, howmany int, cvalues &&char, cnames &&char) int { unsafe { for i in 0 .. howmany { print('| ${cstring_to_vstring(cnames[i])}: ${cstring_to_vstring(cvalues[i]):20} ') } } println('|') return 0 } fn main() { db := &C.sqlite3(unsafe { nil }) // this means `sqlite3* db = 0` // passing a string literal to a C function call results in a C string, not a V string C.sqlite3_open(c'users.db', &db) // C.sqlite3_open(db_path.str, &db) query := 'select count(*) from users' stmt := &C.sqlite3_stmt(unsafe { nil }) // Note: You can also use the `.str` field of a V string, // to get its C style zero terminated representation C.sqlite3_prepare_v2(db, &char(query.str), -1, &stmt, 0) C.sqlite3_step(stmt) nr_users := C.sqlite3_column_int(stmt, 0) C.sqlite3_finalize(stmt) println('There are ${nr_users} users in the database.') error_msg := &char(0) query_all_users := 'select * from users' rc := C.sqlite3_exec(db, &char(query_all_users.str), my_callback, voidptr(7), &error_msg) if rc != C.SQLITE_OK { eprintln(unsafe { cstring_to_vstring(error_msg) }) C.sqlite3_free(error_msg) } C.sqlite3_close(db) }

Calling V from C

Since V can compile to C, calling V code from C is very easy, once you know how.

Use v -o file.c your_file.v to generate a C file, corresponding to the V code.

More details in call_v_from_c example.

Passing C compilation flags

Add #flag directives to the top of your V files to provide C compilation flags like:

  • -I for adding C include files search paths
  • -l for adding C library names that you want to get linked
  • -L for adding C library files search paths
  • -D for setting compile time variables

You can (optionally) use different flags for different targets. Currently the linux, darwin , freebsd, and windows flags are supported.

[!NOTE] Each flag must go on its own line (for now)

#flag linux -lsdl2 #flag linux -Ivig #flag linux -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 #flag linux -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1 #flag linux -DIMGUI_IMPL_API=

In the console build command, you can use:

  • -cc to change the default C backend compiler.
  • -cflags to pass custom flags to the backend C compiler (passed before other C options).
  • -ldflags to pass custom flags to the backend C linker (passed after every other C option).
  • For example: -cc gcc-9 -cflags -fsanitize=thread.

You can define a VFLAGS environment variable in your terminal to store your -cc and -cflags settings, rather than including them in the build command each time.

#pkgconfig

Add #pkgconfig directives to tell the compiler which modules should be used for compiling and linking using the pkg-config files provided by the respective dependencies.

As long as backticks can't be used in #flag and spawning processes is not desirable for security and portability reasons, V uses its own pkgconfig library that is compatible with the standard freedesktop one.

If no flags are passed it will add --cflags and --libs to pkgconfig (not to V). In other words, both lines below do the same:

#pkgconfig r_core #pkgconfig --cflags --libs r_core

The .pc files are looked up into a hardcoded list of default pkg-config paths, the user can add extra paths by using the PKG_CONFIG_PATH environment variable. Multiple modules can be passed.

To check the existence of a pkg-config use $pkgconfig('pkg') as a compile time "if" condition to check if a pkg-config exists. If it exists the branch will be created. Use $else or $else $if to handle other cases.

$if $pkgconfig('mysqlclient') { #pkgconfig mysqlclient } $else $if $pkgconfig('mariadb') { #pkgconfig mariadb }

Including C code

You can also include C code directly in your V module. For example, let's say that your C code is located in a folder named 'c' inside your module folder. Then:

  • Put a v.mod file inside the toplevel folder of your module (if you created your module with v new you already have v.mod file). For example:
Module { name: 'mymodule', description: 'My nice module wraps a simple C library.', version: '0.0.1' dependencies: [] }
  • Add these lines to the top of your module:
#flag -I @VMODROOT/c #flag @VMODROOT/c/implementation.o #include "header.h"

[!NOTE] @VMODROOT will be replaced by V with the nearest parent folder, where there is a v.mod file. Any .v file beside or below the folder where the v.mod file is, can use #flag @VMODROOT/abc to refer to this folder. The @VMODROOT folder is also prepended to the module lookup path, so you can import other modules under your @VMODROOT, by just naming them.

The instructions above will make V look for an compiled .o file in your module folder/c/implementation.o. If V finds it, the .o file will get linked to the main executable, that used the module. If it does not find it, V assumes that there is a @VMODROOT/c/implementation.c file, and tries to compile it to a .o file, then will use that.

This allows you to have C code, that is contained in a V module, so that its distribution is easier. You can see a complete minimal example for using C code in a V wrapper module here: project_with_c_code. Another example, demonstrating passing structs from C to V and back again: interoperate between C to V to C.

C types

Ordinary zero terminated C strings can be converted to V strings with unsafe { &char(cstring).vstring() } or if you know their length already with unsafe { &char(cstring).vstring_with_len(len) }.

[!NOTE] The .vstring() and .vstring_with_len() methods do NOT create a copy of the cstring, so you should NOT free it after calling the method .vstring(). If you need to make a copy of the C string (some libc APIs like getenv pretty much require that, since they return pointers to internal libc memory), you can use cstring_to_vstring(cstring).

On Windows, C APIs often return so called wide strings (utf16 encoding). These can be converted to V strings with string_from_wide(&u16(cwidestring)) .

V has these types for easier interoperability with C:

  • voidptr for C's void*,
  • &u8 for C's byte* and
  • &char for C's char*.
  • &&char for C's char**

To cast a voidptr to a V reference, use user := &User(user_void_ptr).

voidptr can also be dereferenced into a V struct through casting: user := User(user_void_ptr).

an example of a module that calls C code from V

C Declarations

C identifiers are accessed with the C prefix similarly to how module-specific identifiers are accessed. Functions must be redeclared in V before they can be used. Any C types may be used behind the C prefix, but types must be redeclared in V in order to access type members.

To redeclare complex types, such as in the following C code:

struct SomeCStruct { uint8_t implTraits; uint16_t memPoolData; union { struct { void* data; size_t size; }; DataView view; }; };

members of sub-data-structures may be directly declared in the containing struct as below:

pub struct C.SomeCStruct { implTraits u8 memPoolData u16 // These members are part of sub data structures that can't currently be represented in V. // Declaring them directly like this is sufficient for access. // union { // struct { data voidptr size usize // } view C.DataView // } }

The existence of the data members is made known to V, and they may be used without re-creating the original structure exactly.

Alternatively, you may embed the sub-data-structures to maintain a parallel code structure.

Export to shared library

By default all V functions have the following naming scheme in C: [module name]__[fn_name].

For example, fn foo() {} in module bar will result in bar__foo().

To use a custom export name, use the @[export] attribute:

@[export: 'my_custom_c_name']
fn foo() {
}

Translating C to V

V can translate your C code to human readable V code, and generating V wrappers on top of C libraries.

C2V currently uses Clang's AST to generate V, so to translate a C file to V you need to have Clang installed on your machine.

Let's create a simple program test.c first:

#include "stdio.h" int main() { for (int i = 0; i < 10; i++) { printf("hello world\n"); } return 0; }

Run v translate test.c, and V will generate test.v:

fn main() { for i := 0; i < 10; i++ { println('hello world') } }

To generate a wrapper on top of a C library use this command:

v translate wrapper c_code/libsodium/src/libsodium

This will generate a directory libsodium with a V module.

Example of a C2V generated libsodium wrapper:

https://github.com/vlang/libsodium


When should you translate C code and when should you simply call C code from V?

If you have well-written, well-tested C code, then of course you can always simply call this C code from V.

Translating it to V gives you several advantages:

  • If you plan to develop that code base, you now have everything in one language, which is much safer and easier to develop in than C.
  • Cross-compilation becomes a lot easier. You don't have to worry about it at all.
  • No more build flags and include files either.

Working around C issues

In some cases, C interop can be extremely difficult. One of these such cases is when headers conflict with each other. For example, V needs to include the Windows header libraries in order for your V binaries to work seamlessly across all platforms.

However, since the Windows header libraries use extremely generic names such as Rectangle, this will cause a conflict if you wish to use C code that also has a name defined as Rectangle.

For very specific cases like this, we have #preinclude.

This will allow things to be configured before V adds in its built in libraries.

Example usage:

// This will include before built in libraries are used. #preinclude "pre_include.h" // This will include after built in libraries are used. #include "include.h"

An example of what might be included in pre_include.h can be found here

This is an advanced feature, and will not be necessary outside of very specific cases with C interop, meaning it could cause more issues than it solves.

Consider it last resort!