‘プログラミング’のエントリ

EUC 用漢字 1 バイト目 2 バイト目認識。

iskanji1(unsigned int c)
{
    return (c >= 0xa1 && c <= 0xfe);
}
iskanji2(unsigned int c)
{
    return (c >= 0xa1 && c <= 0xfe);
}

と書いていたが,これだと半角カナがきたときに,SS2 の次のバイトを漢字 1 バイト目だと認識してしまう。とりあえず SS2 を漢字 1 バイト目だと認識するように修正。

GCC。string::compare だが,

int compare (const basic_string& str, size_type pos, size_type n) const;
int compare (const charT* s, size_type pos, size_type n) const;

の 2 つの挙動が違う。というか後者の場合うまく動かない。これも詳しく調べてないけど前者なら期待通りに動いてるのでまあいいか,とか。

GCC。B 様の調査によると,STL を使う場合,__STL_USE_NAMESPACES を指定した方がよい場合があるらしい。例えば iterator とかを単独で使う場合などに,これがないと iterator が定義されなくなってしまうらしいが…。

1999/09/14「コンソールアプリを,DOS 窓なしで GUI から呼び出す。」だが,この方法だとデータを得られない,という読者さんからのご指摘を頂く。読んでくれた人がいるというだけでもありがたいが,メールまで頂くと非常に嬉しい。

以下,頂いたサンプル。(見やすくするためと簡略化のため,改行や出力先等,多少変更してあります。)

// コマンドライン
LPTSTR lpCommandLine;
lpCommandLine = "xxx.exe";

// 標準出力用パイプ
HANDLE hReadPipe = NULL, hWritePipe = NULL;

// エラー出力用パイプ
HANDLE hErrReadPipe = NULL, hErrWritePipe = NULL;

// セキュリティ属性(ハンドル継承を指定)
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;

// 標準出力パイプの作成
::CreatePipe(&hReadPipe, &hWritePipe, &sa, 8192);

// エラー出力パイプの作成
::CreatePipe(&hErrReadPipe, &hErrWritePipe, &sa, 8192);

STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;

::ZeroMemory(&StartupInfo, sizeof(STARTUPINFO));
StartupInfo.cb = sizeof(STARTUPINFO);

// ハンドルの継承を指定
StartupInfo.dwFlags = STARTF_USESTDHANDLES;

// DOS窓を表示しない
StartupInfo.wShowWindow = SW_HIDE;

// 標準出力ハンドルとエラー出力ハンドルを設定
StartupInfo.hStdOutput = hWritePipe;
StartupInfo.hStdError  = hErrWritePipe;

// コンソールアプリ起動
if (::CreateProcess(
    NULL,
    lpCommandLine,
    NULL,
    NULL,
    TRUE,    // ハンドルの継承
    DETACHED_PROCESS, // DOS窓を表示しないための指定
    NULL,
    NULL,
    &StartupInfo, &ProcessInfo))
{
    // パイプ内容受け取り用バッファ
    char bufStdOut[8192], bufErrOut[8192];
    DWORD dwStdOut = 0, dwErrOut = 0;
    DWORD dwRet;

    // プロセス起動中のパイプ内容受け取り処理
    while ( (dwRet = ::WaitForSingleObject(ProcessInfo.hProcess, 0)) !=
        WAIT_ABANDONED)
    {
        memset(bufStdOut, 0, sizeof(bufStdOut));
        memset(bufErrOut, 0, sizeof(bufErrOut));

        // 標準出力パイプの内容を調べる
        ::PeekNamedPipe(hReadPipe, NULL, 0, NULL, &dwStdOut, NULL);
        if (dwStdOut > 0)
        {
             // 内容が存在すれば、読み取る
             ::ReadFile(hReadPipe, bufStdOut, sizeof(bufStdOut) - 1, &dwStdOut,
                NULL);
             // bufStdOut にパイプ出力が入る
        }

        // 同様にエラー出力の処理
        ::PeekNamedPipe(hErrReadPipe, NULL, 0, NULL, &dwErrOut, NULL);
        if (dwErrOut > 0)
        {
             ::ReadFile(hErrReadPipe, bufErrOut, sizeof(bufErrOut) - 1, &dwErrOut,
                NULL);
             // bufErrOut にエラー出力が入る
        }

        // メッセージキューを取得し、存在すれば、処理を促す
        MSG msg;
        if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
             ::TranslateMessage(&msg);
             ::DispatchMessage(&msg);
        }
        // プロセス終了なら、ループを抜ける
        if (dwRet == WAIT_OBJECT_0)
             break;
    }

    // プロセスハンドルとスレッドハンドルを閉じる
    DWORD res;
    ::GetExitCodeProcess(ProcessInfo.hProcess, &res);
    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);
}

