2014/09/18


PDCurses はその名の通り Public Domain な curses 実装な訳ですが、Windows の実装こそありますが如何ともし難い問題がありました。

PDCurses には、ワイド文字列関数とバイト列で貰う実装があります。UNIX 系アプリケーションの多くは内部文字列がほぼ UTF-8 となり、C言語で受け渡される文字列は既成事実として UTF-8 になってしまいました。

しかしながら日本の Windows ではバイト列と言えば Shift_JIS な訳で、そのまま移植してしまうと文字化けが発生します。各アプリケーション側がワイド文字列でコードを書いてくれれば良いのですが、そんな事を気にする人はまずいません。ましてや各アプリケーションが UTF-8 と MBCS (Multi Byte Character Set) の変換を行うのは酷な話です。出来れば内部文字列は UTF-8 のままで Windows でも正しく表示されて欲しいのです。

mattn/pdcurses - GitHub
https://github.com/mattn/pdcurses

という訳で pdcurses に手を入れてみました。もし pdcurses.dll をリンクしていて表示が文字化けしているアプリケーションがあれば、dll を入れ替えるだけで文字化けが解消するかと思います。

今日はこれを使って、コマンドラインベースのプレゼンテーションツールである mdp を Windows で動かしてみました。

visit1985/mdp · GitHub

A command-line based markdown presentation tool.

https://github.com/visit1985/mdp

mdp 自身は大した事をやってないので、ncurses の代わりに pdcurses をリンクする様な修正を入れただけです。

diff --git a/Makefile b/Makefile
index 63a1feb..54f8327 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@
 
 CFLAGS   = -O3 -Wall
 LDFLAGS  = -s
-LDLIBS   = -lncurses
+LDLIBS   = -lpdcurses
 OBJECTS  = cstring.o cstack.o markdown.o parser.o viewer.o mdp.o
 DESTDIR ?= /usr/bin
 
diff --git a/include/viewer.h b/include/viewer.h
index 68c7a13..f6e70fb 100644
--- a/include/viewer.h
+++ b/include/viewer.h
@@ -32,7 +32,7 @@
  *
  */
 
-#include <ncurses.h>
+#include <curses.h>
 
 #include "parser.h"
 #include "cstack.h"
diff --git a/viewer.c b/viewer.c
index ba76f2f..ed83196 100644
--- a/viewer.c
+++ b/viewer.c
@@ -22,7 +22,6 @@
  */
 
 #include <locale.h> // setlocale
-#include <ncurses.h>
 #include <stdlib.h>
 #include <string.h> // strchr
 #include <unistd.h>

sample.md も日本語にして正しく動作する事が確認出来ました。

mdp1
mdp2
mdp3
mdp4
Posted at by



2014/09/04


コマンドラインから「あ*」等とワイルドカードを使用してコマンドを起動した場合、UNIX とは異なり Windows ではプロセス自身が引数を展開します(内部的にではありますが)。

main 関数の argc/argv は MSVC コンパイラであれば setargv.obj というオブジェクトファイルをリンクする事で展開される様になりますが、MinGW ではデフォルトでワイルドカード展開が実行されてしまいます。しかしながら、このワイルドカード展開は各コンパイラベンダーの実装次第という所があり、時に異なる挙動となる場合があります。

そこでワイルドカードは自前でプログラムで展開するのが一番良い訳ですが、MinGW の場合は既述の通り、デフォルトが展開動作となっています。しかし以下のたった1行のおまじないを入れる事で、引数展開が無効となります。

#include <stdio.h>

#define WIN32_LEAN_AND_MEAN  1
#include <windows.h>

int _CRT_glob = 0// おまじない

int
main(int argc, char **argv) {
  int i;
  printf("command line: %s\n", GetCommandLine());
  for (i = 0; i < argc; i++)
    printf( "argv[%d]: %s\n", i, argv[i]);
  return 0;
}

おまじないの行をコメントアウトした場合

C:\dev>a あ*
command line: a  あ*
argv[0]: a
argv[1]: あ.txt

おまじないを実行した場合

C:\dev>a あ*
command line: a  あ*
argv[0]: a
argv[1]: あ*

