電柱日報

日々の由無し事

CrystalでC言語の関数を使う際のポインタ操作

※:この記事は,Crystal Advent Calendar 2018 の7日目です。

最近の高級言語(って今でも言うんですかね?)では,めっきり触ることが少なくなったポインタ操作ですが,Crystal で libc の関数を直接叩いたりバインディングを書いたりすると,ちょくちょくその必要にかられる場面に遭遇します。

Crystalの公式ドキュメントで 安全でない(unsafe)コード のトップに挙げられているように,どうしても必要な時以外は手を出さない方が良いのですが,C言語の関数を使おうと思うとそうも言ってはいられません。

というわけで,Crystalのオブジェクトとそのポインタとの相互変換方法や,ちょっと特殊なポインタ操作についてご紹介します。

ポインタ型

Crystalでは T 型のポインタは Pointer(T) 型と表現します。

この表記もたまに使う分には明確で良いのですが,なんども出てくると一々書くのが億劫です。また,C言語でよく目にするポインタのポインタ,例えば int ** などは,Pointer(Pointer(LibC::Int)) となってさらに面倒臭くなります。

なので,Pointer(T)T* と書けるようになっています。ポインタのポインタも T** とかけてとても楽。

また,あるオブジェクトをポインタに変換する場合には pointerof() を使用します。

p a = 1
#=> 1

p typeof(a)
#=> Int32

ptr_a = pointerof(a)

p typeof(ptr_a)
#=> Pointer(Int32)

逆に,ポインタオブジェクトの #value メソッドを使用すると,そのポインタが指し示すオブジェクトを取得することができます。

# 上のコードの続き

p b = ptr_a.value
#=> 1

p typeof(b)
#=> Int32

この時,変数 ab は同じメモリを参照しています。

上記の例にあるプリミティブな数値型の場合,自身を破壊的に変更することがないためよほど問題になりませんが,Crystalの一般的なオブジェクトで同じような事をすると,オブジェクトの状態が想定外に変化する場合があるため注意が必要です。

c = [0, 1]

ptr_c = pointerof(c)

d = ptr_c.value

c << 2

p d
#=> [0, 1, 2]

あるポインタを別の型のポインタとして扱う

C言語では,まれに(よく?)ある型のポインタとして宣言された変数を別の型のポインタにキャストして使う,といった場面があります。

例えば,LinuxとMac OS XでMACアドレスを取得する方法 で紹介されているMac版サンプルコードの19行目は以下のようになっています。

dl = (struct sockaddr_dl*)ifa->ifa_addr; 

変数 ifa はその上の11行目で,ifaddrs 構造体として宣言されており,その ifa_addr メンバは本来 sockaddr 構造体のポインタであるはずです。しかしここでは,その ifa_addr メンバを sockaddr_dl 構造体のポインタであるものとして変数 dl へ代入しており,以降dlsockaddr_dl 構造体として利用しています。

ifaddrssockaddrsockaddr_dl 構造体をそれぞれ LibC::Ifaddrs 型,LibC::Sockaddr 型,LibC::Sockaddr 型としてバインディングが定義されていた場合に,同様の操作をするには以下のような処理になります。

# ifa : LibC::Ifaddr
# ifa.ifa_addr : LibC::Sockaddr*
dl = ifa.ifa_addr.as(LibC::SockaddrDl*).value
p typeof(dl)
#=> LibC::SockaddrDl

Crystalの情報を得るには(2018年版)

※:この記事は,Crystal Advent Calendar 2018 の6日目です。

2018年末時点で利用可能な,Crystalに関連した情報を取得できるサイトをまとめてみました。

何だかんだで英語で得られる情報の方が充実していますが,1.0に向けて開発も佳境を迎えそうな2019年,Crystalに注目してみてはいかがでしょうか。

注:Crystalは現在も開発が進められており,時折言語仕様に破壊的な変更が加わる場合があります。書かれてから時間が経ったコンテンツは,その内容と現在の実装との間で齟齬が生じている可能性がありますのでご注意ください。

日本語の情報

Introducing Crystal Programming Language

日本のCrystalユーザグループ Crystal-JP が技術書典5で刊行した入門書のWeb版。

Crystalを使ってみたいと思っている方向けの,比較的最近の情報がまとまっています。

Slack

Crystal-JPが運用しているSlack ワークスペース

日本語で質問すると,だれかが答えてくれるかもしれません。

実は海外のCrystal関連の大御所がこっそり(?)参加していたりもします。