// すべてのパイプを閉じる
::CloseHandle(hWritePipe);
::CloseHandle(hReadPipe);
::CloseHandle(hErrWritePipe);
::CloseHandle(hErrReadPipe);

修正個所としては,

  • CreateProcess の bInheritHandles,dwCreationFlags の値。
  • 全体的なエラーチェック。

というところ。

# ちなみに 1999/09/14 では Win98 で動いているのを確認しているので,多分 NT 等では動かなかったのでは…と思う。いずれにしろ上のコードならば大丈夫でしょう。

# 2006/07/10 CreateProcess第3引数にsaを渡さないようにした。将来プロセスハンドルを子に継承させるならsaを渡すのだが,この目的では不要。


その後読者さんとは話がスクロールバーの話題へ。スクロールバーを MFC で使っていると,OnVScroll() の SB_THUMBPOSITION,SB_THUMBTRACK において,nPos が 16bit を越えると負の値となる。また,MFC のヘルプには

nMinPos と nMaxPos で指定された値の差は、32、767以下でなければいけません。

ということが書いてあったので,てっきりスクロールバーは 16bit しか扱えないんだと思っていたが,これは WM_VSCROLL が nPos を 16bit しか渡してくれないから,ということだ。(nPos = (short int) HIWORD(wParam); となっている。)

読者さんの指摘通り,OnVScroll() 内で GetScrollInfo() を直接呼び出し,スクロール位置を得ることで 32bit の値をとることができた。


いや,いつも身内だけでやってるのでこういうのはいいですね。上記以外でも,もしこの日記を読んだ方がいて,何か変だと思うことがあったら是非知らせて欲しいです。

Visual Studio のエディタ。左余白をクリックすると 1 行選択されるが,そのまま Shift を押しながら上の行の左余白をクリックして 1 行選択していくと下にも 1 行ずつ選択範囲が拡張されていく。

ってそんな使い方する人いないか。


ステータスバー。前に使ったことはあるが,日記には書いてなくてどうやるか悩んだのでここに書いておく。

  • String リソースに indicators の ID を追加
  • CMainFrame の indicators に ID を追加
  • View 等に コマンドのハンドラを作成

IME の使用。

void CXXXView::OnImeStartComposition(WPARAM wParam, LPARAM lParam)
{
    COMPOSITIONFORM cpf;
    HIMC hIMC;

    // ウィンドウハンドルを取得
    HWND hWnd = GetSafeHwnd();
    if(!hWnd)
    {
        return;
    }

    // IME のコンテキストを得る
    hIMC = ::ImmGetContext(hWnd);
    if(!hIMC)
    {
        return;
    }

    // CompositionWinow
    cpf.dwStyle = CFS_POINT;
    cpf.ptCurrentPos.x = GetCaretPos().x;
    cpf.ptCurrentPos.y = GetCaretPos().y;

    // フォント
    LOGFONT logFont;
    m_font.GetLogFont(&logFont);

    ::ImmSetCompositionFont(hIMC, &logFont);
    ::ImmSetCompositionWindow(hIMC, &cpf);

    // IME のコンテキストを解放
    ::ImmReleaseContext(hWnd, hIMC);

    // デフォルトプロシージャ
    DefWindowProc(WM_IME_STARTCOMPOSITION, wParam, lParam);
}

void CXXXView::OnImeChar(WPARAM wParam, LPARAM lParam)
{
    // 何もしない
}

void CXXXView::OnImeComposition(WPARAM wParam, LPARAM lParam)
{
    if(lParam & GCS_RESULTSTR)
    {
        COMPOSITIONFORM cpf;
        HIMC hIMC;

        // ウィンドウハンドルを取得
        HWND hWnd = GetSafeHwnd();
        if(!hWnd)
        {
            return;
        }

        // IME のコンテキストを得る
        hIMC = ::ImmGetContext(hWnd);
        if(!hIMC)
        {
            return;
        }

        // 文字列の取得
        int nSize = ::ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);
        char *buf = new char[nSize + 1];
        ::ImmGetCompositionString(hIMC, GCS_RESULTSTR, buf, nSize);
        buf[nSize] = '';

        // IME のコンテキストを解放
        ::ImmReleaseContext(hWnd, hIMC);

        // 入力
        Input(buf);
        delete [] buf;

        // ウィンドウの移動
        cpf.dwStyle = CFS_POINT;
        cpf.ptCurrentPos.x = GetCaretPos().x;
        cpf.ptCurrentPos.y = GetCaretPos().y;

        ::ImmSetCompositionWindow(hIMC, &cpf);
    }

    // デフォルトプロシージャ
    DefWindowProc(WM_IME_COMPOSITION, wParam, lParam);
}

