DOBON.NET

文字コードを判別する

バイト型配列を文字列に変換する方法は「バイト型配列のデータを文字コードを指定して文字列に変換する」で紹介しましたが、データの文字コードが分からなければ変換できません。ここでは、バイト型配列のデータから文字コードを判別する方法を紹介します。

.NET Frameworkでは、基本的には、文字コードを判別する方法が用意されていませんので、外部DLL、OCX等を使うか、自分でコードを書くかということになります。

BOMで判断する方法

BOM(バイトオーダーマークbyte order mark)と呼ばれる「印」がデータの先頭に付いている時は、これを手掛かりに文字コードを判別することができます。

以下に、BOMから文字コード(UTF-8、UTF-16BE、UTF-16LE、UTF-32BE、UTF-32LE)を判別するメソッドの例を示します。

VB.NET
コードを隠すコードを選択
''' <summary>
''' BOMを調べて、文字コードを判別する。
''' </summary>
''' <param name="bytes">文字コードを調べるデータ。</param>
''' <returns>BOMが見つかった時は、対応するEncodingオブジェクト。
''' 見つからなかった時は、null。</returns>
Public Shared Function DetectEncodingFromBOM(bytes As Byte()) _
    As System.Text.Encoding

    If bytes.Length < 2 Then
        Return Nothing
    End If
    If (bytes(0) = &HFE) AndAlso (bytes(1) = &HFF) Then
        'UTF-16 BE
        Return New System.Text.UnicodeEncoding(True, True)
    End If
    If (bytes(0) = &HFF) AndAlso (bytes(1) = &HFE) Then
        If (4 <= bytes.Length) AndAlso _
            (bytes(2) = &H0) AndAlso (bytes(3) = &H0) Then
            'UTF-32 LE
            Return New System.Text.UTF32Encoding(False, True)
        End If
        'UTF-16 LE
        Return New System.Text.UnicodeEncoding(False, True)
    End If
    If bytes.Length < 3 Then
        Return Nothing
    End If
    If (bytes(0) = &HEF) AndAlso (bytes(1) = &HBB) AndAlso _
        (bytes(2) = &HBF) Then
        'UTF-8
        Return New System.Text.UTF8Encoding(True, True)
    End If
    If bytes.Length < 4 Then
        Return Nothing
    End If
    If (bytes(0) = &H0) AndAlso (bytes(1) = &H0) AndAlso _
        (bytes(2) = &HFE) AndAlso (bytes(3) = &HFF) Then
        'UTF-32 BE
        Return New System.Text.UTF32Encoding(True, True)
    End If

    Return Nothing
End Function
C#
コードを隠すコードを選択
/// <summary>
/// BOMを調べて、文字コードを判別する。
/// </summary>
/// <param name="bytes">文字コードを調べるデータ。</param>
/// <returns>BOMが見つかった時は、対応するEncodingオブジェクト。
/// 見つからなかった時は、null。</returns>
public static System.Text.Encoding DetectEncodingFromBOM(byte[] bytes)
{
    if (bytes.Length < 2)
    {
        return null;
    }
    if ((bytes[0] == 0xfe) && (bytes[1] == 0xff))
    {
        //UTF-16 BE
        return new System.Text.UnicodeEncoding(true, true);
    }
    if ((bytes[0] == 0xff) && (bytes[1] == 0xfe))
    {
        if ((4 <= bytes.Length) &&
            (bytes[2] == 0x00) && (bytes[3] == 0x00))
        {
            //UTF-32 LE
            return new System.Text.UTF32Encoding(false, true);
        }
        //UTF-16 LE
        return new System.Text.UnicodeEncoding(false, true);
    }
    if (bytes.Length < 3)
    {
        return null;
    }
    if ((bytes[0] == 0xef) && (bytes[1] == 0xbb) && (bytes[2] == 0xbf))
    {
        //UTF-8
        return new System.Text.UTF8Encoding(true, true);
    }
    if (bytes.Length < 4)
    {
        return null;
    }
    if ((bytes[0] == 0x00) && (bytes[1] == 0x00) &&
        (bytes[2] == 0xfe) && (bytes[3] == 0xff))
    {
        //UTF-32 BE
        return new System.Text.UTF32Encoding(true, true);
    }

    return null;
}

