🏣

C言語のソースコードをZigに変換する機能を試す(その2)

2022/07/21に公開

この続き。
https://zenn.dev/tetsu_koba/articles/95ce0deb9a6704
今回は実際にgolangのプログラムからCGOで使っていたCの部分を題材にしてみた。

webcamの設定値を読み書きする

webcam_ctrl.h
#ifndef _WEBCAM_CTRL_H_
#define _WEBCAM_CTRL_H_

#include <linux/v4l2-controls.h>

int get_control(int fd, int id, int *val);
int set_control(int fd, int id, int val);

#endif /* _WEBCAM_CTRL_H_ */
webcam_ctrl.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/v4l2-controls.h>
#include <linux/videodev2.h>
#include <stdlib.h>
#include "webcam_ctrl.h"

int set_control(int fd, int cid, int val)
{
	struct v4l2_control v4l2_ctrl = {
		.id	= cid,
		.value	= val
	};
	if(ioctl(fd, VIDIOC_S_CTRL, &v4l2_ctrl)) {
		return -1;
	}
	return 0;
}

int get_control(int fd, int cid, int* val)
{
	struct v4l2_control v4l2_ctrl = { .id = cid };
	if(ioctl(fd, VIDIOC_G_CTRL, &v4l2_ctrl)) {
		return -1;
	}
	*val = v4l2_ctrl.value;
	return 0;
}

#ifndef CGO
int main(int argc, char** argv)
{
	char *fname;
	int fd;
	int cid;
	int ret;
	int val;
	int val0;

	if (argc < 2) {
		fprintf(stderr, "usage: %s val\n", argv[0]);
		return 1;
        }
	val = atoi(argv[1]);
	
	fname = "/dev/video0";
	fd = open(fname, O_RDWR);	
	if (fd < 0) {
		fprintf(stderr, "failed to open: %s\n", fname);
	}
	ret = get_control(fd, V4L2_CID_BRIGHTNESS, &val0);
	if (ret != 0) {
		fprintf(stderr, "failed to get_control: ret=%d\n", ret);
	}
	printf("Brightness=%d\n", val0);
	
	ret = set_control(fd, V4L2_CID_BRIGHTNESS, val0 + val);
	return ret;
}
#endif

zig ccでビルドできる。

$ zig cc webcam_ctrl.c
$ file a.out
a.out: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, stripped

CのソースコードをZigに変換する

test用のmainは除いてzigに変換する。

$ zig translate-c -DCGO \
-I /usr/local/zig-linux-aarch64-0.9.1/lib/libc/include/aarch64-linux-gnu \
-I /usr/local/zig-linux-aarch64-0.9.1/lib/libc/include/generic-glibc \
-I /usr/local/zig-linux-aarch64-0.9.1/lib/libc/include/any-linux-any \
webcam_ctrl.c > webcam_ctrl.zig
$ wc webcam_ctrl.zig
  5303  31035 300798 webcam_ctrl.zig

できた。5000行ある。

共有ライブラリ作ってみた。

$ zig build-lib -dynamic -lc webcam_ctrl.zig
$ ldd libwebcam_ctrl.so 
	linux-vdso.so.1 (0x0000ffffa2f28000)
	libpthread.so.0 => /lib/aarch64-linux-gnu/libpthread.so.0 (0x0000ffffa2e46000)
	libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffffa2cd3000)
	/lib/ld-linux-aarch64.so.1 (0x0000ffffa2ef8000)

Zigでライブラリビルド環境を整える

$ zig init-lib
info: Created build.zig
info: Created src/main.zig
info: Next, try `zig build --help` or `zig build test`
$ mv webcam_ctrl.zig src/

build.zigを編集。

build.zig
const std = @import("std");

