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

Dena’s Image,Pha-jtd 氏の助言により,

  • サポートされてないファイルを開いたときに落ちるバグを修正
  • 「画像サイズにウィンドウを合わせる」メニューを追加

バイト先の MK 氏に触発され,Dena’s Image の最新版を公開。このソフト,いきなり MDI 化したらバグが出まくってたため公開は見送っていたのだが,氏から「減色すると落ちるよ」という指摘を頂いたため,急いでバグを直し,公開してみた。MK 氏にはこの場を借りてお礼を申し上げたい。

あとくまのおえかきアルバムで,アルバムファイルを開くときに BMP を開くと落ちるという指摘を頂いた。で,修正しようと思ったら,当時のソースの一部は既に新たなソースに生まれ変わっていて,コンパイルすら出来ない状態になっていた。ので,当分このままかも。

XML4C を使って XML を出力することを試みる。が,XMLPlatformUtils::Initialize で,「XML4C Panic Error」というエラーで終了してしまう。で,トレースしてみると,エラーコード U_FILE_ACCESS_ERROR というエラーを返している。

結局,Unicode 変換用データベース icudata.dll がない,ということだった。何か,VC のデバッグ用作業ディレクトリに入れといただけじゃダメで実行ファイルのあるディレクトリに置かないと認識してくれなかった。まあいいや。

VC++ MFC で作ったアプリケーション。Form View でコントロールをはると,Ctrl+C などのショートカットを何故か受け付けてくれない。以下のように自分でメッセージをとばして一応解決はした。

void CXXXView::OnEditCopy() 
{
    GetFocus()->PostMessage(WM_COPY);
}

brpuzzle Java アプレット・アプリケーション版を公開


ライブラリのリンクで

fatal error LNK1202: "xxxvc60.pdb" には参照モジュールに対するデバッグ情報がありません

と言われる。これはライブラリを作成したときの vc60.pdb を削除していたからだが,ライブラリのプロジェクトの設定で [C/C++]-[デバッグ情報] を「行番号のみ」等 pdb を使わない設定にしておけば vc60.pdb がいらなくなる。

Java で jar ファイルをダブルクリックで起動する方法。

Main-Class: mainclass

のようなファイルを作成し,

jar cvfm app.jar manifest *.class

のように jar ファイルを作成する。

KVM。Graphics.drawBorder で思うように描画されない。いろいろ試してみると,このメソッドの引数の,幅,高さはそれぞれボーダーをいれない大きさらしいことが分かった。


Java。今までコンパイル時に -J-Djavac.pipe.output=true を使ってエラー出力をファイルに出力していたが,JDK1.3 になってできなくなったらしい。別アプリでパイプ入出力を介してやるしかないんですかね。

brpuzzle をとりあえず公開してみる。


J2ME でのコンパイル方法。

set PATH=%PATH%;c:jdk1.3bin;c:j2me_cldcbin
set CLASSPATH=.;c:j2me_cldcbinapiclasses;c:j2me_cldctoolspalmclasses
javac name.java
preverify -d . name
java palm.database.MakePalmApp -bootclasspath c:j2me_cldcbinapiclasses -icon name_32.bmp -smallicon name_16.bmp -creator NAME name

こんな感じ。

15 パズルを Palm + KVM で作る。PalmIIIc に付いてくる 15 パズルでは Brandish で鍛えた私には暇つぶしにもならないので 4~100 パズルを作成したわけだが,やはり C で作るのと違って簡単にできた。

「Palm で動く電卓を作ろう計画」を立ち上げていたのだが,挫折。

  • デストラクタや throw を使うとリンクエラーが出たりする。
  • 構造体にクラスを入れるとリンクエラーが出る。
  • sin,cos などが標準では未サポート。
  • double がきちんと使えない。
  • CW Lite のエディタが変。

などが理由。もちろん単に自分が使い方を知らないだけというのもあるだろうし,いくつかはライブラリを使えば解決するものもあるが,そのライブラリもなんか…ということで。

標準ライブラリなしの C 言語と API だけで開発することもできるが,作ってて全然楽しくない。ということで,はっぱ氏お勧めの KVM もやってみようという感じ。

VS に VSS のプロジェクトを挿入するときに,VSS と関連づけてくれない場合がある。その時はプロジェクトの挿入ダイアログの,「ソースコード管理のプロジェクトを開く」で追加すればいい。

Code Warrior Lite for Palm,デストラクタを作ると delete がない,というリンクエラーが出る。デストラクタを定義しなければいいんだけど…。うーん。

今まで j-sky 用の Web ページ(MML)の情報がなかなかなかったような気がするが,いろいろと公開されてきたようだ。特に j-phone の絵文字の仕様がやっと公開されていてよかった。

ちなみに EzWeb には「マイデッキエディター」,「マイデッキエディター Pro」という公式ソフトがある。これはなかなか素晴らしいソフトで(以下略)。


はっぱ氏がある SDK を使っていたが,へッダ内に以下のような記述が。

#define Stop 0xFC
#define Active 0xFE

