当前位置: 首页 > news >正文

Zig FFI与第三方C库的集成与使用

Zig FFI与第三方C库的集成与使用

Zig的官方文档中没有对于与第三方C库集成说明太多,实际使用时,出现很多问题。

  1. C的数据类型与Zig数据类型的对照。

    官方有基础类型的,对于字符串,结构体,特别是指针,官方直接不建议使用!但是实际上使用cimport进来的很多数据类型,都是C风格的指针,需要用户自己处理!这是最大的坑点.

  2. C中的Union结构体,如何在zig中读取和解析

    官方默认的实现是?*anyopaque, 不明确的指针,需要用户自己强转。

  3. Zig的fmt/print相关函数,字符串格式化具体参数与说明在哪里?

    直接参考文章第一小节。

这里以集成libclibmpv为例子,展示一个完整的zig集成第三方C lib库遇到的坑点的解决办法!(虽然解决办法不完美,但是基本够用了)

std.fmt

针对各种print的格式化控制

https://zig.guide/standard-library/formatting-specifiers

https://zig.guide/standard-library/advanced-formatting

Linux LibC

stdio.h

/usr/include/x86_64-linux-gnu/bits/stdio.h
/usr/include/c++/12/tr1/stdio.h
/usr/include/stdio.h

zig code

// build.zigconst std = @import("std");pub fn build(b: *std.Build) void {const target = b.standardTargetOptions(.{});const optimize = b.standardOptimizeOption(.{});const exe = b.addExecutable(.{.name = "zdemo",.root_source_file = b.path("src/main.zig"),.target = target,.optimize = optimize,});exe.linkLibC();b.installArtifact(exe);// run commandconst run_cmd = b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());// This allows the user to pass arguments to the application in the build// command itself, like this: `zig build run -- arg1 arg2 etc`if (b.args) |args| {run_cmd.addArgs(args);}const run_step = b.step("run", "Run the app");run_step.dependOn(&run_cmd.step);
}// src/main.zig
const std = @import("std");const io = @cImport({@cInclude("stdio.h");  // /user/include/stdio.h
});pub fn main() !void {_ = io.printf("Hello, i am from C lib!\r\n");
}

重点解析:

  • 所有的返回值(如printf),必须得处理,遵循zig要求
  • build.zig中需要link libc(如果是其他库,要link其他库)

linux mpv

https://mpv.io/manual/stable/#list-of-input-commands

pkg-config

$ pkg-config --list-all |grep mpv
mpv                            mpv - mpv media player client library$ pkg-config --cflags mpv
-I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/harfbuzz -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/fribidi -I/usr/include/x86_64-linux-gnu -I/usr/include/libxml2 -I/usr/include/lua5.2 -I/usr/include/SDL2 -I/usr/include/uchardet -I/usr/include/pipewire-0.3 -I/usr/include/spa-0.2 -D_REENTRANT -I/usr/include/libdrm -I/usr/include/sixel -I/usr/include/spirv_cross $ pkg-config --libs mpv
-lmpv # mpv 的头文件
$ ls /usr/include/mpv/
client.h  render_gl.h  render.h  stream_cb.h

zig code

