# 集成C代码库

V代码库很多都直接调用C标准库函数来实现，对C标准库的依赖还是很重的。

由于V代码编译后生成的是C代码，然后再调用C编译器编译成可执行文件，这样的机制决定了V语言可以很方便地调用C世界的各种代码库。

这对于V语言来说，是个很大的一个优势，毕竟C代码库经过多年的积累，很丰富。

## 调用步骤

在V代码里调用C代码也非常简单，在V标准库里随处可见，以下是调用C代码库的基本步骤：

1. **使用#flag添加编译标志**

   这一步是可选的，比如调用C标准库的函数就不需要，一般调用第三方库才需要用到。

   flag编译标志的定义要放在C语言宏之前。
2. **在V代码中使用C语言宏**

   比如#include宏或#define宏，编译时这些宏会被原封不动地搬到生成的C代码中。
3. **使用V语法定义要使用的C函数或结构体声明**

   函数名或结构体名一定要在C名称的基础上添加`C.`前缀，主要是给V编译器使用，看到这个标记V编译器就知道这是C函数或结构体，会根据函数或结构体签名进行参数和返回值的类型检查。
4. **在V代码中调用C函数或结构体**

   调用时，名称前要使用`C.`作为前缀。

   原理其实很简单：V编译器会统一把函数和结构体名称前面的C.前缀去掉，这样在C代码里面就可以正常调用。

调用标准库的例子：

```v
module main

#include <stdio.h>  //使用C语言宏,包含头文件

fn C.getpid() int //先定义要使用的C函数声明，名字在C函数名字前统一加上C.前缀

fn main() {
	pid := C.getpid() //然后就可以在V代码中调用
	println(pid)
}
```

myslq库中的参考代码：

```v
//flag编译标志，提供给C编译器使用
#flag -lmysqlclient //C编译器链接时，就会查找libmysqlclient.a的库文件
#flag linux -I/usr/include/mysql //特定操作系统的编译标志
//宏定义
#include <mysql.h>
//声明要使用的C结构体
struct C.MYSQL
struct C.MYSQL_RES
//声明要使用的C函数
fn C.mysql_init(mysql &C.MYSQL) &C.MYSQL
fn C.mysql_real_connect(mysql &C.MYSQL, host &u8, user &u8, passwd &u8, db &u8, port u32, unix_socket &u8, clientflag u64) &C.MYSQL
fn C.mysql_query(mysql &C.MYSQL, q &u8) int
fn C.mysql_select_db(mysql &C.MYSQL, db &u8) int
fn C.mysql_error(mysql &C.MYSQL) &u8
fn C.mysql_errno(mysql &C.MYSQL) int
fn C.mysql_num_fields(res &C.MYSQL_RES) int
fn C.mysql_store_result(mysql &C.MYSQL) &C.MYSQL_RES
fn C.mysql_fetch_row(res &C.MYSQL_RES) &&u8
fn C.mysql_free_result(res &C.MYSQL_RES)
fn C.mysql_real_escape_string_quote(mysql &C.MYSQL, to &u8, from &u8, len u64, quote u8) u64
fn C.mysql_close(sock &C.MYSQL)

//调用C函数或结构体
pub fn connect(server, user, passwd, dbname string) !DB {
	conn := C.mysql_init(0)
	if isnil(conn) {
		return error_with_code(get_error_msg(conn), get_errno(conn))
	}
	conn2 := C.mysql_real_connect(conn, server.str, user.str, passwd.str, dbname.str, 0, 0, 0)
	if isnil(conn2) {
		return error_with_code(get_error_msg(conn), get_errno(conn))
	}
	return DB {conn: conn2}
}
```

另一个集成C代码库的例子：vlib/clipboard/clipboard\_linux.c.v。

使用了结构体注解\[typedef]来定义C语言的结构体。

```v
//定义C宏
#flag -lX11
#include <X11/Xlib.h>

//定义C结构体
[typedef]
struct C.Display  //在v代码中只需使用C结构体，不使用结构体字段
  
[typedef]
struct C.XSelectionRequestEvent{ //在v代码中要使用结构体字段，要定义所需的字段
	mut:
	display &C.Display	/* Display the event was read from */
	owner C.Window
	requestor C.Window
	selection C.Atom
	target C.Atom
	property C.Atom
	time int
}

//定义C函数
fn C.XInitThreads() int
fn C.XCloseDisplay(d &Display)
fn C.XFlush(d &Display)
fn C.XDestroyWindow(d &Display, w C.Window)
```

