Hatena::ブログ(Diary)

マクロツイーター このページをアンテナに追加 RSSフィード Twitter

2018-05-12

Windows の ANSI コードページを知りたい話(1)

問題の背景とか

ANSI コードページについてテキトーに語る

基本的に、イマドキの Windows は文字列を(論理的には*1)「Unicode 文字の列」として扱う。Windows のフツーの GUI プログラムUnicode 文字列を直接扱う。しかし、Windows 的にレガシーな「文字列をバイト列として扱う」ような API*2を使う場合には、文字列とバイト列の間で相互変換が必要になる。

このような場合に用いられる文字コードANSI コードページである。要するに「システムで規定された、レガシー文字コードの種類」(の一つ)である。(コードページ(code page)とは文字コードの種類を表す整数値を指す用語。)例えば、日本語版 Windows の既定の ANSI コードページは「932」で、これは“Microsoft 的なシフト JIS”を表し、また、英語版 Windows の既定の ANSI コードページは「1252」で、これは“Microsoft 的な Latin-1”を表す。

※「システムの規定値(または設定値)であるレガシー文字コード」には他にも「OEM コードページ」とかイロイロあるけど詳細は省略。

マルチプラットフォームCUI プログラムでは「バイト列扱いの API」が使われることが多く、そういうプログラムの動作は ANSI コードページに依存することになる。

Python な文字列の話

以下では、プログラム言語における“文字列”の扱いと Windows のそれとの整合性について述べてみる。

Windows 上の Python でファイルを作成する場合に、次のように非 ASCII 文字のファイル名を指定したとする。

# -*- coding: utf-8 -*-
with open('アレ.txt', 'w') as f:
    f.write("TeX\n")

これは期待通り、「アレ.txt」というな雨のファイルを作成する。Python は文字列を(論理的には*3)「Unicode 文字の列」として扱う。つまり、“Python の文字列”の 'アレ.txt' が表すのは「アレ.txt」という Unicode 文字列であり、従って、最終的に WindowsAPI を呼ぶ場合には(バイト列ではなく)この文字列を渡すべきであり、また実際にそう処理しているため期待通りの結果が得られているわけである。

最近のプログラム言語は、Python と同様に、文字列を「Unicode 文字の列」として扱うものが多く*4、そういう言語(かつ Windows にちゃんと対応したもの)であれば、非 ASCII 文字のファイル名は正常に取り扱えるであろう。

Lua な文字列の話

ところが、Lua だと事情は異なる。

-- このファイルの文字コードは UTF-8
local n = 'アレ.txt'
-- n == '\xE3\x82\xA2\xE3\x83\xAC.txt' である
local f = assert(io.open(n, 'w'))
f:write("TeX\n")
f:close()

Lua は文字列を単なるバイト列として扱う言語である。つまり、“Lua の文字列”の 'アレ.txt' が表すものは「E3 82 A2 E3 83 AC 2E 74 78 74」というバイト列でしかない。文字コードの情報が欠けているため、このバイト列単体では「アレ.txt」という文字列を表さない。

そこで、LuaWindows の機能を呼び出す場合、“文字列が渡せない”ため、必然的に「文字列をバイト列として扱う」API が利用される。その結果、先述の通り、バイト列は ANSI コードページで解釈されることになる。例えば、日本語版 Winddows の既定であれば、「E3 82 A2 E3 83 AC 2E 74 78 74」をコードページ 932(シフト JIS)で解釈して、「繧「繝ャ.txt」という名前のファイルが作成される。

Perl な文字列の話

アレアレなことで有名な Perl では、アレアレなことになる。

# このファイルの文字コードは UTF-8
use v5.20;
use utf8;
my $n = 'アレ.txt';
# $n eq "\x{30A2}\x{30EC}.txt" である
open(my $h, '>', $n) or die;
print $h ("TeX\n");
close($h);

PerlPython 等と同じく文字列を(論理的には)「Unicode 文字の列」として扱う。*5だから、“Perl の文字列”の 'アレ.txt' はそのまま「アレ.txt」という文字列である。*6

ということは、作成されるファイル名は Python の時と同じく「アレ.txt」となるべきであるが、実際にはなぜか Lua の時と同じ「繧「繝ャ.txt」になってしまう。アレアレである。

さらにもっとアレアレな例を挙げる。

# このファイルの文字コードは UTF-8
use v5.20;
#use utf8; # これの有無は無関係

my $n1 = "\xB1";
my $n2 = substr("\xB1\x{100}", 0, 1);
# $n1 と $n2 は同じ文字列…
($n1 eq $n2) or die 'WOO'; # …なのは確か

# $n1 のファイル名で作成
open(my $h1, '>', $n1) or die;
print $h1 ("TeX\n");
close($h1);
# $n2 のファイル名で追記
open(my $h2, '>>', $n2) or die;
print $h2 ("expl3\n");
close($h2);

