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には、出力を受けるバッファを指定します。
ここでは、ファイルに1バイトずつ書き込み、"\r"が出てきたら、書き込まない方式を とっています。
nkfの使い方は、非常に簡単です。これを自力でやるのはかなり、面倒くさいので 非常に重宝します。この他に、BASE64や、半角全角の変換などもできます。研究して みてください。
Update 05/Oct/2004 By Y.Kumei