2000年5月のエントリ

Win98,起動しなくなる。なんかスタンバイしたら復帰しなくなって,再起動かけるとレジストリが破壊されたみたいで,ログイン画面前で止まる。とりあえず Linux から最新のデータをバックアップして,再インストール。

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 の値をとることができた。


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

TeX を消してしまったので,再インストール。その際フォントを作成するのが面倒(1999/10/27 日記参照)なので,どうにかして自動生成させようとする。

DVIOUT に指定する template に以下を記述。

%1st
set PATH=%PATH%;c:texDVIOUT;c:texbin
set TEXMF=c:texsharetexmf
set TEXMFCNF=.;c:texsharetexmfweb2c
c:
cd c:texsharetexmffontspkcxpubliccm
%2nd
MakeTeXPK ^s ^d ^D ^d/^D canonbjc
%3rd
copy c:texsharetexmffontspkcxpubliccm*.tfm c:texsharetexmffontstfmptex
del c:texsharetexmffontspkcxpubliccm*.tfm
del c:texsharetexmffontspkcxpubliccm*.log
del c:texsharetexmffontspkcxpubliccm*.gf

いや,絶対もっといい方法があるはずなんだけど。

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 をキャッシュ。