$n1$n2 はともに "\xB1" という文字列であり、事実、$n1 eq $n2 が成立している。なので、上掲のプログラムはファイルを 1 つだけ作成するはずである。ところが、日本語版の Windows で実行すると、2 つのファイルが作られるというアレアレなことになる。

  • $n1 に対する open は「ア」(B1)というファイルを作る。
  • $n2 に対する open は「ツア」(C2 B1)というファイルを作る。

どうやら、Perl は常に「文字列をバイト列として扱う」API を使っていて、しかもその際に“Perl の文字列”の内部表現のバイト列をそのまま渡しているようなのである。“Perl の文字列”の内部表現には 2 種類あって、$n1$n2 は同じ文字列であるが内部表現が異なる*7ため、WindowsAPI に別のバイト列として渡されたと推定できる。

実用的な慣習としては、Perl ではファイル名は常にバイト列と見なすべきなのだろう。つまり、「文字列をバイト列として扱う」API に渡したときに辻褄が合うように、プログラマが文字列を適切な文字コードでバイト列に変換する必要がある。Windwos の場合は、ANSI コードページに依存するということになる。

# このファイルの文字コードは UTF-8
use v5.20;
use utf8;
use Encode qw(encode);
# 再度確認: "Perl の文字列"はUnicode文字列とバイト列の
# 何れを表すのにも用いられる

my $n1 = "アレ.txt"; # Unicode文字列
# ANSI コードページのバイト列に変換する
my $n2 = encode('cp932', $n1); # バイト列

open(my $h, '>', $n2) or die;
print $h ("TeX\n");
close($h);

上記プログラムは、日本語版 Windows において、期待通り「アレ.txt」という名のファイルを作成する。

とりあえずまとめ

Windows の機能(ファイル操作など)を利用するプログラムの場合:

  • Python とか Ruby とかは ANSI コードページに依存しない。
  • LuaPerlANSI コードページに依存する。
  • Perl はアレアレ。

「日本語 Windows だったら CP932 だろ常考」の終焉

既に述べたとおり、日本語版の WindowsANSI コードページは 932(シフト JIS)である。これは Windows が登場してから 20 年以上経っても変わっていない。だから“日本語版 Windows ユーザ専用の CUI プログラム”では「ANSI コードページは 932」という決め打ちが行われることが多い。これは実際に文字列のエンコードを行う場合の他に、所謂「ダメ文字対策*8」を行う場合も含まれる。

ところが、最近はその状況が変わってきているようである。

どうやら、Windows 10 が「ANSI コードページを 65001(UTF-8 を表すコードページ)にする運用」をサポートし始めたようだ。*9いまや「文字コードUTF-8 が正義」な時代であるので、この流れは自然なことであろう。それゆえ、日本語 Windows ユーザでも ANSI コードページを UTF-8 にしている人は増えていく可能性は大いにあるだろう。そうなると、プログラム側での「ANSI コードページは 932」という決め打ちは破綻してしまう。

というわけで帰結は

PerlLua で、ANSI コードページの設定値が何であるかを知りたい。

ふう、やっと導入の話が終わった……。

続く

*1:つまり、内部表現は「符号化形式(CEF)としての UTF-16」であるが、それは捨象する、ということ。要するに、Windows にとって“ネイティブ”な符号化文字集合Unicode である。

*2POSIX 互換のシステム関数や C ライブラリ関数も含む。

*3:再び内部表現については捨象する。

*4Ruby は少し特殊で、“Ruby の文字列”は「Unicode 文字の列」ではないが、単なるバイト列ではなく文字としての情報を持っているため、“Ruby の文字列”を Unicode 文字列に変換できる。従って、Windows での動作についてこの点で問題になることはない。<

*5:ただし Perl はアレアレなので、“Perl の文字列”は文字列とバイト列の何れを表すのにも用いられる。アレアレである。

*6:例えば、substr('アレ.txt', 0, 1) は先頭の 1 文字(1 バイトでなく)の 'ア' を返す。

*7:“Perl の文字列”の内部表現には「直接」と「UTF-8」があり、$n1 は「直接」形式なので、"\xB1" が「B1」というバイト列になり、$n2 は「UTF-8」形式なので、"\xB1" が「C2 B1」というバイト列になったと考えられる。Perl によくある「UTF-8 フラグの亡霊問題」(同じ文字列のはずなのに挙動が異なる)の一種なんだろう。

*8:2 バイト目が 5C である 2 バイト文字が「\」と見なされるのを防ぐこと。ANSI コードページが 932 でない場合は、この対策はかえって有害になる。

*9:「ANSI コードページを UTF−8 にできる」ということは、もはや「ANSI コードページはレガシー文字コード」という位置づけではなくなる。

ttkttk 2018/05/18 20:25 TeX Live の kpathseach では「ダメ文字対策」は、GetACP()とかGetOEMCP()でCodePageをチェックしCP932, CP936, CP950以外では発動しないようになっているので、ANSIコードページが CP65001になっても大丈夫だと思います。
しかし、Linuxと同様に、p(La)TeXの日本語ファイル名はCP932で今まで動いていたものが動かなくなるでしょうね。やれやれ。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証