アドホックすぎてかなり嫌ですね。どのコンパイラでも同じ動作をさせたいのならば、GetCommandLineW()CommandLineToArgvW() を使うべきだと思います。

Posted at by



2014/09/02


名前そのままやん感がすごいですが。

tylertreat/chan - GitHub
https://github.com/tylertreat/chan

golang の chan をC言語から使える様にするライブラリです。やはりC言語というだけあって、受け渡す値の型は void* ですがそこは目をつむりましょう。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "./src/chan.h"

#ifndef _WIN32
#include <unistd.h>
#include <termios.h>

char
getch() {
  char c = 0;
  struct termios old = {0};
  fflush(stdout);
  if (tcgetattr(0, &old) < 0)
    perror("tcsetattr()");
  old.c_lflag &= ~ICANON;
  old.c_lflag &= ~ECHO;
  old.c_cc[VMIN] = 1;
  old.c_cc[VTIME] = 0;
  if (tcsetattr(0, TCSANOW, &old) < 0)
    perror("tcsetattr ICANON");
  if (read(0, &c, 1) < 0)
    perror("read()");
  old.c_lflag |= ICANON;
  old.c_lflag |= ECHO;
  if (tcsetattr(0, TCSADRAIN, &old) < 0)
    perror ("tcsetattr ~ICANON");
  return c;
}
#endif

void*
capture(void *p) {
  chan_t* r = (chan_t*) p;
  while (!chan_is_closed(r)) {
    char c = getch();
    if (c == 'q'break;
    chan_send(r, (void*) &c);
  }
  chan_close(r);
  return NULL;
}

void*
fizzbuzz(void *p) {
  chan_t* r = (chan_t*) p;
  int n;
  for (n = 1; n <= 100 && !chan_is_closed(r); n++) {
    char buf[20] = {0};
    if (n % 3 == 0) strcat(buf, "Fizz");
    if (n % 5 == 0) strcat(buf, "Buzz");
    if (buf[0] == 0) sprintf(buf, "%d", n);
    chan_send(r, (void*) buf);
    sleep(1);
  }
  chan_close(r);
  return NULL;
}


int
main(int argc, char* argv[]) {
  chan_t* chans[2] = {chan_init(0), chan_init(0)};
  pthread_t th[2];

  if (pthread_create(&th[0], NULL, fizzbuzz, chans[0]) !=0) {
    perror("pthread_create");
    exit(1);
  }

  if (pthread_create(&th[1], NULL, capture, chans[1]) !=0) {
    perror("pthread_create");
    exit(1);
  }

  while (!chan_is_closed(chans[0]) && !chan_is_closed(chans[1])) {
    void* v = NULL;
    switch(chan_select(chans, 2, &v, NULL0NULL)) {
    case 0:
      printf("FizzBuzz: %s\n", (char*) v);
      break;
    case 1:
      printf("KeyTyped: %c\n", *(char*) v);
      break;
    default:
      break;
    }
  }
  chan_close(chans[0]);
  chan_close(chans[1]);
  pthread_join(th[0], NULL);
  pthread_join(th[1], NULL);
  return 0;
}

1秒毎に更新される FizzBuzz を表示しながら、キーボードで入力された文字を表示します。FizzBuzz が100を超えるか、q をタイプすると終了します。FizzBuzz もキーボード入力もどちらもスレッドで実行しており非同期に処理されます。golang の様に go と書くだけで非同期処理が実行される訳ではないので pthread_create なんて長ったらしい名前を書く必要があります。C++ なら std::sync あたりを使えば簡単に書けそうな気もしますね。

pthread まわりをもうちょっと隠蔽すれば、まぁまぁかっこいい非同期処理が書ける様になるんじゃないかと思います。ただし golang の様に OS のスレッドとコルーチンをうまく切り替えたりはしないのでその辺は頑張る他無いです。

メッセージングを一から作るとなると結構大変なのでそこそこ使えるライブラリだと思います。

ただし void* で値を受け渡すということは、ヒープがそのまま渡るので、chan のバッファを1以上で動かす際には都度確保したメモリを渡さないと上書きされてしまいます。上記の例では説明の手間を省くために簡略化していますが、実際はもっと malloc/free が入り組んだコードになるかと思います。

Posted at by