ドラッグアンドドロップ。前エクスプローラへのドロップ,ショートカットへのドロップができなかったが,できるようになった。

エクスプローラへのドロップは,DROPFILES にパラメータとファイル名を設定すればよい。(以前うまくいかなったんだが,今回はできた。)

// DROPFILES のサイズを計算
int nSize = sizeof(DROPFILES);
nSize += 1000;  // 適当に

// DROPFILES を作成
HGLOBAL hData = ::GlobalAlloc(GPTR, nSize);
if(hData == NULL)
{
throw cant_cache();
}
DROPFILES *pDropFiles = (DROPFILES *)::GlobalLock(hData);
memset((void *)pDropFiles, NULL, nSize);
pDropFiles->pFiles = sizeof(DROPFILES);
pDropFiles->pt = CPoint(0, 0);
pDropFiles->fNC = FALSE;
pDropFiles->fWide = FALSE;

// ファイル名をセット
char *pFileName = (char *)pDropFiles + sizeof(DROPFILES);
// ( 区切りでコピー)
*pFileName = '';

// キャッシュ
::GlobalUnlock(hData);                                                 
pDataSource->CacheGlobalData(CF_HDROP, hData);

ショートカットへのドロップは Shell IDList Array 形式をサポートすればいいのだが,ちょっと長いし私も完全に理解しているわけではないので手順だけ。

  • デスクトップフォルダの IShellFolder へのポインタを得る
  • ITEMIDLIST を作成。その際最初のデータがフォルダへの ITEMIDLIST で,次のデータからがファイルへの ITEMIDLIST。
  • CIDA を作る。
  • CIDA,ITEMIDLIST をキャッシュ。

Install Shield3 だが,コンパイルしようとすると

Unable to get current directory

というエラーがでる。どうやらディレクトリ名が長すぎるようなので,ルートディレクトリとかでやったらうまくいった。

CButton にビットマップを貼り付けていたのだが,Windows2000 で見ると背景色がダイアログの背景と違う。Win98 と Win2000 で微妙に色が変わっていた訳だが,結局

CBitmap::LoadBitmap() で CButton に CButton::SetBitmap()

していたのを

CBitmap::LoadMappedBitmap() (引数はデフォルト)

に変えることで背景がきちんと対応するシステムカラーに置き換えられた。

(とりあえず 16 色ビットマップだけしか試してないが。)


CWnd 派生クラスで WM_CHAR が送られてこないで困る。実際には BackSpase とかを押すと警告が鳴る。詳しくは調べてないが,結局 MFC が WM_CHAR をとっているからで,PreTranslateMessage で WM_CHAR を横取ることで解決。

でも昔 CView でやったときは大丈夫だったのだが。

プリコンパイル済みヘッダ。自分で作ったヘッダをプリコンパイル済みヘッダに指定すると,ビルド中に「プリコンパイル済みヘッダー ファイルではありません。」と言われる。

で,StdAfx.h を使う版のものを参考にして,プロジェクト自体の設定も,「プリコンパイル済みヘッダファイル(.pch)を使用」にすることでコンパイルできた。

参考までにプリコンパイル済みヘッダの使用方法。

  • 変更されることが少ないヘッダを選び,そのヘッダを a.h,それをインクルードしてるファイルの代表 1 つを a.cpp とする。
  • プロジェクトの設定で,プロジェクトの [c/c++]-[プリコンパイル済みヘッダ] の設定を「プリコンパイル済みヘッダファイル(.pch)を使用」にし,a.h を指定。
  • a.cpp の設定を「プリコンパイル済みヘッダファイル(.pch)を作成」とし,a.h を指定。
  • a.cpp 以外で a.h をインクルードしている cpp ファイルの設定を「プリコンパイル済みヘッダファイル(.pch)を使用」とし,a.h を指定。