## 使用$env编译时函数

$env也可以在#flag和#include等C宏中使用，让C宏定义更灵活。

```v
module main

//可以在C宏语句中使用,让C宏定义更灵活
#flag linux -I $env('JAVA_HOME')/include

fn main() {
	compile_time_env := $env('PATH')
	println(compile_time_env)
}

```

## 简单封装

由于命名风格不一致的原因，习惯上会对C函数或结构体进行一层简单封装，名字可以重新改为V风格(小写加下划线的小蛇风格)，或者更为简短的名字。

一般来说，对一个C代码库中的简单封装会涉及到：结构体，联合体，函数，枚举这4大类，经过封装，就可以得到一个V风格的封装库。

如果是简单的C代码库，可以直接把这3类的简单封装都放在一个V源文件中。

如果C代码库规模大一些，也可以这3类，各自单独一个V源文件，归属于同一个V模块。

以下代码是GUI代码库中引用了sokol C代码库，进行了简单封装：

vlib/sokol/sokol.v部分代码：

```v
//只要在同模块中的任何一个V源文件中引入，该模块的其他源文件就可以直接使用C代码库内容
#define SOKOL_IMPL
#define SOKOL_NO_ENTRY
#include "sokol_app.h"

#define SOKOL_IMPL
#define SOKOL_NO_DEPRECATED
#include "sokol_gfx.h"

//可以直接使用，函数以C.作为前缀
pub fn init_sokol() {
	C.sapp_isvalid()
	C.sapp_width()
}
//也可以进行简单的封装
module sapp

[inline] //可以给函数添加inline注解，变为内联函数
pub fn isvalid() bool {
	return C.sapp_isvalid()
}

[inline]
pub fn width() int {
	return C.sapp_width()
}
```

## 启用全局变量

默认情况下编译器禁止全局变量声明，为了跟C代码集成，有时需要定义全局变量，可以在调用编译器时，通过增加 -enable-globals选项来启用。

```
v -enable-globals run main.v
```

```v
module main

// 单个全局变量定义
__global g1 int

// 组定义全局变量，类似常量的定义
__global (
	g2 u8 
	g3 u8 
)

fn main() {
	g1 = 1
  g2 = 2
  g3 = 3
	println(g1)
	println(g2)
	println(g3)
}
```

## 函数\[inline]注解

对C函数进行简单的封装时，可以给函数添加inline注解，编译生成C代码时，这个函数就会变成C语言里的static inline函数。

内联函数有些类似于宏，内联函数的代码会被直接嵌入在它被调用的地方，调用几次就嵌入几次，没有使用call指令。

inline函数能省去函数调用时的额外开销，提升性能。不过调用次数多的话，会使可执行文件变大。

同时也可以统一和简化C函数的命名，变为V风格的简短命名，一举多得。

```v
[inline]
pub fn width() int {
	return C.sapp_width()
}
```

## 结构体简单封装

定义C结构体等价的V版本结构体，V版本结构体名称以C.做前缀，这样就可以直接使用V版本的结构体来创建变量。

C代码库中的结构体：

```c
typedef struct sapp_event {
    uint64_t frame_count;
    sapp_event_type type;
    sapp_keycode key_code;
    uint32_t char_code;
    bool key_repeat;
    uint32_t modifiers;
    sapp_mousebutton mouse_button;
    float mouse_x;
    float mouse_y;
    float scroll_x;
    float scroll_y;
    int num_touches;
    sapp_touchpoint touches[SAPP_MAX_TOUCHPOINTS];
    int window_width;
    int window_height;
    int framebuffer_width;
    int framebuffer_height;
} sapp_event;
```

定义等价的V版本结构体：

V版本结构体可以不用所有的字段都定义一遍，可以只定义V代码中要使用的字段。

```v
pub struct C.sapp_event {
pub:
    frame_count u64
    @type EventType //这个type字段有点特殊，因为是V的关键字，要用@开头才可以
    key_code KeyCode //枚举类型
    char_code u32
    key_repeat bool
    modifiers u32
    mouse_button MouseButton //枚举类型，下面有MouseButton的V版本定义
    mouse_x f32
    mouse_y f32
    scroll_x f32
    scroll_y f32
    num_touches int
    touches [8]sapp_touchpoint //数组类型
    window_width int
    window_height int
    framebuffer_width int
    framebuffer_height int
}
```

