2014/07/03


先日、C言語向けのパッケージマネージャ clib を紹介したのですが、その後 Windows で使えそうなパッケージを3つ程書きました。
Big Sky :: clib の使い勝手にマジ感動した

C言語でアプリケーションを書くのは他の言語と比べて少し気合が必要ですよね。例えば HTTPからデータを取得する 取得したデータを json パースする 結果の一部を色付きで表示する こんな場合、C言語...

http://mattn.kaoriya.net/software/lang/c/20140627222830.htm

locale-string.c

最近のライブラリは文字列が utf-8 前提で書かれている物が多く、Windows だと「それ、ツライわー(汗)」だったりするのですが、かと言ってこれだけの為に変換ライブラリを入れるのはアレですし、iconv 入れちゃったら GPL(LGPL) だし仕事で使えねー、みたいな事が起きてしまいます。
でもロケール文字列と utf-8 との変換なら mbstowcs や wcstombs があれば出来るし、Windows なら MultiByteToWideChar や WideCharToMultiByte があれば出来るので、それだけのライブラリを書きました。
mattn/locale-string.c - GitHub
https://github.com/mattn/locale-string.c
使い方は簡単。
setlocale(LC_CTYPE"");
const char* ptr = "こんにちわ世界";
char* mbs = utf8_to_locale_alloc(ptr);
assert(NULL != mbs);
char* utf8 = utf8_from_locale_alloc(mbs);
assert(NULL != utf8);
assert(0 == strcmp(ptr, utf8));
使い終わったら free が必要。utf8_to_locale_alloc で utf-8 文字列をロケール文字列へ、utf8_from_locale_alloc でロケール文字列から utf-8 へ変換します。
Linux や Mac だと必要ない機能なのでマクロを用意してあります。
#ifdef _WIN32
# define UTF8_ALLOC(p) utf8_from_locale_alloc(p)
# define UTF8_FREE(p) free(p)
# define LOCALE_ALLOC(p) utf8_to_locale_alloc(p)
# define LOCALE_FREE(p) free(p)
#else
# define UTF8_ALLOC(p) (p)
# define UTF8_FREE(p)
# define LOCALE_ALLOC(p) (p)
# define LOCALE_FREE(p)
#endif
Windows 以外ならばメモリ確保も解放も行いませんので、UTF8_ALLOC して使い終わったら UTF8_FREE しておくと UNIX でも Windows でも同じ様に動作する、という仕組みです。

wcwidth.c

皆さんしってる wcwidth です。
wcwidth, wcswidth, wcwidth_cjk, wcswidth_cjk がそろっていますが、オマケで utf-8 文字列の幅を求める string_width と string_width_cjk も付いてきます。
mattn/wcwidth.c - GitHub
https://github.com/mattn/wcwidth.c
assert(14 == string_width("こんにちわ世界"));
assert(20 == string_width("こ★ん■に●ち▲わ☆世◆界"));
assert(26 == string_width_cjk("こ★ん■に●ち▲わ☆世◆界"));
こんな感じに使います。

ansicolor-w32

最近の Linux のアプリケーションでコンソールに色付きで出力される物も最近では珍しくなくなってきました。
ただ Windows だと API が超メンドクサイので Constellation さんの console-colors.c を使うのが良いのですが、UNIX 系ツールを移植していてなるべくオリジナルコードを触りたくない場合にはツライんです。
mattn/ansicolor-w32.c - GitHub
https://github.com/mattn/ansicolor-w32.c
そこで ansicolor-w32.c というのを書きました。ansicolor-w32.h を include すると、エスケープシーケンスを解析して Windows でも色付けで表示してくれます。
ansicon というツールも存在しますが、このライブラリはそれ無しでも色付け可能です。

fprintf, fputs, printf, puts だけ対応しています。

#include <stdio.h>
#ifdef _WIN32
# include "ansicolor-w32.h"
#endif

int
main(int argc, char* argv[]) {
  printf("\x1b[2J\x1b[10,10H\x1b[24m\x1b[30m");
  printf("\x1b[42m博\x1b[43m多\x1b[46mの\x1b[45m塩\x1b[0m"); 
  return 0;
}
例えばこんなコードを書いて実行すると
博多の塩
この様に表示されます。