過去のAdvent Calendar

実は2015年から行われている Crystal の Advent Calendarの過去ログ。

すでに情報として古くなっている場合もありますのでご注意ください。

Qiita

ここで crystal タグを漁ると,日本語で書かれた関連記事が見つかります。

Crystalのシンタックスハイライトに対応してくれていないのが唯一の難点。

Compass

IT勉強会のスケジューリングや参加登録ができるWebサービス

Crystal勉強会が開催される際は,大抵ここで募集がかかります。

過去の勉強会ページでは,当日の発表資料も公開されています。

英語の情報

公式サイト

Crystal言語の総本山。

ここから,後述する公式ブログ,ドキュメント,APIリファレンス,GitHubリポジトリ,公式フォーラム,その他へ飛ぶことができます。

公式ブログ

公式サイト内のブログ。

更新頻度はあまり高くありませんが,新バージョンのリリースノートやコンパイラの実装に関するコアな解説,Crystalを採用したプロジェクトの紹介記事などが上がっています。

公式ドキュメント

各環境への Crystal コンパイラのインストール手順から,基本的な型や文法の紹介,さらにはマクロやCバインディングといった中級者以上向けの内容までをザックリと網羅。

英語に負けず,まずここを読んでみるのが取っ掛かりには近道かも。

APIリファレンス

Crystalコンパイラに標準添付されている型やメソッドを参照可能。

Crystalでプログラミングを初めてから,なにか困ったらココを見にくるのが吉。

GitHubリポジトリ

Crystalコンパイラの開発拠点。

Crystalコンパイラ自身がCrystal言語で書かれているので,膨大なサンプルコード集としても利用できるありがたい存在です。

前述のAPIリファレンスから,GitHubリポジトリ上のメソッド実装箇所へ飛ぶリンクなどもあり,メソッドの挙動がよくわからない時などはソースを当たると結構解決したりします。

また,盛り上がってるIssueやプルリクなどをチェックしすると,コンパイラ界隈の動向が見えて面白かったりも。

メーリングリスト

Google Groups上で運用されている公式のメーリングリスト

コアチームの面々に直接質問が出せたりします。

次項の公式フォーラムが始まったことで,これまでメーリングリスト上で行われていたコミュニケーション部分の機能は最終的には公式フォーラムへ移行し,こちらは現行コンテンツのアーカイブになる方向性が示されました。

We are launching a discourse forum at https://forum.crystal-lang.org.

We will like to encourage that future conversation happen there and in the future the mailing list will stay as an archive.

[ANN] forum.crystal-lang.org

公式フォーラム

つい最近,今月(2018年12月)になって公開された公式のフォーラム。

メールアドレスでユーザを登録するか,GitHubアカウントと連携させて参加できます。

Twitter

Crystal言語の公式Twitterアカウント。

新バージョンの告知や,Crystal関連の情報がつぶやかれます。

Gitter

Gitter上のCrystalチャンネル。

かなり活発に発言が飛び交っており,公式フォーラムは掲示板,Gitterはチャットといった使い分けになっているようです。

IRCボットも動いていて,irc.freenode.net 上の IRCチャンネルへの発言と相互連携しています。

Stack Overflow

プログラミング関連の質問をすると誰かが答えてくれる互助会的なサイト。

ここでも結構コア開発者から回答をもらえたりします。

Reddit

ニュース記事、画像のリンクやテキストを投稿し、コメントをつけてリスト化できるWebサイト。

Reddit にもCrystal関連の情報がまとまっており,自分の作った shard(Ruby でいう gem のような外部ライブラリ)などをここで宣伝することもできます。

awesome-crystal

一定の条件を満たした自薦他薦の shard がカテゴリ分類してリスト化。

リストの更新も人の手で管理されているため、限定的ではありますが,ある程度の基準をクリアした shard が見つかります。

CrystalShards.xyz

GitHub で公開されている shard のキーワード検索機能を提供。

GitHub で公開されている shard を自動的に収集しているため玉石混交ではあるものの、Awesome Crytstal より網羅的にマイナな shard も見つけることができます。

Crystal Weekly

Crystal 関連のメールニュース。

メールアドレスを登録しておくと,不定期にCrystal関連の情報が配信されます。

Crystal [ANN]

Crystal関連のニュースをまとめたWebサイト。

Crystal関連の新しいプロダクトの情報がちょこちょこ更新されています。

指定したネットワークインタフェースのMACアドレスを取得する

