Chez Scheme でコンソールに日本語を出力する

プログラミング言語 Scheme の処理系のひとつである Chez Scheme は仕様 (R6RS) が定める三種類の符号での入出力をサポートしています。

  • ISO8859-1
  • UTF-8
  • UTF-16

更に、独自拡張として iconv による変換も可能ですので、ほとんどどんな文字コードでも使えると思ってもいいでしょう。 もちろん、日本語の文字コードとしてよく使われる Shift_JIS やら CP932 やらにも対応しています。

さて、 R6RS では標準入出力用のポートにどんな文字コード変換器が結びついているか、あるいは結びついていないかは未定義ですが、 Chez Scheme では UTF-8 を採用しています。 しかし、 Windows の標準出力に UTF-8 のテキストを流し込んでもコンソール上では化けてしまいます。 それを解決するために CP932 に変換してからの出力を試みている事例を見掛けました。

>>Noppi のおぼえがき: Chez Schemeでコンソールに日本語を出力する

ですが、これでは CP932 の文字セットに含まれない文字の情報が消えてしまいます。

Windows は Unicode 対応に積極的で、文字列のやりとりが必要な API はほぼ全て Unicode 版が存在しますし、コンソール関連の API についてもまたそうであるのですが、互換性の制約からか、標準出力経由でコンソールに表示する文字は CP932 と解釈してしまうようになっているのであって、コンソール関連の API を直接使えば Unicode のままで利用できます。

そこで、 Chez Scheme の Foreign Interface を用いて Unicode の文字をコンソールに表示するライブラリを作ってみました。 カスタムポートとして定義しており、このポートに対して出力することでコンソールに表示されます。 標準出力をリダイレクトしていてもコンソールに出力します。

(library (console-port)
  (export open-console-output-port)
  (import (chezscheme))

  (define dummy (begin (load-shared-object "kernel32.dll") 1))

  (define-ftype handle void*)

  (define open-existing 3)

  (define create-file
    (foreign-procedure __stdcall "CreateFileW"
      (wstring unsigned-32 unsigned-32 void* unsigned-32 unsigned-32 void*)
      void*))

  (define file-share-write 2)

  (define generic-write #x40000000)
  
  (define (get-active-console-buffer)
    (create-file "CONOUT$" generic-write file-share-write 0 open-existing 0 0))

  (define write-console
    (foreign-procedure __stdcall "WriteConsoleW"
      (void* wstring unsigned-32 u32* void*)
      boolean))

  (define (open-console-output-port)
    (let ((output-handle (get-active-console-buffer))
          (vsize (make-bytevector 4)))
      (define (write-to-console string start count)
        (let ((str (substring string start (+ start count))))
          (write-console output-handle str count vsize 0)
          count))
      (make-custom-textual-output-port "console" write-to-console #f #f #f)))
  )

使い方としては、 open-console-output-port 手続きが返すポートに書き込むだけです。

(import (rnrs)
        (console-port))

(let ((port (open-console-output-port)))
  (display "あいうえお\nかきくけこ\n" port)
  (display "♘♞♙♕♟♝♜♗♛♚♖♔" port)
  (flush-output-port port)) ;; フラッシュを忘れずに!

CP932 の範囲外の文字もきちんと表示できています。 ただし、コンソールに適用しているフォントがグリフを持っていない場合もあるので Unicode にある文字を全て確実に表示できるというわけではありません。

Document ID: fb260a71b629c76209cdc797c43c9bdd

時刻:
ラベル:

3 件のコメント:

  1. 上記 library console-port をコンパイルすると以下のようなエラーが出てコンパイルできません。
    解決方をご教授頂けますでしょうか。 よろしくお願いいたします。

    Exception: invalid foreign-procedure convention __stdcall at line 12, char 24 of console-port.ss

    返信削除
  2. 上記 library console-port コンパイルの件

    __stdcall を取ったらコンパイルできました。 

    返信削除
    返信
    1. 64bit 版では __stdcall でも __cdecl でもない ABI で統一されていて、指定する必要がない (指定すると誤り) ようです。

      削除