よろしければお使い下さい。MIT ライセンスです。

2014/06/30


夏だし、怖い話しようぜ!
#include <cstdio>
#include <math.h>
 
class C {
public:
  C() {}
  C(double) { printf("hello world\n"); }
};
 
int main(int argc, char** argv)
{
  C(NAN);
  return 0;
}
僕は今日... こんなコードをコンパイルしたんだ...。gcc でコンパイルして実行したんだ...。

hello world
期待通りだった。
僕は安心し、これを今度は MSVC でビルドしたんだ...。

実行すると...

何も出ない...

僕は怖くなった。

何なんだ!何なんだ!NaN なんだーーー!

僕の C++ 人生はなんだったのか...。僕は病に侵されているに違いない...。

怖いながらもデバッガで追ったんだ...

C++ の怖い話
そしてステップインした...
C++ の怖い話
C++ の怖い話
えっ...

尋常ではない程の汗が僕の頬を伝った。

まっ...まさか!

そう、MSVC には NAN が宣言されていない。
これのおかげで2時間程無駄にした。
Fix test. MSVC doesn't have definition of NAN by mattn - Pull Request #43 - kazuho/picojson - GitHub

Note: https://gist.github.com/mattn/bf1e56aa832c031e5df6 illustrates the problem. MSVC somehow succe...

https://github.com/kazuho/picojson/pull/43
NAN が宣言されていない為に、NAN という変数が宣言されてしまっていた。
NAN なんだ、まったく。
酷い一日だった。
なんて日だ!

2014/06/27


C言語でアプリケーションを書くのは他の言語と比べて少し気合が必要ですよね。例えば
  • HTTPからデータを取得する
  • 取得したデータを json パースする
  • 結果の一部を色付きで表示する
こんな場合、C言語プログラマは

「HTTP か、じゃぁcurlかな」
「JSON か、parson かな」
「色表示か...エスケープシーケンスでもいいけどWindowsがなー...」

といった事を考え、そこから curl や parson といった資材の調達を始める事になります。途中で新しい機能を追加したくなり、それを外部ライブラリに頼る場合だとその都度資材を調達する必要があり、思考を停止しなければなりません。
この辺は ruby や perl、nodejs、golang 等の様に、ちょっとした手間だけで済ませたい物です。
またC言語の場合、ヘッダファイルはシステムの include フォルダに提供元が期待する通りに配置するか、手元の Makefile でパスを通す必要があり、ライブラリもバージョンに従ったファイル名である必要があります。
こういうチマチマとした作業も楽しかったりはするのですが、今すぐ作りたいって時には腰が重い作業となります。include 配下を整理し出したらどうも気になり始めて、気付けば全く関係の無い事をやっていて作りたかった物が何も出来ていなかった、なんて事もありますよね。

以前 github で clib というプロジェクトを見つけました。
clibs/clib - GitHub

Package manager for the C programming language.

https://github.com/clibs/clib
言うなれば、C言語版の bundler であったり、carton であったり、npm だったりする訳です。

今日は、この clib を使うとどの様に開発が進められるかを手順と共に説明します。

package.json を用意する

上記の様な仕様を満たすライブラリは幾つかあります。また github 上には clib から扱えるライブラリが多く存在ます。そしてそのリポジトリには package.json というファイルが含まれています。
今回の仕様を満たす為に、僕は以下の package.json を作りました。
{
  "name""helloworld",
  "version""1.0.0",
  "repo""mattn/helloworld",
  "dependencies": {
    "stephenmathieson/http-get.c""0.1.0",
    "kgabis/parson""*",
    "Constellation/console-colors.c""1.0.1"
  },
  "install""make install"
}
追記: parson-repo が無くなってたので修正

これをリポジトリ直下に置いて以下のコマンドを叩きます。

clib install