※:この記事は Crystal Advent Calendar 2018 の4日目です。

以前から,crystal-emailcrystal-patliteなど,CrystalでTCPベースのネットワークプログラムをチョコチョコと書いていましたが,ここのところネットワーク熱が再燃しています。

直近だと,ICMP Echoを投げてPingコマンドもどきの動作をするping.crとか。

まぁ,完全に知的好奇心でチャレンジしているので,実用性については深く追求しない方向でお願いします。

で,今回はなんとなくCrystalのプログラム中で,ハードウェアに搭載されたネットワークインタフェースのMACアドレスを取得することに挑戦してみました。

現状Linuxでしか動作確認が取れていませんが(MacOS 10.14だとエラーになる),一応の形になったコードがこちら。

# get_mac.cr
require "socket"

lib LibC
  IF_NAMESIZE   =     16
  IFHWADDRLEN   =      6
  SIOCGIFHWADDR = 0x8927

  # if_req構造体の簡易版
  struct IfReq
    ifr_name : StaticArray(Char, IF_NAMESIZE)
    ifr_addr : Sockaddr
  end

  # ioctl関数の定義
  fun ioctl(fd : Int, request : Int, value_result : Void*) : Int
end

unless ARGV.size == 1
  STDERR << "usage: get_mac IF_NAME\n"
  exit(1)
end

if_name = ARGV.first

unless if_name.size < LibC::IF_NAMESIZE
  STDERR << "Error: IF name too long.\n"
  exit(1)
end

# ioctl関数に渡すソケットを用意
socket = LibC.socket(LibC::AF_INET, LibC::SOCK_DGRAM, 0)

# if_req構造体を生成
ifreq = LibC::IfReq.new

# if_req構造体にアドレスファミリを設定
ifreq.ifr_addr.sa_family = LibC::AF_INET

# if_req構造体に対象のインターフェース名を設定
i = 0
if_name.to_slice.each do |byte|
  ifreq.ifr_name[i] = byte
  i += 1
end
ifreq.ifr_name[i] = 0

# ioctl関数が負の値を返してきたらエラー
if LibC.ioctl(socket, LibC::SIOCGIFHWADDR, pointerof(ifreq).as(Void*)) < 0
  STDERR << "Error: " << Errno.new(Errno.value) << '\n'
  exit(1)
end

# if_req構造体からMACアドレスを取り出して整形
puts mac = ifreq.ifr_addr.sa_data.to_slice[0, LibC::IFHWADDRLEN].map { |b| b.to_s(16).rjust(2, '0') }.join(':')
$ ip a
      :
      :
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:44:5b:f7 brd ff:ff:ff:ff:ff:ff
    inet 172.16.239.146/24 brd 172.16.239.255 scope global noprefixroute dynamic ens33
       valid_lft 1646sec preferred_lft 1646sec
    inet6 fe80::c720:1500:965d:9420/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
$ crystal build get_mac.cr 
$ ./get_mac ens33
00:0c:29:44:5b:f7

やっていることは,LibCにioctl関数関係の定義を追加して,あとは,ガリガリとLibCの機能を叩きまくってる感じです。

Macだとioctl関数がENXIOで落ちるトカ,if_req構造体が簡易版過ぎて他の用途に使えないトカ,アラは色々ありますが,CentOS7環境では一応MACアドレスが取れてきているようです。

低レイヤの機能を触ってみるのも楽しいですね。

時刻と時間

※:この記事はCrystal Advent Calendar 2018 の2日目です。

英語の "time" は 時刻(時の流れのある1点)と時間(2つの時刻の差)の両方の意味を含んでいます。

多くの言語にTimeという名前の型が存在しますが,多くの場合それらが表現しているのは時刻であり,時間は「時刻の差」として表現される場合が多いようです。(少なくとも,私の狭い観測範囲の中においては)

例えば,RubyTimeクラスは時刻を表す型で,独立した時間を表す型は標準では用意されていません。2つのTimeオブジェクトの差をとると,その間の秒数(時間)がFloat型で返されます。

start = Time.now
sleep(5)
finish = Time.now
finish - start
#=> 5.004982

また,Timeオブジェクトに数値(秒数)を加減算することで,ある時刻を基準とした別の時刻を取得することができます。

now = Time.now
#=> 2018-11-30 11:56:54 +0900
now + 3600
#=> 2018-11-30 12:56:54 +0900