こんなよく使うものを define するとは,とはっぱ氏はお怒りであった。

はっぱ氏作の Unix/Win 用ソケットラッパーを使う。FTP オープンができずに困ったが,アプリケーションの最初で AfxSocketInit() を行うことで解決。

CCalc をバージョンアップ。


F1 キーでのヘルプを出ないようにする方法。CWinApp::WinHelp() をオーバーライドする。


コンボボックスでの Enter キーを取得する方法。今まで コンボボックス内のエディットボックスをサブクラス化し,WM_USER を発行して以下のように取得していた。

WM_CHAR->WM_USER->処理関数

だが,よく調べてみると,WM_CHAR の部分が呼ばれてなく,何故か 1024 番(=WM_USER)のメッセージが直接とんできて結果的に処理されていただけだった。コンボボックスで Enter が押されたときに MFC が勝手に 1024 番のメッセージをとばしているようだ。

で,気持ちが悪いので改善。CComboBox から派生したクラスを作り,その中の PreCreateWindow メッセージ内で処理するようにした。これでうまくいって,さらに,今まで Ctrl+Shift+H で勝手にバックスペースになってしまう,となっていたのも回避することができるようになった。

i-mode C-HTML。フォームの URL に何も書かないと,IE5.0 ではそのディレクトリのインデックスを表示するが,i-mode ではボタンを押しても反応しない。

STL。ある vector に他の型の vector の要素を insert しようする。

vector<A> a;
vector<B> b;
b.insert(b.end(), a.begin(), a.end());

こんな感じだが,VC++ ではコンパイルエラーになった。これは多分メンバ関数テンプレートを完全にサポートしてないからなワケだが,なんとかならないのか。

ある Web ページで占いをやっていたのでやってみる。生年月を選んでスロットを止め,そしてボタンを押す。すると,見慣れたものが! というか Perl のソースが表示された。なんかの設定が変なんだろうが,これって 1 回でもテストすれば分かるのでは…。

VC で Resource View を追加する方法。

リソースを追加し,.rc を保存。で,[プロジェクト]-[プロジェクトへ追加]-[ファイル] で .rc と resource.h を追加する。

static なコールバック関数を呼ぶ。が,動作がおかしい。いろいろ調べてみたところ,基底クラスと派生クラスで同じ名前のメソッドを作っていた。

XML4C を使う。この中に XMLString をローカルな文字列に変換する関数,transcode というのがあるのだが,この関数は内部で new した文字列を返す。当然後で delete しなくてはならないのだが,ここで問題が発生。XML4C のライブラリは DLL になっているので,DLL で new したメモリを exe 側で delete しなくてはならず,デバッグ版(だけ?)ではメモリ管理の不整合が起きて assertion されてしまう。

結局こんな感じで無理矢理解決。

DOMString dom_str("aaa");
const XMLCh *xml_ch = dom_str.rawBuffer();
char local_str[255];
XMLString::transcode(xml_ch, local_str, dom_str.length());

STL。vector::back() でコンパイルエラー。これは begin(),end() と違って iterator じゃなく reference を返すのか。

ワーカースレッドからスレッドの作成元の CDialog::UpdateData() を呼び出したりすると Assert が発生する。これは CWnd のポインタが複数スレッドでの共有を許していないからで,PostMessage とかでウィンドウハンドルを使ったメッセージ経由でやりとりすれば大丈夫。

メモリリーク。

AAA *p = NULL;

try
{
    AAA *p = new AAA;

    // 例外を throw する関数呼び出し
    func_with_exception();

    delete p;
}
catch(exception &)
{
    if(p != NULL)
    {
        delete p
    }
}

つまり p というポインタを例外ハンドラで削除しようということだが,p が try ブロックで再定義されている。ので,例外ハンドラで delete しようとする p は当然外の p なので,必ず NULL って感じで。2 日くらい気付かなかった。

VC のデバッガのクイックウォッチ等に独自文字列を入れる方法。

例えば CString では中身の文字列が,CPoint では x と y が表示されるが,これらは実は visual studio ディレクトリ内にある autoexp.dat に指定されている。ヘルプにも載っているが,これを編集すれば自分で作ったクラス等で好きな文字列を表示させられる。

これが java だったら toString メソッドを実行させて表示,とかもっときれいにできるんだろうなぁ。

ダイアログ内でのエディットボックスでの改行。そのままだとリターンが押された時点でデフォルトボタンが押されてしまうので,エディットボックスプロパティの [スタイル]-[改行を許可] をチェック。

ユーザー定義メッセージの作り方

  1. メッセージを必要とするクラスに const static UINT のメッセージ用メンバ変数を用意。
  2. メッセージ番号を ::RegisterWindowMessage(“XXX”); で初期化。
  3. LRESULT OnXXX(WPARAM wParam, LPARAM lParam); を .h のメッセージマップに追加。
  4. ON_REGISTERED_MESSAGE(XXX, OnFilterStart); を .cpp のメッセージマップに追加。

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


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