// build.zig
const std = @import("std");pub fn build(b: *std.Build) void {const target = b.standardTargetOptions(.{});const optimize = b.standardOptimizeOption(.{});const exe = b.addExecutable(.{.name = "zdemo",.root_source_file = b.path("src/main.zig"),.target = target,.optimize = optimize,});exe.linkLibC();exe.linkSystemLibrary("mpv");b.installArtifact(exe);// run commandconst run_cmd = b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());// This allows the user to pass arguments to the application in the build// command itself, like this: `zig build run -- arg1 arg2 etc`if (b.args) |args| {run_cmd.addArgs(args);}const run_step = b.step("run", "Run the app");run_step.dependOn(&run_cmd.step);
}// utils.zig
pub fn cstring2slice(buff: [*c]const u8) []const u8 {var i: usize = 0;while (buff[i] != 0) : (i += 1) {}return buff[0..i];
}pub fn slice2cstring(slice: [:0]u8) [*c]const u8 {const ptr = slice.ptr;const buff: [*c]const u8 = @constCast(ptr);return buff;
}// main.zig
const std = @import("std");
const libmpv = @cImport({@cInclude("/usr/include/mpv/client.h");
});
// const libc = @cImport({
//     @cInclude("stdio.h");
// });const util = @import("utils.zig");
var g_postition: i64 = 0;
var g_duration: i64 = 0;pub fn main() !void {var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);defer arena.deinit();const allocator = arena.allocator();const mpv_handle = libmpv.mpv_create();if (mpv_handle == null) {std.debug.print("Failed to create mpv context.\n", .{});return;}defer libmpv.mpv_destroy(mpv_handle);// propertys_ = libmpv.mpv_set_property_string(mpv_handle, "vid", "no");_ = libmpv.mpv_set_property_string(mpv_handle, "input-default-bindings", "yes");// events_ = libmpv.mpv_observe_property(mpv_handle, 0, "time-pos", libmpv.MPV_FORMAT_INT64);_ = libmpv.mpv_observe_property(mpv_handle, 0, "duration", libmpv.MPV_FORMAT_INT64);_ = libmpv.mpv_request_log_messages(mpv_handle, "no"); // infoif (libmpv.mpv_initialize(mpv_handle) < 0) {std.debug.print("Failed to initialize mpv context.\n", .{});return;}// load file{const cmd = [_][*c]const u8{"loadfile", // MPV 命令"/home/andy/音乐/test/1.mp3", // 文件名null, // 结束符 NULL};const cmd_ptr: [*c][*c]const u8 = @constCast(&cmd);_ = libmpv.mpv_command(mpv_handle, cmd_ptr);}// wait for eventswhile (true) {const event = libmpv.mpv_wait_event(mpv_handle, 0).*; // 解引用*c的指针到zig structif (event.event_id == libmpv.MPV_EVENT_NONE) continue;// const e_name = libmpv.mpv_event_name(event.event_id);// _ = libc.printf("event: %d => %s\n", event.event_id, e_name);switch (event.event_id) {libmpv.MPV_EVENT_PROPERTY_CHANGE => {const prop: *libmpv.mpv_event_property = @ptrCast(@alignCast(event.data));const name = util.cstring2slice(prop.name);// std.debug.print("property: {s} format: {d}\n", .{ name, prop.format });// _ = libc.printf("prop: %s\r\n", name);if (prop.format == libmpv.MPV_FORMAT_INT64 and std.mem.eql(u8, name, "time-pos")) {if (prop.data) |ptr| {const time_pos: *i64 = @ptrCast(@alignCast(ptr));g_postition = time_pos.*;var percent: f32 = 0.0;if (g_duration > 0) {percent = @as(f32, @floatFromInt(g_postition)) / @as(f32, @floatFromInt(g_duration)) * 100.0;}std.debug.print("progress: {} - {} => {d:.1}\n", .{ g_postition, g_duration, percent });}}if (std.mem.eql(u8, name, "duration")) {if (prop.data) |ptr| {const duration: *i64 = @ptrCast(@alignCast(ptr));g_duration = duration.*;std.debug.print("duration: {d}\n", .{duration.*});}}},libmpv.MPV_EVENT_LOG_MESSAGE => {const msg: *libmpv.mpv_event_log_message = @ptrCast(@alignCast(event.data));const text: [*]const u8 = @constCast(msg.text);const level: [*]const u8 = @constCast(msg.level);std.debug.print("log: {s} => {s}\n", .{ util.cstring2slice(level), util.cstring2slice(text) });// _ = libc.printf("log: %s => %s\n", level, text);},libmpv.MPV_EVENT_FILE_LOADED => {std.debug.print("file loaded.\n", .{});{// parse metadata// https://mpv.io/manual/stable/#command-interface-metadatavar meta_count: i64 = 0;_ = libmpv.mpv_get_property(mpv_handle, "metadata/list/count", libmpv.MPV_FORMAT_INT64, &meta_count);// std.debug.print("metadata count: {d}\n", .{meta_count});for (0..@as(usize, @intCast(meta_count))) |i| {const kk = try std.fmt.allocPrintZ(allocator, "metadata/list/{d}/key", .{i});const vv = try std.fmt.allocPrintZ(allocator, "metadata/list/{d}/value", .{i});// std.debug.print("metadata keys: {s} => {s}\n", .{ kk, vv });const k = libmpv.mpv_get_property_string(mpv_handle, util.slice2cstring(kk));const v = libmpv.mpv_get_property_string(mpv_handle, util.slice2cstring(vv));std.debug.print("metadata: {s} => {s}\n", .{ util.cstring2slice(k), util.cstring2slice(v) });}}{// seek to 90%const cmd = [_][*c]const u8{"seek", // MPV 命令"50","absolute-percent",null, // 结束符 NULL};const cmd_ptr: [*c][*c]const u8 = @constCast(&cmd);_ = libmpv.mpv_command(mpv_handle, cmd_ptr);}},libmpv.MPV_EVENT_END_FILE => {std.debug.print("file end.\n", .{});loadfile(mpv_handle, "/home/andy/音乐/test/1.mp3");},libmpv.MPV_EVENT_SHUTDOWN => {std.debug.print("shutdown.\n", .{});break;},else => {std.debug.print("null process event: {d}\n", .{event.event_id});},}}
}fn loadfile(mpv_handle: ?*libmpv.mpv_handle, filename: []const u8) void {// load fileconst cmd = [_][*c]const u8{"loadfile", // MPV 命令@as([*c]const u8, @constCast(filename.ptr)), // 文件名null, // 结束符 NULL};const cmd_ptr: [*c][*c]const u8 = @constCast(&cmd);_ = libmpv.mpv_command(mpv_handle, cmd_ptr);
}