本来数値には単位はないため,現在時刻に1を加えた際,それが1秒なのか1時間なのか,はたまた1年なのかは明確ではないのですが,ここでは暗黙的に「時刻に加減算される数値は秒数である」という前提が置かれています。

一方,Crystal では,時刻を表すTime型とは別に,時間を表すTime::Span型が標準で用意されており,コンストラクタを使用する場合は以下のようにして生成することができます。

# 日,時間,分,秒,ナノ秒(省略可)を指定する方式
Time::Span.new(1, 2, 3, 4, 567)
# 1日と2時間3分4秒と567ナノ秒

# 時間,分,秒を指定する方式
Time::Span.new(10, 11, 12)
# 10時間11分12秒

# 名前付き引数で秒,ナノ秒で指定する方式
Time::Span.new(seconds: 128, nanoseconds: 256)
# 128秒と256ナノ秒

# 名前付き引数でナノ秒だけを指定する方式
Time::Span.new(nanoseconds: 4000)
# 4マイクロ秒

Timeオブジェクト同士の差を取ると,Time::Spanオブジェクトが返されますし,

start = Time.now
sleep(5)
finish = Time.now
span = finish - start
#=> 00:00:05.004945000
typeof(span)
#=> Time::Span

Timeオブジェクトに直接数値を加減算することはできず,Time::Spanオブジェクト(もしくは後述するTime::MonthSpanオブジェクト)を使用する必要があります。

p now = Time.now
#=> 2018-11-30 15:10:04.188704000 +09:00 Local

p now + Time::Span.new(1,0,0)
#=> 2018-11-30 16:10:04.188704000 +09:00 Local

p now + 3600
#=> Error: no overload matches 'Time#+' with type Int32

ただ,時間計算の度にいちいちTime::Span.newTime::Spanオブジェクトを生成するのは非常に煩雑ですので,標準の整数型にはTime::Spanオブジェクトを生成するためのインスタンスメソッドが定義されています。

  • 1ナノ秒1.nanosecond
  • 1マイクロ秒:1.microsecond
  • 1ミリ秒:1.millisecond
  • 1秒:1.second
  • 1分:1.minute
  • 1時間:1.hour
  • 1日:1.day
  • 1週間(7日間):1.week

Note: これらのメソッドには複数形(weeksdaysなど)も併せて用意されています。

これらを使用すると,時間操作の可読性を大きく向上させることができます。

# rubyで現在時刻から2週間後の時刻を取得する
Time.now + 3600 * 24 * 14

# crystalで現在時刻から2週間後の時刻を取得する
Time.now + 2.weeks

また,Time::Spanには,自身を各単位(ナノ秒,マイクロ秒,ミリ秒,秒,分,時,日)に換算した数値(Float64型)を返すインスタンスメソッド(total_******に複数形の単位名)が用意されています。ですので,「1週間って何秒だっけ?」という時(そんな状況が実際にあるかどうかは別として)にも以下のようにして求めることができます。

p 1.week.total_seconds
#=> 604800.0

月単位の時間操作

1秒は109ナノ秒で1時間は3600秒,1日は24時間で1週間は7日と,週までの時間単位は(閏秒などの例外をのぞいて)いつを起点としても同じ量になります。しかし,月や年といったそれ以上の時間単位になると,そうもいきません。

「ある時刻の1ヶ月後」を取得したい場合には,その月が何日あるのかを考える必要がでてきます。日付の月要素を取り出して1を加える,という大雑把な方法もありますが,年末を挟むと年も修正する必要があったり,8月31日だと翌月(9月)には31日が存在しなかったりと,こちらも細々とした調整が必要になります。

真面目にやろうとすると意外と煩雑な月単位の時間操作のために,Crystal の標準ライブラリにはTime::Span型とは別に月間を表すTime::MonthSpan型が用意されています。

Time::Span型と同様,Time::MonthSpan型にも整数型に生成用のインスタンスメソッドが用意されていますので,自然な形で月や年単位の時間操作を記述することができます。

  • 1ヶ月間:1.month
  • 1年間:1.year
p Time.new(2018,12,1) + 1.month
#=> 2019-01-01 00:00:00.0 +09:00 Local

この時,例えば「1月30日の1ヶ月後」のように,計算後の月には存在しない日を基準として計算した場合,計算後の日付はその月の末日になります。

p Time.new(2018,10,31) + 1.month
#=> 2018-11-30 00:00:00.0 +09:00 Local

ちゃんと閏年も考慮されており,2019年1月29日の1ヶ月後は2019年2月28日が返されますが,2020年(閏年)の1月29日の1ヶ月後は2020年2月29日が返ってきます。