pub fn build(b: *std.build.Builder) void {
    // Standard release options allow the person running `zig build` to select
    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
    const mode = b.standardReleaseOptions();

    const lib = b.addSharedLibrary("webcam_ctrl", "src/webcam_ctrl.zig", b.version(0,1,0));
    lib.linkLibC();
    lib.setBuildMode(mode);
    lib.install();

    const exe = b.addExecutable("test", null);
    exe.addCSourceFile("test.c", &[_][]const u8{});
    
    exe.linkLibrary(lib);
    exe.linkSystemLibrary("c");

    b.default_step.dependOn(&exe.step);

    const run_cmd = exe.run();

    const test_step = b.step("test", "Test the program");
    test_step.dependOn(&run_cmd.step);    
}

ライブラリをCでテストするコード。

test.c
#include <stdio.h>
#include <fcntl.h>
#include <linux/v4l2-controls.h>
#include "webcam_ctrl.h"

int main(int argc, char** argv)
{
	char *fname;
	int fd;
	int ret;
	int val;
	int val_org;

	fname = "/dev/video0";
	fd = open(fname, O_RDWR);	
	if (fd < 0) {
		fprintf(stderr, "failed to open: %s\n", fname);
	}
	ret = get_control(fd, V4L2_CID_BRIGHTNESS, &val);
	if (ret != 0) {
		fprintf(stderr, "failed to get_control: ret=%d\n", ret);
	}
	printf("Brightness=%d\n", val);
	val_org = val;
	
	ret = set_control(fd, V4L2_CID_BRIGHTNESS, val + 10);
	printf("set Brightness=%d\n", val + 10);

	ret = get_control(fd, V4L2_CID_BRIGHTNESS, &val);
	if (ret != 0) {
		fprintf(stderr, "failed to get_control: ret=%d\n", ret);
	}
	printf("Brightness=%d\n", val);
	
	ret = set_control(fd, V4L2_CID_BRIGHTNESS, val_org);
	printf("set Brightness=%d\n", val_org);
	
	ret = get_control(fd, V4L2_CID_BRIGHTNESS, &val);
	if (ret != 0) {
		fprintf(stderr, "failed to get_control: ret=%d\n", ret);
	}
	printf("Brightness=%d\n", val);
	return ret;
}
$ zig build
$ ls -l zig-out/lib/libwebcam_ctrl.so*
lrwxrwxrwx 1 koba koba     19  7月 20 17:57 zig-out/lib/libwebcam_ctrl.so -> libwebcam_ctrl.so.0
lrwxrwxrwx 1 koba koba     23  7月 20 17:57 zig-out/lib/libwebcam_ctrl.so.0 -> libwebcam_ctrl.so.0.1.0
-rwxrwxr-x 1 koba koba 566864  7月 20 17:20 zig-out/lib/libwebcam_ctrl.so.0.1.0

ライブラリをビルドできた。

$ zig build test
Brightness=20
set Brightness=30
Brightness=30
set Brightness=20
Brightness=20

想定通りの結果。

自動生成したzigのソースコードを手で修正

自動生成されたものは5000行ちかくあったが、@cIncludeをすることで大幅に削減できた。
さらにいくつか修正して、すっきりさせたものがこちら。

webcam_ctrl.zig
const c = @cImport({
    @cInclude("sys/ioctl.h");
    @cInclude("linux/v4l2-controls.h");
    @cInclude("linux/videodev2.h");
});

pub export fn get_control(fd: c_int, cid: c_int, val: [*c]c_int) c_int {
    var v4l2_ctrl: c.struct_v4l2_control = .{
        .id = @bitCast(c.__u32, cid),
        .value = 0,
    };
    if (c.ioctl(fd, c.VIDIOC_G_CTRL, &v4l2_ctrl) != 0) {
        return -@as(c_int, 1);
    }
    val.* = v4l2_ctrl.value;
    return 0;
}
pub export fn set_control(fd: c_int, cid: c_int, val: c_int) c_int {
    var v4l2_ctrl: c.struct_v4l2_control = .{
        .id = @bitCast(c.__u32, cid),
        .value = val,
    };
    if (c.ioctl(fd, c.VIDIOC_S_CTRL, &v4l2_ctrl) != 0) {
        return -@as(c_int, 1);
    }
    return 0;
}

C言語で書かれた共有ライブラリをそっくりそのままZig言語に置き換えることができた。

Discussion