难点解析:

  • [*c]const u8, [*]const u8, []const u8, []u8他们之间的互相转换。
    • 对于C库,都需要转为[*c]const u8提供字符串。
    • 对于zig,都需要转为[]u8, []const u8提供字符串。
    • 转换的重点是 @as(xxx, @constCast(yy)). zig中的slice有一个ptr的指针,可以直接使用,或者使用slice首个元素的地址/数组地址。
  • [*c][*c]const u8如何初始化?
    • 没有初始化的办法,只能通过数组做类型转换
    • 先构造[_][*c]const u8的数组,然后对数组指针进行类型转换[*c][*c]const u8
    • 最后一个元素一定要是null, 防止C数组越界。
  • C语言的union 对应zig的?*anyopaque,如何提取数据?
    • 通过强转zig中的指针,var a:*i64 = @ptrCast(@alignCast(data_ptr)); var b = a.*; 这样子就可以得到。本质上还是指针的类型转换,然后加上zig的解引用。
  • 对于复合错误类型的返回值,可以使用try直接取值。
  • 对于?*type可以使用if(xx) | x | {} 解开使用。
  • 字符串格式化,与C差别很大,需要参考zig guide 中相关章节。zig官方文档没有介绍。
    • https://zig.guide/standard-library/formatting-specifiers
    • https://zig.guide/standard-library/advanced-formatting
  • libmpv文档也很少,需要参考
    • https://mpv.io/manual/stable/#command-interface-metadata
    • https://github.com/mpv-player/mpv-examples/blob/master/libmpv/qt/qtexample.cpp

http://www.mrgr.cn/news/43884.html

相关文章:

  • 【梯级水电站调度优化】基于线性递减策略优化粒子群算法
  • 思维导图工具,轻松搞定复杂问题!
  • dockerpull
  • 为什么说函数是Python中的一等公民
  • MySQL查询优化
  • 港股大跌敲响警钟
  • 用Python Turtle绘制天安门技术讲解
  • 一项研究表明,只需一滴干血,新的检测技术或许可以在几分钟内发现癌症
  • 脉冲神经网络(SNN)论文阅读(六)-----ECCV-2024 脉冲驱动的SNN目标检测框架:SpikeYOLO
  • Python - Windows下安装pip
  • 频繁full gc问题排查及解决
  • 用IMX6UL开发板编写按键输入实验
  • MSF捆绑文件
  • 超简单 Flux 换背景工作流:结合最新 ControlNet 深度 Depth 模型
  • 二分搜索算法
  • 跨境电商独立站轮询收款问题
  • VMware桥接模式无法连接网络
  • 一个项目javaweb项目如何debug
  • YOLO11改进 | 卷积模块 | 用Ghost卷积轻量化网络【详细步骤】
  • 蓝桥杯省赛真题打卡day4