以上で完了し,a.cpp 以外のコンパイルが高速になる。

デバッグのアウトプットに例外が表示される。そこで

  • デバッグ中に [デバッグ]-[例外処理] でダイアログを出す
  • Microsoft C++ Exception を常に停止させる

として例外で停止させる。ここで原因は判明(単純な配列範囲例外)したのだが,その後のコードが

catch(...)

となっていた。結局,せっかく処理系が例外を発生させているのに全ての例外が catch されてしまって分からなくなっていたのだ。やはりめんどうでも exception の派生クラスを定義するなどした方がいいようだ。

メモリリークでかなり悩む。昔の誰かのソースを移植していたのだが,

strdup

が原因だった。この関数は文字列をコピーするが,勝手に malloc するらしい。

Java で 4 バイトを数値として書き込もうとするが,どうもうまくいかない。入出力でエンコーディングを変換しているようなので,Writer を使ってバイナリを読み書きしようというのがまずいのか。OutputStream でやればいいのだが。

list 使用時に,list 要素のコピーコンストラクタがないとのエラーが出る。コピーコンストラクタはあるはずなのに…と調べていると,コピーコンストラクタの引数が const になっていなかった。

unsigned int i = strtol("0x80000000", &p, 16);

とやって,i が 0x7fffffff になる。正解は strtoul。

PostgreSQL ODBC ドライバ。VB からアクセスしているが,どうも書き込めない。Access でもできなかったのだが,ドライバの設定が読みとり専用になっていた。デフォルトが書き込み禁止なのか。覚えとかないと。

というかなんかぐちゃぐちゃな感じが。昨日 C で C++ っぽくプログラムを組んでそのソースを Perl で TeX 形式にして Pha-jtd 氏の HTML を添削したりして,今日 VB と SQL と C++ をやってるワケで,VB で ‘文字列’ とかやるし。ワケ分からん。


DLL の登録場所。

HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionSharedDLLs

VB で小数点以下 n 桁四捨五入。なんか方法があるのかも知れないが,ヘルプで見つからなかったので。

Int(a * 100 + 0.5) / 100

#その後の情報によると Format を使うらしい。

しばらく Visual Basic をやっているが,C に戻ってみると,

if(a = 1)

とやってしまう。ちなみにやりたいのは ==。

メンバに string を入れると他の string が破壊されるという現象に悩まされる。こんな怪現象にはリビルドだ,とか思ってリビルドしても直らない。結局 1 日悩んだが,インクルードパスに古いバージョンが残っていて,ファイルごとにインクルードするファイルのバージョンが違うということだった。ふう。

そういえば書いてなかったような気がするが,HTML Help Workshop は起動するたびに *.log を関連づけてくれる。こんな拡張子をしかもほとんど関係ないソフトがとるってのはどうだろう。Word の Doc 以上ですな。

Pha-jtd 氏から,CGI が動かないとメールが届く。原因は#!/usr/local/bin/perl#!/user/local/bin/perlと書いていたから。

#ってここで書いていいですか > Pha-jtd 様

大学のレポートプログラムでコンパイルエラー。何故か for を使うとエラーになるというものだが,for 文の中でローカル変数を使っていたからだった。

課題自体は「C 言語」で簡単な計算機を作るというものだが,スタック作るのに template を使えないので

#define name2(x, y) x##y
#define declare(x, y) name2(x, declare)(y)
#define implement(x, y) name2(x, implement)(y)

とかやって実装したりして。ていうか C++ か Java くらい教えるようにして欲しいけど > 某大学情報工学科。

Perl。引数を処理しようとしてうまく行かない。

if(@argv < 5)
{
}

とかやっていたが,引数を表すのは @ARGV つまり大文字だった。

ふとある .cpp を見てみると,

#ifndef XXX_CPP
#define XXX_CPP

とか書いてあった。いつの間にこんなの書いたのか。


TeX で表に番号とキャプションを付ける。tabular を begin{table} end{table} で囲み,label と caption を付ける。はずだが,うまく出ない。参照すると節番号になってしまう。でネットをあさって原因が判明。label は caption の後に書くらしい。確かに既にうまくいってる「図」はそうなっている。で,そのようにして XXX.aux を消し,再コンパイルすると見事に正常に表示された。

# でも TeX でこういう順番気にしなきゃいけない事なんてなかったからなあ。普通気にしないような。