次にこのメソッドの使い方を示します。この例では、TextBox1にテキストファイルのパスを入力し、Button1をクリックすると、テキストファイルの文字コードを調べ、デコードし、RichTextBox1にその内容を表示するようにしています。

VB.NET
コードを隠すコードを選択
Private Sub Button1_Click(sender As Object, e As EventArgs) _
    Handles Button1.Click

    'テキストファイルを開く
    Dim bs As Byte() = System.IO.File.ReadAllBytes(TextBox1.Text)
    '.NET Framework 1.1以下では、次のようにする
    'Dim fs As New System.IO.FileStream(TextBox1.Text, _
    '    System.IO.FileMode.Open, System.IO.FileAccess.Read)
    'Dim bs(fs.Length - 1) As Byte
    'fs.Read(bs, 0, bs.Length)
    'fs.Close()

    '文字コードを判別する
    Dim enc As System.Text.Encoding = DetectEncodingFromBOM(bs)

    If enc Is Nothing Then
        Console.WriteLine("BOMが見つかりませんでした。")
    End If

    'デコードして表示する
    'はじめのBOMを飛ばしてデコードする
    Dim bomLen As Integer = enc.GetPreamble().Length
    RichTextBox1.Text = enc.GetString(bs, bomLen, bs.Length - bomLen)
End Sub
C#
コードを隠すコードを選択
//Button1のClickイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //テキストファイルを開く
    byte[] bs = System.IO.File.ReadAllBytes(TextBox1.Text);
    //.NET Framework 1.1以下では、次のようにする
    //System.IO.FileStream fs = new System.IO.FileStream(
    //    TextBox1.Text, System.IO.FileMode.Open,
    //    System.IO.FileAccess.Read);
    //byte[] bs = new byte[fs.Length];
    //fs.Read(bs, 0, bs.Length);
    //fs.Close();

    //文字コードを判別する
    System.Text.Encoding enc = DetectEncodingFromBOM(bs);

    if (enc == null)
    {
        Console.WriteLine("BOMが見つかりませんでした。");
    }

    //デコードして表示する
    //はじめのBOMを飛ばしてデコードする
    int bomLen = enc.GetPreamble().Length;
    RichTextBox1.Text = enc.GetString(bs, bomLen, bs.Length - bomLen);
}

なお、StreamReaderクラスを使用してテキストファイルを開いた時は、BOMが付加されていれば、自動的に適切なエンコーディングが選択されます。よって、テキストファイルにBOMが付いていることが分かっているのであれば、普通にStreamReaderクラスで開くことができます。BOMが付いているか分からない場合でも、はじめにテキストファイルの先頭の4バイトのみを読み込み、それがBOMかどうかを調べ、BOMならばテキストファイルをStreamReaderクラスで開くという方法もあります。StreamReaderクラスについて詳しくは、「文字コードを指定してテキストファイルを読み込む」をご覧ください。

Jcode.pmを参考にした方法

ここからは、BOMが付いていないデータの文字コードを判別する方法を紹介します。ただし、あくまで文字コードを推測するだけで、正確に判別することはできません。

次に示すコードは、私がJcode.pmのgetcodeメソッドを参考にして書かせていただいた(移植したつもり)メソッドです。バイナリ配列で渡されたデータが、JIS、Shift-JIS、EUC、UTF-8(もしくはASCII)のいずれであるかを判別し、結果をEncodingオブジェクトで返します。

