第350章 nkfを使う その1


nkfとは、市川至、森和彦氏らによって開発された、漢字コード変換フィルタです。 また、海人氏により32ビット版に移植されました(もちろん市川氏らの32ビット版もある)。EUC,S-JIS,JISコード変換が非常に簡単に実現できます。ここでは、海人氏の32ビット版DLLを使った、簡単なプログラムを 作ってみます。



まずは、ベクター等よりNKF32を入手し適当なディレクトリにインストールします。ここでは、C:\nkfにインストールしてたものとします。

この中に、nkf32.h, hkf32.dll, nkf32.lib, nkf32b.lib, nkf32.docが入っています。

dllは、システムのフォルダなどパスの通っているディレクトリにコピーしておくと便利です。

ここにある、ヘッダファイルと、ライブラリファイルをそのまま使うためには、C++ではなく、Cでプログラムを書く必要があります。

さて、VC++では次のような設定をしておきます。

プロジェクトにnkf32.libを参加させます。(注:nkf32b.libはC++Builder用です) ヘッダファイルをプロジェクトのフォルダにコピーしてもいいのですが、ここでは、 ソリューション・エクスプローラを右クリックしてプロパティを選択し、C/C++ の「全般」「追加のインクルードディレクトリ」にc:\nkfを追加しておきます。



今回は、簡単のためS-JISで入力された、テキストをEUCに変換して出力する簡単な エディタを作ってみます。

// nkf01.rcの一部

/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

MYMENU MENU 
BEGIN
    POPUP "ファイル(&F)"
    BEGIN
        MENUITEM "終了(&X)...",                 IDM_END
        MENUITEM "名前を付けてEUCに変換して保存(&A)...", IDM_SAVEAS
    END
END
普通のメニューのリソース・スクリプトです。
// nkf01.c

#define ID_EDIT 100

#include <windows.h>
#include "resource.h"
#include "nkf32.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
BOOL MySaveAs(HWND, HWND);

char szClassName[] = "nkf01";    //ウィンドウクラス
HINSTANCE hInst;
ソース・ファイルの拡張子は「C」にしておきます。nkf32.hをインクルードしておきます。
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;
    
    hInst = hCurInst;

    if (!InitApp(hCurInst))
        return FALSE;
    if (!InitInstance(hCurInst, nCmdShow)) 
        return FALSE;
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            break;
        } else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

//ウィンドウ・クラスの登録

ATOM InitApp(HINSTANCE hInst)
{
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;    //プロシージャ名
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;//インスタンス
    wc.hIcon = (HICON)LoadImage(NULL,
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);
    wc.hCursor = (HCURSOR)LoadImage(NULL,
        MAKEINTRESOURCE(IDC_ARROW),
        IMAGE_CURSOR,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = "MYMENU";    //メニュー名
    wc.lpszClassName = (LPCSTR)szClassName;
    wc.hIconSm = (HICON)LoadImage(NULL,
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);

    return (RegisterClassEx(&wc));
}

//ウィンドウの生成

BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
    HWND hWnd;

    hWnd = CreateWindow(szClassName,
            "猫でもわかるnkf", //タイトルバーにこの名前が表示されます
            WS_OVERLAPPEDWINDOW, //ウィンドウの種類
            CW_USEDEFAULT,    //X座標
            CW_USEDEFAULT,    //Y座標
            CW_USEDEFAULT,    //幅
            CW_USEDEFAULT,    //高さ
            NULL, //親ウィンドウのハンドル、親を作るときはNULL
            NULL, //メニューハンドル、クラスメニューを使うときはNULL
            hInst, //インスタンスハンドル
            NULL);
    if (!hWnd)
        return FALSE;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}