ある 2 つのビューがあり,A はマウスのクリックでマウスをキャプチャーする。B はフォーカスがなくなった時にダイアログを出すとする。(B にエディットボックスがあり,そこで編集を行うが,フォーカスがなくなったら値チェックを行う,など。もっと分かりやすい例は後で。)

ここで,B で編集をし,そのまま A をクリックするとどうなるかという問題である。この場合,

  • B のフォーカスが失われる
  • ダイアログが出現し,OK ボタンを押す
  • A に WM_LBUTTONDOWN メッセージが来て,マウスがキャプチャーされる

という具合になるのだが,ここで問題が生じる。A の WM_LBUTTONUP が呼ばれないのだ。よってマウスのリリースができず,困った状況になる。で,解決するために結構悩んだんだが,そういえばエクスプローラも同じ事をやっている。以下の手順で試してみて欲しい。

  • ツリービューでフォルダ名を編集し,* とかにする
  • 編集中のままリストビューをクリック
  • エラーが出るので,OK。

さて,どうなっただろうか。答えは「マウスボタンは離しているのにラバーバンドが現れる」だ。なあんだ。エクスプローラもできてないのか。

もう 1 つエクスプローラの問題。同様に

  • リストビューでファイル名を編集し,* とかにする
  • 編集中のまま真ん中の境界線をクリックし,素早くサイズ変更
  • エラーが出るので,OK。

さて,今度はどうなっただろうか。境界を右に動かした方はリストビューが欠けてしまい,逆に左に動かした方は白い変な領域ができたと思う。これは誰も気付かなかったバグなのか,それとも対応不可能なバグなのか。ちなみに私が試したのは Win98 だけなので他ではどうなるか分からない。

#後で試したところ,NT も同様だった。

Install Sield,ファイルコピー中のファイル名で,カタカナのア・カ行辺りが文字化けする。

Enable(INDVFILESTATUS);

を消し,表示そのものを行わないようにする。

いつか hr みたいな線の作り方を書いたけど,それよりよさそうな方法。

  • ピクチャーコントロールを貼り付ける
  • スタイルのくぼみをチェックする

これだけ。

MFC 利用スタティックライブラリで

定義されていない基本クラス

とか言われる。StdAfx.h に

#include <afxext.h>

を追加。


複数のプロジェクトを扱う時,他のプロジェクトを変更したときにリコンパイルする方法。プロジェクトの依存関係で依存するプロジェクトをチェック。


libjpeg を入れる。

  1. VC98bin にある Vcvars32.bat を実行。
  2. jconfig.vc を jconfig.h に,makefile.vc を makefile に直す。
  3. nmake でデバッグ版が完成。
  4. namke nodebug=1 でリリース版が完成。

ちなみに環境変数の領域が足りない場合は config.sys に

shell=c:command.com /e:4096 /p

を追加。(前書いたっけ?)

修正追加。内部コードを EUC に変更したので,ismbblead,ismbbtrail(自作)を EUC にした。

VC++ + SQL Server で作っていたクラス群を Linux + Postgres へ移植。Turbo Linux 4.0 の gcc とかなり違っていて大変だが,今回修正が必要だった部分を書いておく。以下,gcc と書いたら Turbo Linux 4.0 付属のものとする(多分現時点で最新だと思う)。

  1. VC では ios_base があるが,gcc では古い ios しかない。
  2. ソースファイルは EUC にしないと変なエラーが出ることがある。
  3. stringstream がない。代わりに古い(?)strstream がある。ちなみに istrstream のコンストラクタは string ではなく char * をとる。
  4. basic_string::compare(size_type, size_type, const string &) がない。これは const string & が最初の引数になっていた。
  5. VC では <cxxx> が std 名前空間になっていないが,gcc ではちゃんと std 名前空間になっている。
  6. stream の ! 演算子がない(* もかな?)。good() などで代用。
  7. VC の stricmp は gcc では strcasecmp になっている。
  8. VC の itoa がない(と思う)。
  9. <iostream> がなく,<iostream.h>。

全体的には,コンパイラは gcc の方が標準に準拠しているが,標準 C++ ライブラリ,STL は VC++ の方が新しいという感じ。てことは STL だけ Linux でコンパイルすれば最高の環境になるのか,な。どうだろ。

あるウィンドウで IME 入力を禁止する方法

ImmAssociateContext

で,第 1 引数をハンドル,第 2 引数を NULL にして呼び出す。