集成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代码里面就可以正常调用。

调用标准库的例子:

myslq库中的参考代码:

另一个集成C代码库的例子:vlib/clipboard/clipboard_linux.c.v。

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

使用$env编译时函数

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

简单封装

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

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

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

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

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

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

启用全局变量

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

函数[inline]注解

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

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

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

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

结构体简单封装

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

C代码库中的结构体:

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

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

联合体简单封装

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

枚举简单封装

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

类型别名封装

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

二级指针

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

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

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

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

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

难以封装的内容

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

  • 如果C代码库的结构体中出现内嵌的结构体或联合体,目前在V中还没有办法封装

  • 如果C代码库中的函数使用了不确定数量参数,目前也没有很好的办法封装

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

flag编译标记

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

关于C编译器的flag参数可以参考帖子

  1. 要在使用C宏之前先定义#flag。

  2. -L 在库文件的搜索路径列表中添加指定的路径。

    -l 要链接的库名称。

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

    -D 设置编译时变量。

  3. 还可以在#flag后增加平台标识,针对不同的平台配置不同的flag,目前支持的平台有:linux/darwin/windows。

以下例子,提供参考:

集成自己的C库

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

目录结构

testlib.c文件内容

linkcTest.v文件内容

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

使用v编译linkcTest.v

使用c2v

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

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

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

也可以直接使用c2v工具:

pkgconfig

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

命令行

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

  • 编译pkgconfig

切换到vlib/v/pkgconfig目录中,然后执行:

  • 使用pkgconfig命令行

    跟C版本的使用兼容,具体使用参考:

一般来说,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编译器的可选垃圾收集器使用了C版本的bdw-gc,在vlib/builtin/builtin_d_gcboehm.c.v源代码中有这么一段代码:

解析pc配置文件内容

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

Vlib/v/pkgconfig/test_sample目录中有许多pc配置的示例:

最后更新于

这有帮助吗?