いつもと同じです。現在のインスタンスハンドルをグローバル変数にコピーしておきました。
//ウィンドウプロシージャ

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    int id;
    static HWND hEdit;

    switch (msg) {
        case WM_CREATE:
            hEdit = CreateWindow("EDIT", "", 
                WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
                ES_MULTILINE | ES_AUTOHSCROLL |    ES_AUTOVSCROLL | 
                ES_WANTRETURN, 
                0, 0, 0, 0, hWnd, (HMENU)ID_EDIT, hInst, NULL);
            break;
        case WM_SIZE:
            MoveWindow(hEdit, 0, 0, LOWORD(lp), HIWORD(lp), TRUE);
            break;
        case WM_SETFOCUS:
            SetFocus(hEdit);
            break;
        case WM_COMMAND:
            switch (LOWORD(wp)) {
                case IDM_END:
                    SendMessage(hWnd, WM_CLOSE, 0, 0);
                    break;
                case IDM_SAVEAS:
                    MySaveAs(hWnd, hEdit);
                    break;
            }
            break;
        case WM_CLOSE:
            id = MessageBox(hWnd, "終了してもよろしいですか",
                    "終了確認", MB_OKCANCEL);
            if (id == IDOK) {
                DestroyWindow(hEdit);
                DestroyWindow(hWnd);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return 0;
}
メイン・ウィンドウのプロシージャです。

WM_CREATEメッセージが来たら、エディットコントロールを作っておきます。

WM_SIZEメッセージが来たら、エディットコントロールの大きさを調整して、クライアント 領域と同じ大きさにしておきます。

WM_SETFOCUSメッセージが来たら、SetFocus関数でエディットコントロールに、キーボード・フォーカスを設定します。このメッセージは、ウィンドウがキーボード・フォーカス を取得したら送られてきます。

LRESULT CALLBACK WindowProc(
  HWND hwnd,       // ウィンドウハンドル
  UINT uMsg,       // WM_SETFOCUS
  WPARAM wParam,   // ウィンドウハンドル
  LPARAM lParam    // 使用しない
);
メニューから、IDM_SAVEASが選択されたら、自作関数MySaveAsを呼びます。

BOOL MySaveAs(HWND hWnd, HWND hEdit)
{
    HLOCAL hMem;
    HANDLE hHeap, hFile;
    char *lpszBuf, *lpszOut; 
    static char szFile[MAX_PATH] = "", szFileTitle[MAX_PATH] = "";
    OPENFILENAME ofn;
    int nLen, nLenE, i;
    DWORD dwWritten;

    hMem = (HLOCAL)SendMessage(hEdit, EM_GETHANDLE, 0, 0);
    lpszBuf = (char *)LocalLock(hMem);
    LocalUnlock(hMem);

    memset(&ofn, 0, sizeof(OPENFILENAME));
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hWnd;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = MAX_PATH;
    ofn.Flags = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
    ofn.lpstrDefExt = "txt";
    ofn.lpstrFilter = "text(*.txt)\0*.txt\0All files(*.*)\0*.*\0\0";

    if (GetSaveFileName(&ofn) == 0)
        return FALSE;

    nLen = (int)strlen(lpszBuf);

    hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, (nLen + 256) * 3, 0);
    lpszOut = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, (nLen + 256) * 3);

    SetNkfOption("-Se");
    NkfConvert(lpszOut, lpszBuf);

    hFile = CreateFile(szFile, GENERIC_WRITE,
        0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        MessageBox(hWnd, "CreateFile関数失敗", "Error", MB_OK);
        HeapDestroy(hHeap);
        return FALSE;
    }

    nLenE = (int)strlen(lpszOut);
    for (i = 0; i < nLenE; i++) {
        if (lpszOut[i] == 0x0d)    {
            continue;    
        } else {
            WriteFile(hFile, &lpszOut[i], 1, &dwWritten, NULL);
        }
        if (dwWritten == 0) {
            MessageBox(hWnd, "書き込みエラーです", "Error", MB_OK);
        }

    }
    SetEndOfFile(hFile);

    CloseHandle(hFile);

    HeapDestroy(hHeap);

    return TRUE;
}
エディットコントロールの文字列を取得して、これをECUに変換して、ファイルに保存する 関数です。

エディットコントロールから文字列を取得するにはGetWindowText関数でもよいのですが、 ここでは、EM_GETHANDLEメッセージを送って、エディットコントロールのメモリハンドルを取得しています。

SendMessage( 
  (HWND) hWnd,         // ウィンドウハンドル
  EM_GETHANDLE,        
  (WPARAM) wParam,     // 使用しない(0にしておく)
  (LPARAM) lParam      // 使用しない(0にしておく)
);
成功すれば、複数行エディットコントロールが内容を保持しているメモリのハンドルが 返されます。失敗した時や、単一行エディットコントロールに送った場合は0が返されます。

メモリオブジェクトのハンドルが返されたら、LocalLock関数で、ポインタを取得できます。

LPVOID LocalLock(
  HLOCAL hMem   // ローカルメモリオブジェクトのハンドル
);
関数が失敗した時はNULLが返されます。

