/+ 旧暦ですら1月後半になってますが、D言語くんAdCが蒸発し、
D言語のAdCもスカスカで悲しいのでカレンダーに追加しました +/
3年前にImportCの記事を書きましたが、
皆様のおかげで「ImportC」で調べると公式の解説に続いて2番目くらいに表示されているようです。応援ありがとうございます。
さて使っているメインPCをX11からWaylandに仮移行して使い勝手を試しているのですが、
使用しているコンポジターがwl_shellに対応しておらず、
xdg_shellを使わないと動かなくなっていたので改めてImportCを試してみました。
3年でImportCがかなり強化されているようです。
- 執筆時の D version は v2.109.1
- OSはArch Linux
- Waylandコンポジターはlabwc
まず前回なかったWayland特有の手順として、
xdg_shell等の拡張プロトコルをxmlからc/hに変換する必要があります。(参考)
詳細は省きますが、提供されたヘッダーをincludeするだけではだめで、
wayland-scannerコマンドを使用してヘッダーとCソースを生成する必要があるようです。
$ wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-client-protocol.h
$ wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-protocol.c
続いて使用するCのライブラリをincludeするだけのCソースを用意します。
#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"
#include "xdg-shell-protocol.c" // お行儀は良くないかもしれない
#include <unistd.h>
#include <sys/mman.h>
そしてそれをDソースの中でimportします
import wl2c;
コンパイルはDソースとinclude用のCソースを指定します。
$ dmd -L-lwayland-client wl2.d wl2c.c && ./wl2
はい、もはやプリプロセッサを事前に通す必要がなくなっています。
基本的にbindingを用意する必要がなくなって使い勝手が劇的に向上しています。
前回同様、import core.sys.linux.unistd;
とも書けますが、
#include <unistd.h>
とも書けるようになったため
システムに搭載されたライブラリがC言語に準拠したヘッダーを持っていたり、
最新のLinuxカーネルの機能をその場で使いたいといった場合に
bindingを自前で用意or定義が不足していてガッカリしなくても良くなりました。
どうあがいても言語人口の少なさからアクセス可能なライブラリ資産の観点で
D言語は不利な状況(もちろんD言語特化の高度なライブラリもあれど)でしたが
C言語の豊富な資産を非常に容易に利用可能となっていて革命的に便利になったと思います。
ソース本体
// dmd -L-lwayland-client wl2.d wl2c.c && ./wl2
import std;
// wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-client-protocol.h
// wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-protocol.c
// cat << EOF > wl2c.c
// #include <wayland-client.h>
// #include "xdg-shell-client-protocol.h"
// #include "xdg-shell-protocol.c"
// #include <unistd.h>
// #include <sys/mman.h>
// EOF
import wl2c;
auto myassert(T)(T eval){ writeln(eval); assert(eval); return eval; }
__gshared wl_compositor* g_compositor;
__gshared xdg_wm_base* g_shell;
__gshared wl_shm* g_shm;
extern(C){
void reg_global(void* data, wl_registry* reg, uint id, const(char)* iface, uint ver){
auto iface_ = iface.fromStringz;
printf("%p %p %d %s %d\n", data, reg, id, iface, ver);
switch(iface_){
case "wl_compositor":
g_compositor = cast(wl_compositor*)wl_registry_bind(reg, id, &wl_compositor_interface, 1).myassert;
break;
case "xdg_wm_base":
g_shell = cast(xdg_wm_base*)wl_registry_bind(reg, id, &xdg_wm_base_interface, 1).myassert;
xdg_wm_base_add_listener(g_shell, cast(xdg_wm_base_listener*)[
(void* _, xdg_wm_base* p, uint s){
printf("!xdg_wm_base_pong [%p] [%p] [%d]\n", _, p, s);
xdg_wm_base_pong(p, s);
}.bindDelegate
], null);
break;
case "wl_shm":
g_shm = cast(wl_shm*)wl_registry_bind(reg, id, &wl_shm_interface, 1).myassert;
break;
default:
}
}
int memfd_create(const(char)*, uint);
}
auto bindDelegate(T)(T t) {
static T dg;
dg = t;
extern(C) static ReturnType!T func(ParameterTypeTuple!T args) {
return dg(args);
}
return &func;
}
void main(){
auto dpy = wl_display_connect(null).myassert;
auto reg = wl_display_get_registry(dpy).myassert;
wl_registry_add_listener(reg, cast(wl_registry_listener*)[cast(void*)®_global, null], null);
wl_display_roundtrip(dpy);
auto srf = wl_compositor_create_surface(g_compositor).myassert;
auto shsrf = xdg_wm_base_get_xdg_surface(g_shell, srf).myassert;
xdg_surface_add_listener(shsrf, cast(xdg_surface_listener*)([
(void* _, xdg_surface* p, uint s){
printf("!xdg_surface_ack_configure [%p] [%p] [%d]\n", _, p, s);
xdg_surface_ack_configure(p, s);
}.bindDelegate
]), null);
auto tlshsrf = xdg_surface_get_toplevel(shsrf).myassert;
xdg_toplevel_add_listener(tlshsrf, cast(xdg_toplevel_listener*)[
(){}.bindDelegate,
(){}.bindDelegate,
], null);
auto shmfd = memfd_create("".ptr, 0).myassert;
auto shmsize = 128*128*4;
ftruncate(shmfd, shmsize);
void* shmptr = mmap(null, shmsize, PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0).myassert;
shmptr[0..128*128*4] = iota(128*128*4).map!(a=>uniform!(ubyte)).array;
auto shmpool = wl_shm_create_pool(g_shm, shmfd, shmsize);
auto shmbuf = wl_shm_pool_create_buffer(shmpool, 0, 128, 128, 128*4, WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(shmpool);
wl_surface_commit(srf);
wl_display_dispatch(dpy).myassert;
wl_surface_attach(srf, shmbuf, 0, 0);
wl_surface_commit(srf);
while(wl_display_dispatch(dpy) != -1){}
}
なるべく前回の記事のコードに寄せてるため変数名が変だったりしてますのでご注意。
その他
extern(C)配下に書けばいいのですが、少し工夫をするとCの関数にDのDelegateを渡すことができるようです。
bindDelegateまわりがその処理になります。(参考)
listenerの処理内容を離れた位置に書くよりは見通しが良くなる気がします。
(が、変な副作用がでるのだろうか)
本編ここまで、以下失敗例
前回SDLで失敗したため、今回もやってみましたがまだそのままでは無理なようでした。
上で「基本的に」と書きましたが、Cで書かれていても、
GNU拡張等の非標準な書き方がなされているとそのままでは使えないようです。
(いつの間にかSDLもSDL3にメジャーバージョンアップしたようですね)
$ cat sdl2.d
import std;
import sdl2c;
void main(){
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
}
$ cat sdl2c.c
#include <SDL3/SDL.h>
$ dmd sdl2.d sdl2c.c
/usr/include/SDL3/SDL_stdinc.h(6047): Error: undefined identifier `__builtin_mul_overflow`
/usr/include/SDL3/SDL_stdinc.h(6085): Error: undefined identifier `__builtin_add_overflow`
/usr/include/SDL3/SDL_bits.h(75): Error: undefined identifier `__builtin_clz`
まとめ
なのでD言語もっと流行れ。