VB.NET
コードを隠すコードを選択
''' <summary>
''' 文字コードを判別する
''' </summary>
''' <remarks>
''' Jcode.pmのgetcodeメソッドを移植したものです。
''' Jcode.pm(http://openlab.ring.gr.jp/Jcode/index-j.html)
''' Jcode.pmの著作権情報
''' Copyright 1999-2005 Dan Kogai <dankogai@dan.co.jp>
''' This library is free software; you can redistribute it and/or modify it
'''  under the same terms as Perl itself.
''' </remarks>
''' <param name="bytes">文字コードを調べるデータ</param>
''' <returns>適当と思われるEncodingオブジェクト。
''' 判断できなかった時はnull。</returns>
Public Shared Function GetCode(ByVal bytes As Byte()) As System.Text.Encoding
    Const bEscape As Byte = &H1B
    Const bAt As Byte = &H40
    Const bDollar As Byte = &H24
    Const bAnd As Byte = &H26
    Const bOpen As Byte = &H28 ''('
    Const bB As Byte = &H42
    Const bD As Byte = &H44
    Const bJ As Byte = &H4A
    Const bI As Byte = &H49

    Dim len As Integer = bytes.Length
    Dim b1 As Byte, b2 As Byte, b3 As Byte, b4 As Byte

    'Encode::is_utf8 は無視

    Dim isBinary As Boolean = False
    Dim i As Integer
    For i = 0 To len - 1
        b1 = bytes(i)
        If b1 <= &H6 OrElse b1 = &H7F OrElse b1 = &HFF Then
            ''binary'
            isBinary = True
            If b1 = &H0 AndAlso i < len - 1 AndAlso bytes(i + 1) <= &H7F Then
                'smells like raw unicode
                Return System.Text.Encoding.Unicode
            End If
        End If
    Next
    If isBinary Then
        Return Nothing
    End If

    'not Japanese
    Dim notJapanese As Boolean = True
    For i = 0 To len - 1
        b1 = bytes(i)
        If b1 = bEscape OrElse &H80 <= b1 Then
            notJapanese = False
            Exit For
        End If
    Next
    If notJapanese Then
        Return System.Text.Encoding.ASCII
    End If

    For i = 0 To len - 3
        b1 = bytes(i)
        b2 = bytes(i + 1)
        b3 = bytes(i + 2)

        If b1 = bEscape Then
            If b2 = bDollar AndAlso b3 = bAt Then
                'JIS_0208 1978
                'JIS
                Return System.Text.Encoding.GetEncoding(50220)
            ElseIf b2 = bDollar AndAlso b3 = bB Then
                'JIS_0208 1983
                'JIS
                Return System.Text.Encoding.GetEncoding(50220)
            ElseIf b2 = bOpen AndAlso (b3 = bB OrElse b3 = bJ) Then
                'JIS_ASC
                'JIS
                Return System.Text.Encoding.GetEncoding(50220)
            ElseIf b2 = bOpen AndAlso b3 = bI Then
                'JIS_KANA
                'JIS
                Return System.Text.Encoding.GetEncoding(50220)
            End If
            If i < len - 3 Then
                b4 = bytes(i + 3)
                If b2 = bDollar AndAlso b3 = bOpen AndAlso b4 = bD Then
                    'JIS_0212
                    'JIS
                    Return System.Text.Encoding.GetEncoding(50220)
                End If
                If i < len - 5 AndAlso _
                    b2 = bAnd AndAlso b3 = bAt AndAlso b4 = bEscape AndAlso _
                    bytes(i + 4) = bDollar AndAlso bytes(i + 5) = bB Then
                    'JIS_0208 1990
                    'JIS
                    Return System.Text.Encoding.GetEncoding(50220)
                End If
            End If
        End If
    Next

    'should be euc|sjis|utf8
    'use of (?:) by Hiroki Ohzaki <ohzaki@iod.ricoh.co.jp>
    Dim sjis As Integer = 0
    Dim euc As Integer = 0
    Dim utf8 As Integer = 0
    For i = 0 To len - 2
        b1 = bytes(i)
        b2 = bytes(i + 1)
        If ((&H81 <= b1 AndAlso b1 <= &H9F) OrElse _
            (&HE0 <= b1 AndAlso b1 <= &HFC)) AndAlso _
            ((&H40 <= b2 AndAlso b2 <= &H7E) OrElse _
             (&H80 <= b2 AndAlso b2 <= &HFC)) Then
            'SJIS_C
            sjis += 2
            i += 1
        End If
    Next
    For i = 0 To len - 2
        b1 = bytes(i)
        b2 = bytes(i + 1)
        If ((&HA1 <= b1 AndAlso b1 <= &HFE) AndAlso _
            (&HA1 <= b2 AndAlso b2 <= &HFE)) OrElse _
            (b1 = &H8E AndAlso (&HA1 <= b2 AndAlso b2 <= &HDF)) Then
            'EUC_C
            'EUC_KANA
            euc += 2
            i += 1
        ElseIf i < len - 2 Then
            b3 = bytes(i + 2)
            If b1 = &H8F AndAlso (&HA1 <= b2 AndAlso b2 <= &HFE) AndAlso _
                (&HA1 <= b3 AndAlso b3 <= &HFE) Then
                'EUC_0212
                euc += 3
                i += 2
            End If
        End If
    Next
    For i = 0 To len - 2
        b1 = bytes(i)
        b2 = bytes(i + 1)
        If (&HC0 <= b1 AndAlso b1 <= &HDF) AndAlso _
            (&H80 <= b2 AndAlso b2 <= &HBF) Then
            'UTF8
            utf8 += 2
            i += 1
        ElseIf i < len - 2 Then
            b3 = bytes(i + 2)
            If (&HE0 <= b1 AndAlso b1 <= &HEF) AndAlso _
                (&H80 <= b2 AndAlso b2 <= &HBF) AndAlso _
                (&H80 <= b3 AndAlso b3 <= &HBF) Then
                'UTF8
                utf8 += 3
                i += 2
            End If
        End If
    Next
    'M. Takahashi's suggestion
    'utf8 += utf8 / 2;

    System.Diagnostics.Debug.WriteLine( _
        String.Format("sjis = {0}, euc = {1}, utf8 = {2}", sjis, euc, utf8))
    If euc > sjis AndAlso euc > utf8 Then
        'EUC
        Return System.Text.Encoding.GetEncoding(51932)
    ElseIf sjis > euc AndAlso sjis > utf8 Then
        'SJIS
        Return System.Text.Encoding.GetEncoding(932)
    ElseIf utf8 > euc AndAlso utf8 > sjis Then
        'UTF8
        Return System.Text.Encoding.UTF8
    End If

    Return Nothing
End Function
C#
コードを隠すコードを選択
/// <summary>
/// 文字コードを判別する
/// </summary>
/// <remarks>
/// Jcode.pmのgetcodeメソッドを移植したものです。
/// Jcode.pm(http://openlab.ring.gr.jp/Jcode/index-j.html)
/// Jcode.pmの著作権情報
/// Copyright 1999-2005 Dan Kogai <dankogai@dan.co.jp>
/// This library is free software; you can redistribute it and/or modify it
///  under the same terms as Perl itself.
/// </remarks>
/// <param name="bytes">文字コードを調べるデータ</param>
/// <returns>適当と思われるEncodingオブジェクト。
/// 判断できなかった時はnull。</returns>
public static System.Text.Encoding GetCode(byte[] bytes)
{
    const byte bEscape = 0x1B;
    const byte bAt = 0x40;
    const byte bDollar = 0x24;
    const byte bAnd = 0x26;
    const byte bOpen = 0x28;    //'('
    const byte bB = 0x42;
    const byte bD = 0x44;
    const byte bJ = 0x4A;
    const byte bI = 0x49;

    int len = bytes.Length;
    byte b1, b2, b3, b4;

    //Encode::is_utf8 は無視

    bool isBinary = false;
    for (int i = 0; i < len; i++)
    {
        b1 = bytes[i];
        if (b1 <= 0x06 || b1 == 0x7F || b1 == 0xFF)
        {
            //'binary'
            isBinary = true;
            if (b1 == 0x00 && i < len - 1 && bytes[i + 1] <= 0x7F)
            {
                //smells like raw unicode
                return System.Text.Encoding.Unicode;
            }
        }
    }
    if (isBinary)
    {
        return null;
    }

    //not Japanese
    bool notJapanese = true;
    for (int i = 0; i < len; i++)
    {
        b1 = bytes[i];
        if (b1 == bEscape || 0x80 <= b1)
        {
            notJapanese = false;
            break;
        }
    }
    if (notJapanese)
    {
        return System.Text.Encoding.ASCII;
    }

    for (int i = 0; i < len - 2; i++)
    {
        b1 = bytes[i];
        b2 = bytes[i + 1];
        b3 = bytes[i + 2];

        if (b1 == bEscape)
        {
            if (b2 == bDollar && b3 == bAt)
            {
                //JIS_0208 1978
                //JIS
                return System.Text.Encoding.GetEncoding(50220);
            }
            else if (b2 == bDollar && b3 == bB)
            {
                //JIS_0208 1983
                //JIS
                return System.Text.Encoding.GetEncoding(50220);
            }
            else if (b2 == bOpen && (b3 == bB || b3 == bJ))
            {
                //JIS_ASC
                //JIS
                return System.Text.Encoding.GetEncoding(50220);
            }
            else if (b2 == bOpen && b3 == bI)
            {
                //JIS_KANA
                //JIS
                return System.Text.Encoding.GetEncoding(50220);
            }
            if (i < len - 3)
            {
                b4 = bytes[i + 3];
                if (b2 == bDollar && b3 == bOpen && b4 == bD)
                {
                    //JIS_0212
                    //JIS
                    return System.Text.Encoding.GetEncoding(50220);
                }
                if (i < len - 5 &&
                    b2 == bAnd && b3 == bAt && b4 == bEscape &&
                    bytes[i + 4] == bDollar && bytes[i + 5] == bB)
                {
                    //JIS_0208 1990
                    //JIS
                    return System.Text.Encoding.GetEncoding(50220);
                }
            }
        }
    }

    //should be euc|sjis|utf8
    //use of (?:) by Hiroki Ohzaki <ohzaki@iod.ricoh.co.jp>
    int sjis = 0;
    int euc = 0;
    int utf8 = 0;
    for (int i = 0; i < len - 1; i++)
    {
        b1 = bytes[i];
        b2 = bytes[i + 1];
        if (((0x81 <= b1 && b1 <= 0x9F) || (0xE0 <= b1 && b1 <= 0xFC)) &&
            ((0x40 <= b2 && b2 <= 0x7E) || (0x80 <= b2 && b2 <= 0xFC)))
        {
            //SJIS_C
            sjis += 2;
            i++;
        }
    }
    for (int i = 0; i < len - 1; i++)
    {
        b1 = bytes[i];
        b2 = bytes[i + 1];
        if (((0xA1 <= b1 && b1 <= 0xFE) && (0xA1 <= b2 && b2 <= 0xFE)) ||
            (b1 == 0x8E && (0xA1 <= b2 && b2 <= 0xDF)))
        {
            //EUC_C
            //EUC_KANA
            euc += 2;
            i++;
        }
        else if (i < len - 2)
        {
            b3 = bytes[i + 2];
            if (b1 == 0x8F && (0xA1 <= b2 && b2 <= 0xFE) &&
                (0xA1 <= b3 && b3 <= 0xFE))
            {
                //EUC_0212
                euc += 3;
                i += 2;
            }
        }
    }
    for (int i = 0; i < len - 1; i++)
    {
        b1 = bytes[i];
        b2 = bytes[i + 1];
        if ((0xC0 <= b1 && b1 <= 0xDF) && (0x80 <= b2 && b2 <= 0xBF))
        {
            //UTF8
            utf8 += 2;
            i++;
        }
        else if (i < len - 2)
        {
            b3 = bytes[i + 2];
            if ((0xE0 <= b1 && b1 <= 0xEF) && (0x80 <= b2 && b2 <= 0xBF) &&
                (0x80 <= b3 && b3 <= 0xBF))
            {
                //UTF8
                utf8 += 3;
                i += 2;
            }
        }
    }
    //M. Takahashi's suggestion
    //utf8 += utf8 / 2;

    System.Diagnostics.Debug.WriteLine(
        string.Format("sjis = {0}, euc = {1}, utf8 = {2}", sjis, euc, utf8));
    if (euc > sjis && euc > utf8)
    {
        //EUC
        return System.Text.Encoding.GetEncoding(51932);
    }
    else if (sjis > euc && sjis > utf8)
    {
        //SJIS
        return System.Text.Encoding.GetEncoding(932);
    }
    else if (utf8 > euc && utf8 > sjis)
    {
        //UTF8
        return System.Text.Encoding.UTF8;
    }

    return null;
}

次にこのメソッドの使い方を示します。このサンプルでも先と同じく、TextBox1にテキストファイルのパスを入力し、Button1をクリックすると、RichTextBox1にその内容を表示します。

VB.NET
コードを隠すコードを選択
'Button1のクリックイベントハンドラ
Private Sub Button1_Click(sender As Object, e As EventArgs) _
    Handles Button1.Click
    'テキストファイルを開く
    Dim bs As Byte() = System.IO.File.ReadAllBytes(TextBox1.Text)
    '文字コードを取得する
    Dim enc As System.Text.Encoding = GetCode(bs)
    'デコードして表示する
    RichTextBox1.Text = enc.GetString(bs)
End Sub
C#
コードを隠すコードを選択
//Button1のクリックイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //テキストファイルを開く
    byte[] bs = System.IO.File.ReadAllBytes(TextBox1.Text);
    //文字コードを取得する
    System.Text.Encoding enc = GetCode(bs);
    //デコードして表示する
    RichTextBox1.Text = enc.GetString(bs);
}

第三者の作成したクラス、コードを使う方法

無料で使用できる(と思われる)クラスやライブラリ、サンプルコード等を、以下に紹介します。使用の際には、ライセンス等の使用条件を必ずご確認ください。

mlang.dllのIMultiLanguage2::DetectInputCodepageを使う方法

外部DLLに頼る方法としては、mlang.dllのIMultiLanguage2::DetectInputCodepageを使用する方法があります。文字コードの判別を行うソフトの多くが、これを使用しているのではないかと思います。この方法は、Internet Explorer 5以上で使用できます。

以下に.NETでIMultiLanguage2::DetectInputCodepageメソッドを使うための方法を簡単に説明します。

COMを登録して、参照設定に追加する

mlang.dllを使うためにVisual Studioのメニュー[プロジェクト]-[参照の追加]でCOMを探してみても、mlang.dllは通常見つかりません。これは、mlang.dllにタイプライブラリがないためです。よって、まず"midl.exe"を使用して"MLang.Idl"からタイプライブラリ"MLang.tlb"を作成し、これを"regtlib.exe"を使って登録します。

"midl.exe"のパスは、Visual Studio .NET 2003ならば「C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\Bin\Midl.Exe」、Visual Studio 2008ならば「C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\midl.exe」のようになるようです。

補足:midl.exeが見つからない場合は、Visual Studioのコマンドプロンプトで「where midl」と入力したり、エクスプローラの検索を使用したりして探してください。以下に紹介するツールのパスも、同様の方法で探すことができます。

"MLang.Idl"のパスは、Visual Studio .NET 2003ならば「C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Include\MLang.Idl」、Visual Studio 2008ならば「C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\MLang.Idl」のようになるようです。

次のようなコマンドを実行すれば、タイプライブラリ"MLang.tlb"がカレントディレクトリに作成されます(ディレクトリのパスは省略しています)。

midl.exe MLang.Idl

注意:midl.exeで「midl : command line error MIDL1005 : cannot find C preprocessor cl.exe」のようなエラーが出る場合は、cl.exeにパスを通すか、「コマンド ライン ビルドのパスと環境変数の設定」で説明されているように、vcvars32.bat(もしくは、Vcvarsall.bat)を実行してください。

"regtlib.exe"のパスは、Visual Studio .NET 2003ならば「C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Include\regtlib.exe」のようになるようです。Visual Studio 2005からは"regtlib.exe"の代わりに"regtlibv12.exe"を使うことができ、「C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regtlibv12.exe」のようなパスになるようです。

次のようなコマンドを実行すれば、MLang.tlbを登録できます(ディレクトリのパスは省略しています)。

regtlib.exe MLang.tlb

補足:登録を解除するときは、"-u"パラメータを付けます。

これで「参照の追加」の「COM」で"mlang.dll"(MultiLanguage Object Model)を選択できるようになります。ここで"mlang.dll"を選択すると、「参照設定」に「MultiLanguage」が追加され、RCWを提供するアセンブリ「Interop.MultiLanguage.dll」がビルド出力先のフォルダに自動的に作成されるようになります。

今までいろいろ面倒なことをやってきましたが、もし「Interop.MultiLanguage.dll」がすでにあるのであれば、COMを登録する必要はなく、「Interop.MultiLanguage.dll」を「参照設定」に追加するだけでOKです。

補足:Visual Studioを使わない(もしくは、COMを登録しない)場合は、"Tlbimp.exe"(タイプライブラリインポーター)を使ってタイプライブラリから「Interop.MultiLanguage.dll」を作成することもできます。また、tlbimp2というオープンソースのツールもあります。

DetectInputCodepageメソッドを使用する

これで、DetectInputCodepageメソッドを使う準備ができました。次にDetectInputCodepageメソッドを使って文字コードを判別するメソッドのサンプルを示します。

私が試した限りでは、このコードは64ビットでは正常に動作しないようです。よって、プロジェクトのプロパティで、「ビルド」(VBでは、「コンパイル」)タブの「プラットフォームターゲット」を「x86」とするか、「32ビットの優先」をオンにしてください。

VB.NET
コードを隠すコードを選択
''' <summary>
''' IMultiLanguage2::DetectInputCodepageを使用して、文字コードを判別する
''' </summary>
''' <param name="bytes">文字コードを調べたいデータ。</param>
''' <returns>第一候補のEncodingオブジェクト。
''' 失敗した時は、null。</returns>
Public Shared Function DetectEncoding(bytes As Byte()) As System.Text.Encoding
    'データが短い時は長くする
    If bytes.Length < 256 Then
        Dim loopCount As Integer = 256 \ bytes.Length + 1
        Dim buf As Byte() = New Byte(loopCount * bytes.Length - 1) {}
        For i As Integer = 0 To loopCount - 1
            Array.Copy(bytes, 0, buf, bytes.Length * i, bytes.Length)
        Next
        bytes = buf
    End If

    'byte型配列をsbyte型配列に変換する
    Dim sbyts As SByte() = DirectCast(DirectCast(bytes, Object), SByte())
    'Dim sbyts(bytes.Length - 1) As System.SByte
    'System.Buffer.BlockCopy(bytes, 0, sbyts, 0, bytes.Length)

    'CMultiLanguageClassを作成
    Dim ml As MultiLanguage.IMultiLanguage2 = _
        DirectCast(New MultiLanguage.CMultiLanguageClass(),  _
            MultiLanguage.IMultiLanguage2)
    If ml Is Nothing Then
        Return Nothing
    End If

    '取得する候補の数だけ、tagDetectEncodingInfo配列を作成
    'ここでは、1つだけとする
    Dim scores As Integer = 1
    Dim detects As MultiLanguage.tagDetectEncodingInfo() = _
        New MultiLanguage.tagDetectEncodingInfo(scores - 1) {}

    Dim enc As System.Text.Encoding = Nothing
    Dim len As Integer = sbyts.Length
    Try
        '文字コードを判別
        ml.DetectInputCodepage(0, 0, sbyts(0), len, detects(0), scores)
        '成功したか確認
        If 0 < scores Then
            '一番初めのtagDetectEncodingInfoのEncodingを取得
            'detectsはnDocPercentの大きい順番に並んでいる?
            enc = System.Text.Encoding.GetEncoding(CInt(detects(0).nCodePage))
        End If
    Finally
        '後始末
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(ml)
    End Try

    Return enc
End Function
End Class
C#
コードを隠すコードを選択
/// <summary>
/// IMultiLanguage2::DetectInputCodepageを使用して、文字コードを判別する
/// </summary>
/// <param name="bytes">文字コードを調べたいデータ。</param>
/// <returns>第一候補のEncodingオブジェクト。
/// 失敗した時は、null。</returns>
public static System.Text.Encoding DetectEncoding(byte[] bytes)
{
    //データが短い時は長くする
    if (bytes.Length < 256)
    {
        int loopCount = 256 / bytes.Length + 1;
        byte[] buf = new byte[loopCount * bytes.Length];
        for (int i = 0; i < loopCount; i++)
        {
            Array.Copy(bytes, 0, buf, bytes.Length * i, bytes.Length);
        }
        bytes = buf;
    }

    //byte型配列をsbyte型配列に変換する
    sbyte[] sbyts = (sbyte[])(object)bytes;
    //sbyte[] sbyts = new sbyte[bs.Length];
    //System.Buffer.BlockCopy(bs, 0, sbyts, 0, bs.Length);

    //CMultiLanguageClassを作成
    MultiLanguage.IMultiLanguage2 ml = (MultiLanguage.IMultiLanguage2)
        new MultiLanguage.CMultiLanguageClass();
    if (ml == null)
    {
        return null;
    }

    //取得する候補の数だけ、tagDetectEncodingInfo配列を作成
    //ここでは、1つだけとする
    int scores = 1;
    MultiLanguage.tagDetectEncodingInfo[] detects =
        new MultiLanguage.tagDetectEncodingInfo[scores];

    System.Text.Encoding enc = null;
    int len = sbyts.Length;
    try
    {
        //文字コードを判別
        ml.DetectInputCodepage(
            0, 0, ref sbyts[0], ref len, out detects[0], ref scores);
        //場合によっては(Visual Studio 2008以下?)、次のようになる
        //ml.DetectInputCodepage(
        //    0, 0, ref sbyts[0], ref len, ref detects[0], ref scores);
        //成功したか確認
        if (0 < scores)
        {
            //一番初めのtagDetectEncodingInfoのEncodingを取得
            //detectsはnDocPercentの大きい順番に並んでいる?
            enc = System.Text.Encoding.GetEncoding((int)detects[0].nCodePage);
        }
    }
    finally
    {
        //後始末
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(ml);
    }

    return enc;
}

注意:「相互運用機能型 'MultiLanguage.CMultiLanguageClass' を埋め込むことができません。代わりに適用可能なインターフェイスを使用してください。」というエラーが出る場合は、「参照設定」にある「MultiLanguage」のプロパティで、「相互運用機能型の埋め込み」をFalseにしてください。

scoresを1より大きくすると、DetectInputCodepageメソッドにより複数の候補が返されることがあります。候補の数はscoresに入り、その数だけdetectsの要素に入ります。候補の内どれが有力かは、tagDetectEncodingInfoのnDocPercentやnConfidenceで判断します。nDocPercentが一番高い候補がdetects[0]に入るようです。

Detect Encoding for In- and Outgoing Text」によると、判別するデータが短すぎる時は、そのデータを繰り返してでも長くした方がよいということです。私が試してもその通りでしたので、上のコードではそれをまねて、データが短い時は長くしてから判別しています。

サンプル

最後に、「Interop.MultiLanguage.dll」を同梱したサンプルのプロジェクトを置いておきます。このプロジェクトはVisual Studio 2013で作成したもので、参照設定にはCOMではなく、「Interop.MultiLanguage.dll」を追加しています。ここで使用している「Interop.MultiLanguage.dll」は、別のプロジェクトでCOMを参照設定に追加した時に作成されたものです。

  • 履歴:
  • 2005/9/8 GetCodeメソッドを修正。
  • 2008/1/26 GetCodeメソッドでEUCの補助漢字判定が間違えていたのを修正。雅階凡さんの記事を紹介。G-PROJECTさんのURLを変更。
  • 2009/1/28 TextEncクラスのURLを変更。
  • 2010/7/19 GetCodeメソッドを修正し、ASCIIを正しく判断できるようにした。「第三者の作成したクラス、コードを使う方法」に新しいリンクを追加。「mlang.dllを使う方法」をより詳しくした。また、TNKソフトウェアさんのコメントを参考に、byte[]からsbyte[]に変換する方法を変更。
  • 2011/1/31 GetCodeメソッドのドキュメントコメントの間違いを修正。
  • 2014/5/29 Marshal.ReleaseComObjectをFinalReleaseComObjectに変更。
  • 2016/8/28 「BOMで判断する方法」を追加。「mlang.dllを使う方法」の説明、サンプルを追加。「第三者の作成したクラス、コードを使う方法」のリンク切れ修正など。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。
共有する

この記事への評価

この記事へのコメント

この記事に関するコメントを投稿するには、下のボタンをクリックしてください。投稿フォームへ移動します。通常のご質問、ご意見等は掲示板へご投稿ください。