## 联合体简单封装

基本的原理和步骤跟结构体的封装一样，关键字为union。

```v
//如果不需要访问到联合体内部的字段，只是类型引用，定义一个联合体声明就可以
[typedef]
pub union C.MIR_val_t { }
//如果需要访问到联合体内部的字段，则需要展开定义
[typedef]
pub union C.MIR_val_t {
pub mut:
	a  voidptr
	i  i64
	u  u64
	f  f32
	d  f64
	ld f64
}
```

## 枚举简单封装

其实就是定义一个跟C版本枚举一样的枚举，枚举名可以按V的风格自由定义，枚举项的整数值一定要跟C版本的枚举值对应正确，最后都是把枚举项的值转为整数，传递给C代码使用。

```v
pub enum MouseButton {
    invalid = -1
    left = 0
    right = 1
    middle = 2
}
```

## 类型别名封装

可以对C结构体和联合体进一步定义类型别名，这样在使用的时候，可以用更简洁的类型名

```v
pub type Context = C.MIR_context_t

pub type Module = C.MIR_module_t

pub type Item = C.MIR_item_t

pub type Func = C.MIR_func_t
```

## 二级指针

在实际的封装过程中，有时会碰到C代码中使用二级指针，比如：

```c
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
```

argv就是一个二级指针，表示一个字符串数组。

C语言中字符串和字符串数组都是使用指针来表示的，而在V语言中字符串数组的表达很简单：

```v
string_array := ['a', 'b', 'c']
```

V语言的数组是基于结构体实现的，`string_array.data`指向的就是这个数组的地址。

但是直接把这个地址作为参数传递给C函数的argv是不正确的。正确的应该是：

```v
//在V代码中定义C函数定义，argv的类型使用通用类型指针voidptr，charptr也可以
fn C.redisCommandArgv(ctx voidptr, argc int, argv voidptr, argvlen voidptr) 
//根据V的字符串数组，构造一个指针数组，把string_array_ptr.data传递给argv就可以得出正确的结果
string_array := ['a', 'b', 'c']
mut string_array_ptr := []&u8{} //byteptr，charptr也可以，不过还是使用&u8比较多
for s in string_array {
  string_array_ptr << s.str
}
```

## 难以封装的内容

实际项目的C集成过程中有2种情况目前比较难以封装：

* 如果C代码库的结构体中出现内嵌的结构体或联合体，目前在V中还没有办法封装
* 如果C代码库中的函数使用了不确定数量参数，目前也没有很好的办法封装

以上2种情况，目前只能自己动手写个C代码，在C代码中封装好以后，再集成到V代码中。

## flag编译标记

flag编译标记跟V编译器的-cflags选项的用法一样，用于传递额外的编译标记给C编译器。