LocalLockしたものはLocalUnlockしなくてはいけません。

BOOL LocalUnlock(
  HLOCAL hMem   // ローカルメモリオブジェクトのハンドル
);
さて、次にGetSaveFileName関数で、保存するファイル名をユーザーに 入力させます。

次に、EUC変換のための前準備をしておきます。 まずは、strlen関数でエディットコントロールに入力されている、バイト数を求めておきます。SJISからEUCに変換した場合、サイズはほとんど変わりません。改行があると、そこで 1バイト少なくなります。

ここでは、(nLen + 1) バイト程度のメモリを出力用に確保しておけば充分でしょう。 今回は、HeapAlloc関数で動的にメモリを確保してみます。この関数を使う前に、HeapCreate関数で、使用可能なヒープオブジェクトを作成しておくことが必要です。 ヒープオブジェクトを作成した後に、HeapAlloc関数でメモリを割り当てます。

メモリを解放するにはHeapFree関数を使います。また、HeapDestroy関数で、ヒープオブジェクトを破棄することもできます。この場合、HeapFree関数を使う必要はありません。

HANDLE HeapCreate(
  DWORD flOptions,       // ヒープ割り当て方法
  SIZE_T dwInitialSize,  // 初期のヒープサイズ
  SIZE_T dwMaximumSize   // 最大ヒープサイズ
);
flOptionsには、作成したいヒープオブジェクトの属性を指定します。

HEAP_GENERATE_EXCEPTIONSは、関数が失敗した時に例外を発生させます。
HEAP_NO_SERIALIZEは、メモリの割り当てや解放を行う時、相互排他を行いません。 (普通は、指定しません)

dwInitialSizeには、ヒープの初期サイズを指定します。

dwMaximumSizeには、ヒープの最大サイズを指定します。0を指定すると拡張可能となります。

関数が成功するとヒープハンドルが返されます。

LPVOID HeapAlloc(
  HANDLE hHeap,   // ヒープブロックのハンドル
  DWORD dwFlags,  // ヒープの割り当て方法
  SIZE_T dwBytes  // 割り当てたいバイト数
);
hHeapには、ヒープハンドルを指定します。

dwFlagsには、割り当て方法のオプションを指定します。
HEAP_GENERATE_EXCEPTIONSは、関数が失敗した時に例外を発生させます。
HEAP_NO_SERIALIZEは、この関数がヒープにアクセスしている時相互排他を行いません。
HEAP_ZERO_MEMORYは、割り当てたメモリを0で初期化します。

dwBytesには、確保したいバイト数を指定します。HeapCreate関数で指定したヒープが 拡張不能の時は、0x7FFF8バイト未満の数値を指定しなくはなりません。

関数が成功すると、メモリブロックへのポインタが返されます。

メモリの準備ができたら、書き込まれた内容をEUCに変換します。

これは、至って簡単です。

SetNkfOption関数で変換オプションを指定します。

int CALLBACK SetNkfOption(LPCSTR optStr);
optStrに変換オプション文字列を指定します。詳しくは添付のドキュメントを読んでください。簡単に覚えるには、入力側は大文字、出力側は小文字を指定します。

たとえば、入力側がS-JISの場合は、"-S"、出力側がEUCの場合は"-e"となります。 これをまとめて"-Se"とすることもできます。(「小さくなって出ていく」と覚えると忘れません。)また、入力側を指定しないと自動的に 判断してくれますが、100%正しいとは言い切れないので、入力側も指定するのが 安全だと思います。

さて、オプションを設定したら、実際に変換をします。

void CALLBACK NkfConvert(LPSTR outStr, LPCSTR inStr);
outStrには、出力を受けるバッファを指定します。
inStrには、変換する文字列を指定します。
さて、この関数は改行コードの変換は行いません。ウィンドウズのS-JISでは、 通常改行は"\r\n"ですが、EUCでは"\n"です。これは、自分でやらないといけません。

ここでは、ファイルに1バイトずつ書き込み、"\r"が出てきたら、書き込まない方式を とっています。

nkfの使い方は、非常に簡単です。これを自力でやるのはかなり、面倒くさいので 非常に重宝します。この他に、BASE64や、半角全角の変換などもできます。研究して みてください。


[SDK第4部 Index] [総合Index] [Previous Chapter]

Update 05/Oct/2004 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。