clib install
deps フォルダ配下にパッケージがインストールされました。
├── deps
│   ├── console-colors
│   │   ├── console-colors.c
│   │   ├── console-colors.h
│   │   └── package.json
│   ├── http-get
│   │   ├── http-get.c
│   │   ├── http-get.h
│   │   └── package.json
│   └── parson
│       ├── package.json
│       ├── parson.c
│       └── parson.h
└── package.json
簡単すぎる!!!まぁなんと便利なんでしょう!
ちなみにこの clib install は、使用するパッケージが依存する別のパッケージも合わせて持ってきてくれる為、依存物地獄に陥る心配もありません。

Makefile を用意する

まず src ディレクトリを作り、リポジトリ直下には以下の Makefile を置きます。
CC     ?= cc
PREFIX ?= /usr/local

ifeq ($(OS),Windows_NT)
BINS    = helloworld.exe
LDFLAGS = -lcurldll
CP      = copy /Y
RM      = del /Q /S
MKDIR_P = mkdir
else
BINS    = helloworld
LDFLAGS = -lcurl
CP      = cp -f
RM      = rm -f
MKDIR_P = mkdir -p
endif

SRC  $(wildcard src/*.c)
DEPS $(wildcard deps/*/*.c)
OBJS $(DEPS:.c=.o)

CFLAGS  = -std=c99 -Ideps -Wall -Wno-unused-function -U__STRICT_ANSI__

all: $(BINS)

$(BINS): $(SRC) $(OBJS) $(RES)
    $(CC) $(CFLAGS) -o $@ src/$(@:.exe=).c $(OBJS) $(RES) $(LDFLAGS)

%.o: %.c
    $(CC) $< -c -o $@ $(CFLAGS)

clean:
    $(foreach c, $(BINS), $(RM) $(c);)
    $(RM) $(OBJS)

install: $(BINS)
    $(MKDIR_P) $(PREFIX)/bin
    $(foreach c, $(BINS), $(CP) $(c) $(PREFIX)/bin/$(c);)

uninstall:
    $(foreach c, $(BINS), $(RM) $(PREFIX)/bin/$(c);)

test:
    @./test.sh

.PHONY: test all clean install uninstall
この Makefile を使うと、deps ディレクトリ配下にうまくパスを通してくれる為、以後 make コマンド一発で依存物がビルド出来る様になっています。

コードを書こう

コードは、江添さんのブログ「本の虫」の Blogger JSON Feed をパースし、タイトルと URL を一覧表示します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "console-colors/console-colors.h"
#include "parson/parson.h"
#include "http-get/http-get.h"

int
main(int argc, char* argv[]) {
  http_get_response_t* res = http_get("http://cpplover.blogspot.com/feeds/posts/default?alt=json");
  if (res->status != 200) {
    cc_fprintf(CC_FG_RED, stderr"ERROR");
    goto leave;
  }

  char* json = calloc(res->size + 11);
  if (!json) goto leave;
  strncpy(json, res->data, res->size);

  JSON_Value *root_value = json_parse_string(json);
  JSON_Object *root = json_value_get_object(root_value);
  JSON_Array *entries = json_object_dotget_array(root, "feed.entry");
  for (int i = 0; i < json_array_get_count(entries); i++) {
    JSON_Object *entry = json_array_get_object(entries, i);
    cc_fprintf(CC_FG_BLUE, stdout"%s\n",
      json_object_dotget_string(entry, "title.$t"));
    JSON_Array *links = json_object_get_array(entry, "link");
    for (int l = 0; l < json_array_get_count(links); l++) {
      JSON_Object *link = json_array_get_object(links, l);
      if (!strcmp("alternate", json_object_get_string(link, "rel"))) {
        cc_fprintf(CC_FG_YELLOW, stdout"%s\n",
          json_object_dotget_string(link, "href"));
        break;
      }
    }
  }
  json_value_free(root_value);

leave:
  if (res) http_get_free(res);
  if (json) free(json);
  return 0;
}
ちょっと適当なのでバグあるかもしれませんがご愛嬌で。
実行結果は以下の通り。
本の虫
簡単すぎる!!!
僕の中ではちょっとしたライフチェンジングですね。
このスピード感なら色んな物が作れそうな気がしますね。実際このコードも clib install を含めて10数分程度で書けました。
皆さんも一度、このスピード感を味わってみて下さい。