关于C编译器的flag参数可以参考[帖子](https://colobu.com/2018/08/28/15-Most-Frequently-Used-GCC-Compiler-Command-Line-Options/)。

1. 要在使用C宏之前先定义#flag。
2. `-L` 在库文件的搜索路径列表中添加指定的路径。

   `-l` 要链接的库名称。

   `-I` 在头文件的搜索路径中添加指定的路径。

   `-D` 设置编译时变量。
3. 还可以在#flag后增加平台标识，针对不同的平台配置不同的flag，目前支持的平台有：linux/darwin/windows。

以下例子，提供参考：

```v
//#flag文档里面的:
#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=
```

```v
//mysql包里面的:
#flag -lmysqlclient //-l开头，表示在库文件的搜索路径中添加mysqlclient库文件
#flag linux -I /usr/include/mysql //-I开头，在头文件的搜索路径中添加指定的路径， 针对linux平台，这样才可以搜索到下面的mysql.h头文件
#include <mysql.h>
```

```v
//sokol包里面的:
#flag -I @VROOT/thirdparty/sokol //@VROOT指向v编译器的根路径
#flag -I @VROOT/thirdparty/sokol/util

#flag darwin -fobjc-arc  //针对mac平台，提供额外的flag编译标记:-fobjc-arc
#flag linux -lX11 -lGL    //针对linux平台，提供额外的flag编译标记

#flag windows -lgdi32 //针对window平台，提供额外的flag编译标记:-l开头，表示链接gdi32

// OPENGL
#flag -DSOKOL_GLCORE33 //提供额外的编译变量:SOKOL_GLCORE33
#flag darwin -framework OpenGL -framework Cocoa -framework QuartzCore
```

## 集成自己的C库

以下的步骤详细介绍了从零开始，开发自己的C库，并且集成到V项目中：

目录结构

```shell
D:\LINKCTEST
│  linkcTest.code-workspace
│  linkcTest.v
│  v.mod
│
├─myCheader
│      test.h
│
└─myClib
        testlib.c
test.h文件内容
```

```c
//声明自定义c函数TestFunc
int TestFunc();
```

testlib.c文件内容

```c
//自定义c函数TestFunc的具体实现
int TestFunc()
{
    return 0000;
}
```

linkcTest.v文件内容

```v
module main

//添加库文件搜索路径
#flag -LD:\linkcTest\myClib
//添加库文件名(前缀lib和后缀名.a不用指定)	
#flag -ltestlib
//添加头文件搜索路径
#flag -ID:\linkcTest\myCheader
//引入头文件test.h
#include <test.h>
//在v中声明自定义C函数
fn C.TestFunc() int

fn main() {
	println('Hello World!')
	//调用自定义C函数
	println(C.TestFunc())
}
```

首先把testlib.c文件编译为静态库

```shell
PS D:\linkcTest> gcc -c .\myClib\testlib.c -o .\myClib\testlib.o   --用gcc -c命令编译为.o文件
PS D:\linkcTest> ar -rcs .\myClib\libtestlib.a .\myClib\testlib.o  --使用ar命令打包成静态库.a文件
PS D:\linkcTest> dir .\myClib\

    Directory: D:\linkcTest\myClib

Mode                 LastWriteTime         Length Name        
----                 -------------         ------ ----        
-a---           2021/9/19    22:35            846 libtestlib.a   --库文件testlib编译成功
-a---           2021/9/19    22:29             81 testlib.c   
-a---           2021/9/19    22:35            700 testlib.o   

PS D:\linkcTest>
```

使用v编译linkcTest.v

```shell
PS D:\linkcTest> v -cc gcc -showcc .\linkcTest.v  --使用-showcc参数查看v使用的编译命令
> C compiler cmd: gcc "@C:\Users\xxxxx\AppData\Local\Temp\v\linkcTest.14127686245975582114.tmp.c.rsp"
> C compiler response file "C:\Users\xxxxx\AppData\Local\Temp\v\linkcTest.14127686245975582114.tmp.c.rsp":
  -std=c99 -D_DEFAULT_SOURCE -o "D:\\linkcTest\\linkcTest.exe"
  -L "D:\\linkcTest\\myClib"     --#flag定义
  -I "D:\\linkcTest\\myCheader"  --#flag定义
  "C:\\Users\\xxxxx\\AppData\\Local\\Temp\\v\\linkcTest.14127686245975582114.tmp.c" -municode -ldbghelp
  -ltestlib                      --#flag定义
PS D:\linkcTest> .\linkcTest.exe
Hello World!
0                                --TestFunc调用成功
```

## 使用c2v

除了手工封装C代码库外，也可以使用c2v工具，将C源代码编译成V源代码，或者自动封装C代码库，提供给V代码调用。

c2v项目代码库：<https://github.com/vlang/c2v>

最简单的方式就是使用translate子命令，translate子命令也是调用的c2v工具。

```shell
v translate main.c
v translate wrapper main.c
```

也可以直接使用c2v工具：

```shell
c2v file.c
c2v wrapper file.c
```

## pkgconfig

pkg-config是C广泛使用的编译依赖配置工具，V也实现了V版本的pkgconfig，使用方法跟C版本保持了兼容。

### 命令行

V版本的pkgconfig源代码位于：vlib/v/pkgconfig

* 编译pkgconfig

切换到vlib/v/pkgconfig目录中，然后执行：

```shell
v ./bin/pkgconfig.v
```

* 使用pkgconfig命令行

  跟C版本的使用兼容，具体使用参考：

```shell
./bin/pkgconfig --help
```

一般来说，pkgconfig命令行比较少直接使用，一般都是通过在V源代码中加载pc配置文件。

### 加载pc配置文件

V版本的pkgconfig配置文件跟C版本一致，配置文件的扩展名也是.pc(package configuration)。

直接在V源代码中加载.pc配置文件，就可以更方便地实现跟C代码库实现集成，能够正确编译。

一般来说，在加载之前先使用$pkgconfig()编译时函数来检查pc配置文件是否存在，如果存在，就可以使用#pkgconfig标记来加载.pc配置文件。

配置文件的搜索路径跟C版本一样：

* 默认会搜索/usr/lib/pkg-config和/usr/local/lib/pkg-config目录
* 若找不到，则会去PKG\_CONFIG\_PATH环境变量指定的路径下查找

```v
$if $pkgconfig('mysqlclient') {	//编译时判断mysqlclient.pc配置文件是否存在
	#pkgconfig mysqlclient //如果存在，则加载mysqlclient.pc配置文件
} $else $if $pkgconfig('mariadb') { 
	#pkgconfig mariadb
}
```

在实际的例子中，V编译器的可选垃圾收集器使用了C版本的bdw-gc，在vlib/builtin/builtin\_d\_gcboehm.c.v源代码中有这么一段代码：

```v
	$if macos {
		#pkgconfig bdw-gc		//加载bdw-gc.pc配置文件到C源代码中
	} $else $if openbsd || freebsd {
		#flag -I/usr/local/include
		#flag -L/usr/local/lib
	}
```

### 解析pc配置文件内容

除了可以在源代码中直接加载pc配置文件到生成的C源代码中，还可以使用v.pkgconig模块来解析pc配置文件中的内容。

Vlib/v/pkgconfig/test\_sample目录中有许多pc配置的示例：

```v
import os
import v.pkgconfig

const vexe = os.getenv('VEXE')

const vroot = os.dir(vexe)

const samples_dir = os.join_path(vroot, 'vlib', 'v', 'pkgconfig', 'test_samples')

fn main() {
	pc_files := os.walk_ext(samples_dir, '.pc')
	assert pc_files.len > 0
	for pc in pc_files {
		pcname := os.file_name(pc).replace('.pc', '')
    //加载pc文件，并解析
		x := pkgconfig.load(pcname, use_default_paths: false, path: samples_dir) or {
			if pcname == 'dep-resolution-fail' {
				continue
			}
			println('>>> err: $err')
			assert false
			return
		}
		assert x.name != ''
		assert x.modname != ''
		assert x.version != ''
		if pcname == 'gmodule-2.0' {
			assert x.name == 'GModule'
			assert x.modname == 'gmodule-2.0'
			assert x.url == ''
			assert x.version == '2.64.3'
			assert x.description == 'Dynamic module loader for GLib'
			assert x.libs == ['-Wl,--export-dynamic', '-L/usr/lib/x86_64-linux-gnu', '-lgmodule-2.0',
				'-pthread', '-lglib-2.0', '-lpcre']
			assert x.libs_private == ['-ldl', '-pthread']
			assert x.cflags == ['-I/usr/include', '-pthread', '-I/usr/include/glib-2.0',
				'-I/usr/lib/x86_64-linux-gnu/glib-2.0/include',
			]
			assert x.vars == {
				'prefix':            '/usr'
				'libdir':            '/usr/lib/x86_64-linux-gnu'
				'includedir':        '/usr/include'
				'gmodule_supported': 'true'
			}
			assert x.requires == ['gmodule-no-export-2.0', 'glib-2.0']
			assert x.requires_private == []
			assert x.conflicts == []
		}
		if x.name == 'expat' {
			assert x.url == 'http://www.libexpat.org'
		}
		if x.name == 'GLib' {
			assert x.modname == 'glib-2.0'
			assert x.libs == ['-L/usr/lib/x86_64-linux-gnu', '-lglib-2.0', '-lpcre']
			assert x.libs_private == ['-pthread']
			assert x.cflags == ['-I/usr/include/glib-2.0',
				'-I/usr/lib/x86_64-linux-gnu/glib-2.0/include', '-I/usr/include']
			assert x.vars == {
				'prefix':          '/usr'
				'libdir':          '/usr/lib/x86_64-linux-gnu'
				'includedir':      '/usr/include'
				'bindir':          '/usr/bin'
				'glib_genmarshal': '/usr/bin/glib-genmarshal'
				'gobject_query':   '/usr/bin/gobject-query'
				'glib_mkenums':    '/usr/bin/glib-mkenums'
			}
			assert x.requires_private == ['libpcre']
			assert x.version == '2.64.3'
			assert x.conflicts == []
		}
	}
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lydiandylin.gitbook.io/vlang/mu-lu/c.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