p Time.new(2019,1,29) + 1.month
#=> 2019-02-28 00:00:00.0 +09:00 Local

p Time.new(2020,1,29) + 1.month
#=> 2020-02-29 00:00:00.0 +09:00 Local

iPadでCrystalコーディング環境

出張の移動中にちょっとしたコーディングがしたくて試行錯誤した内容を備忘録として。

やりたかったこと

導入アプリ1)Textastic

Textastic Code Editor 6

Textastic Code Editor 6

  • Alexander Blach
  • 仕事効率化
  • ¥1,200

iPadで使えるコーディングエディタ。

シンタックスハイライトができて,iCloudドライブやDropboxとも連携できて便利。

当然(?)Crystal言語のハイライトには対応していない……が,Textmateシンタックス定義がそのまま使えるので,GitHubからCrystal.tmbundleを拾ってきて突っ込む。

github.com

突っ込み方は公式で公開されてるiPad版マニュアルのpp. 14-19辺りを参照。

#Textastic フォルダにどうやって.tmbundle 一式を送り込んだら良いのか悩んだものの,GitHubからcloneしたリポジトリ一式を一旦Dropboxにあげて,Textasticのファイル転送機能でフォルダごと持ってきたらできた。

マクロなんかも結構ちゃんと色分けしてくれてニッコリ。

f:id:denchuinc:20180625181942p:plain

シンボルリストからクラス定義やメソッド定義にジャンプしたりもできる。 (注:マクロメソッドの表示が微妙だったり,structがシンボルとして拾われないとかの不具合はある)

f:id:denchuinc:20180625182110p:plain

ただし,Textastic単体ではGitHubからcloneしたりpushしたりはできない。

導入アプリ2) Working Copy

Working Copy

Working Copy

  • Anders Borum
  • 仕事効率化
  • 無料

iPadで使えるGitクライアント。GitHubからローカルへのcloneは無料版でもできる。リモートへのpushは有料機能。

GitHubで認証すると,このアプリ用の公開鍵をGitHubアカウントへ埋め込む。(この挙動自体はどうかと思わないでもないけれど,面白い仕組みではある)

こいつ自身にも簡易のエディタは搭載されているものの,シンタックス定義の追加とかはできない。むしろ,他のテキストエディタから開けるから,エディタは好きなの使って,みたいなスタンスっぽい。

公式サイトの一番上にある動画で17秒あたりから,まさにTextasticでリポジトリを開く方法が紹介されてる。

ローカルにcloneするので,電波が途切れても編集や保存ができる。

その他

コーディング以外でも,iPad標準のVPN機能(L2TP/IPsec/)と

Prompt 2

Prompt 2

  • Panic, Inc.
  • ユーティリティ
  • ¥1,800

辺りのリモートアクセス系アプリを使えば,出先からiPadで最低限の緊急対応はできそうな気配。

今度の出張はMacBook Proを置いて,iPadだけで行ってみようかな。

不快指数……

ここ数日は暖かいを通り越して暑いと感じる日が続いていますね。先週などは20℃前後で肌寒かったことを思うと,温度差にやられそうです。

職場のクーラー解禁は6月以降,30℃を超えるような日はつけても良いようですが,流石にまだそこまで気温は上がらず。

とはいえ今日は湿度も高く,不快指数が怒髪天な感じで辛うございました。

正直がっつり暑くて汗をダラダラ流すような状況よりも,ジットリと汗ばむ感じのほうがキツく感じます。

いまからこの様子だと梅雨本番が思いやられますね。

シングルタスク脳

基本がシングルタスクな脳ミソのため,並行する案件の対処が苦手。

なんとかTSSでこなそうとはしてますが,キューにタスクが溜まってくるととどうしても取りこぼしが起き始めます。

しかも大抵の場合,作業効率はそれまでに連続してそのタスクを処理してる時間に比例して向上するので,タスクのスイッチングが入るとアクセルの踏みなおしになって効率がよろしくない。

そうでなくてもノッてきた頃に割り込み(電話とか)が入るし。

割り込みを無視して1件ずつ潰していった方がトータルとしての効率は上がりそうな気もしますが,まさか自分むけの案件トラッカーでチケット発行,ってわけにも行きませんわな。

仕事に対するスタンスを根本的に見直す時期なのかも……とか言いながら,どっちの方向に行けばいいのかまったく見えてなかったりするんですけど。