プログラム読み物
Lastupdate: 4/4 .2006


メイン
about...
自作ソフト
ファイラ
プログラム
昔の記事
リンク



プログラミングTipな話を用意しました。

といってもまだネタが少ないですが、気が向いたら読んでみてください。:-)
普段プログラムを組んでいて「これは地雷だな...」と感じたネタを中心に用意していきたいと思います。
あと、使用言語は、VisualC++のSDKが中心の話になっています。

●アーカイバーDLLの パラメータについて   (4/4.2006)
アーカイバーDLL(Unlha32.dll、Unzip32.dll、zip32j.dll、cab32.dll)が公開されており、これらを使うと.lzh、.zip、.cabの解凍、圧縮ができるようになります。
ありがたいことに、仕様が共通になるように作られているので 意外と簡単にDLLへアクセスすることができるのですが、なぜかコマンドパラメータの仕様に差があったりして制御に困ることがありました。特に圧縮時のレスポンスファイルについてはサンプルが書いてなくてなかなかうまく動作せず苦労しました。
そこで、うまく動いた時のパラメータを後述しておきますので、ノウハウとして参考にしてください。 ただし、コレが正解かどうかは不明です。(^-^;;
解凍時のパラメータ
例として、D:\G.lzhを、c:\temp\に解凍します。
.lzh
Unlha(hwnd,"x \"D:\\G.lzh\" \"c:\\temp\\\"",NULL,0);
.zip
Unzip(hwnd,"-x \"D:\\G.zip\" \"c:\\temp\\\"",NULL,0);
.cab
Cab(hwnd,"-x \"D:\\G.cab\" \"c:\\temp\\\"",NULL,0);

圧縮時のパラメータ
P:の¥上にある、フォルダ"G"、フォルダ"ayaseeds"、ファイル"10 Book you off.wma"の3つを圧縮する例です。

レスポンスファイルを指定する際の、@と"の位置が肝。 レスポンスファイルの中身は、後述します。
.lzh
Unlha(hwnd,"a \"D:\\test1.lzh\"  @\"D:\\response1.txt\"",NULL,NULL);
.zip
Zip(hwnd,"\"D:\\test2.zip\"  @\"D:\\response2.txt\"",NULL,NULL);
.cab
Cab(hwnd,"-a -r \"D:\\test3.cab\" \"P:\\\"  \"@D:\\response3.txt\"",NULL,NULL);

レスポンスファイルの中身
response1.txtの中身
(.lzh用レスポンスファイル)
"P:\" "G" -xr2
"P:\" "ayaseeds" -xr2
"P:\" "10 Book you off.wma" -xr2
response2.txtの中身
(.zip用レスポンスファイル)
-r "P:\" "G"
-r "P:\" "ayaseeds"
-r "P:\" "10 Book you off.wma"
response3.txtの中身
(.cab用レスポンスファイル)
"G\"
"ayaseeds\"
"10 Book you off.wma"
cab用レスポンスファイルで、フォルダの末尾に¥マークを付けるのが肝。




●ソートの、昇順 / 降順 について   (10/22.2002)
今回は、ソート方法で気づいた話です。

一般的な話になりますが、プログラムでソートをする時には、

値A と 値B を 比較して、「小さかったら」上に移動する。
という感じに、数値を比較し、どんどん順番を入れ替える作業を行っているかと思います。

で、今度は「この並び方の順番を「昇順」で行っていたのを、「降順」に変更したいなぁ〜。」という場合は、どうしたらいいでしょうか?
僕は普通に、

値A と 値B を 比較して、「大きかったら」上に移動する。
という感じに、「比較の条件」を変更すればいいんかなぁ〜と安直に考えていました。
(自己弁護になりますが、普通なら、そう考えちゃいますよね〜。(^^;;

ただ、この考え方はバブルソートなどのシンプルなアルゴリズムではOKな話なのですが、高速化の為にアルゴリズムやら比較条件が複雑になってくると、「小さかったら」 「大きかったら」 に置き換える作業が、かなり面倒な作業になってきます。
さらに、例えば以下のように、ファイラなどではソートの種類も複数(処理A,B,C,D)用意していたりしますが、そんな時にも、処理A’,B’,C’,D’という感じに、それぞれ別々に処理を加工して追加する必要があります。

処理Aファイル名で、昇順にソート処理A’ファイル名で、降順にソート

処理B拡張子で、昇順にソート処理B’拡張子で、降順にソート

処理Cサイズで、昇順にソート処理C’サイズで、降順にソート

処理D日付で、昇順にソート処理D’日付で、降順にソート

もちろんA,B,C,DからA’,B’,C’,D’へ、まともにコピペするとコードのサイズが単純に2倍になってしまうので、比較部分だけをフラグでちまちま分岐してコードを減らしてみたりするんですが、そうすると、余分な分岐命令が増えて実行速度に影響したり、ソースがスパゲッティ化したりしてメンテナンス性が低下したりする恐れがあります。

と、まぁ、そんな事を考えると降順のソート機能を用意するのには、かなり時間と労力がかかりそうなので実装をためらっていました。
#一言でいうと「面倒くさかった。」ということでね... (^^;;

ところが半年以上たってから、ふと「昇順でソートした結果を、先頭と末尾を逆転させれば、降順になるなー」 ということに、今ごろになって気が付きました。 (^^;; 遅すぎ?
つまり 「降順でソートする」 ではなくて、 「昇順でソートした結果を、後で降順に変換する」 にすると楽でいいなぁ〜。という結論です。
この「変換する」という作業は、実際には以下のような数行のコードを追加するだけで済んでしまいました。あっという間でした。

以下ソースの抜粋。
void ff_FTO2::sort_reverse(void){
/// ソートした結果の上下を反転させます。
DWORD i,k;
   if(num<2)return;  ///safety
   for(i=0,k=num-1; i<k; i++,k--)ve_swap(i,k);
}
この方法なら、いろんな種類のソートが用意してあっても共通で利用できるのでプログラムサイズ的にも効果的だし、実行速度的なコストも大して増えない無いし、昇順だけ改良すればいいのでメンテナンス性もバッチリだし、、、という風にめでたくスッキリ解決した雰囲気でよかったです。(^-^)
#やっぱ「プログラミングはアイデア次第なんだなぁ〜」と改めて思いました。(^^;




●キー入力フォーカスを持っているwindowを調べる方法について   (11/25.2001)
みなさん、キー入力フォーカスを持っているwindowを調べる方法をご存知でしょうか?
(それ以前になぜキー入力フォーカスを持っている窓を調べる必要があるかというと、キー入力情報をWindowsメッセージ(WM_〜系)から取得しないで直接キーボードの状態を監視する方法を採用すると、自分のソフトが非Activeの時もキーボードの状態が分かってしまいます。そこで、自分自身にキー入力フォーカスが向いている時だけキー入力処理をするようにするには、どぉしても窓の状態を知る必要がでてきます。
)
僕は、てっきり『アクティブなwindow』 == 『キー入力フォーカスを持つwindow』だと考えていたので、通常は GetActiveWindow()とか、WM_ACTIVE系のメッセージを監視するなどして、アクティブなWindowを調べていました。
で、しばらくこの方法で実装をしていたのですが、たまに「自分自身のWindowはちゃんとActiveになっているけど、キー入力フォーカスは別のwindowに合っている、、、」という困った現象がWindows2000でおきることに気が付きました。Σ( ̄O ̄;)

この現象は、恐らくWin2000以降採用された SystemParametersInfo の「SPI_GETFOREGROUNDLOCKTIMEOUT」という項目が悪さしている気がしてならないのですが、関連性や回避方法が不明です。

で、『アクティブなwindow』 != 『キー入力フォーカスを持つwindow』だと言う事実だけが分かり、しばらく途方にくれていたのですが(イヤだけど英語の)ヘルプを検索しまくったらイイ関数がありました!!
 GetGUIThreadInfo(); という関数です。 コレを使えば一発で色んなWindowの状態を調べることができるようで、その中の情報から「誰がキー入力を持っているのか」が分かるようです。 もっと安直で分かりやすい名前の(GetKeyboardFocus()って感じの)ファンクションを用意しておいて欲しかったな〜...(^^;)
以下にコレを使ったサンプルを載せておきます。
HWND G_window::Get_hwnd_keyboard(void){
/// キー入力の権利を持っている窓をGetします。
/// win98以降、and NT4(SP3)以降に対応。
GUITHREADINFO a;
DWORD i; i=sizeof(a); ZeroMemory(&a,i); a.cbSize=i;
BOOL  b; b=GetGUIThreadInfo(NULL,&a); if(b==0)return NULL;
   return a.hwndFocus;  ///Handle to the window that has the keyboard focus.
}

WM_CHARとか使わずにキー入力操作を行うアプリを作成される方はご参照ください。...ってそんなことをする人には滅多にいないですね。(^^;



●Windowの最大化前の位置の取得について   (6/17.2001)
みなさん、最大化表示しているWindowの最大化前の位置やサイズはどうやって取得していますか?

僕は GetWindowPlacement( ) ってのを使って調べてます。(↓こんな感じ)
WINDOWPLACEMENT a;
int i; i=sizeof(WINDOWPLACEMENT); ZeroMemory(&a,i); a.length=i;
   GetWindowPlacement(h,&a);
   r=a.rcNormalPosition;   ///最大化前の領域

(他の方法としてWM_SIZEとかで最大化する前にGetWindowRectとかで調べてどっかに保存しようかと考えたんですが、WM_SIZEでサイズを調べると既に最大化した時のサイズしか取得できないのでボツになりました。)
「とりあえずGetWindowPlacement( )で調べられるので、いいか...」と気にせずに使っていたんですが、これって実はドキュメントには書かれていない伏兵的仕様?が含まれていました。どうやらこの関数は、GetWindowRect()などのようにWindowsでの絶対的な座標を返す関数とは違って、最大化前の位置をデスクトップ画面との相対座標を返す仕様になっているようです。

普通に使っている分には特に問題無さげに動いてくれるんですが、僕の環境ではこの値を普通に使用するとwindowの位置がどんどん左側に移動してしまう現象が起きてしまいました。よくよく調べてみると、GetWindowPlacement( )で調べた座標が左へなぜかずれていました。
そうなんです、僕はいつもタスクバーを左横に立てて使用しているのですが、
(こんな感じの画面↓)
問題が起きるケース









・デスクトップの基点



デスクトップ領域

問題ない普通のケース
・デスクトップの基点



デスクトップ領域



タスクバー
この画面のように、Windows画面の絶対的な座標と、デスクトップ画面の左上の基点座標がずれてしまっていることに原因があったのです。
つまり、このGetWindowPlacement( ) を使う時は、タスクバーが上側とか左側にある時のようにデスクトップ領域の基点がずれている環境を考慮して座標の補正して使用する必要があるようです。

たぶんいないと思いますが、密かに「タスクバーが下にある時は問題ないけど、上や左側にあると、窓の位置が移動してしまう、、、」という方はご確認ください、、、:-)




●デスクトップのクライアント領域の取得について   (4/9.2001)
デスクトップのクライアント領域を取得するには、ふつーはどうするんでしょうか?
僕はよくわからなかったので、以下のようなコードで頑張って取得してました。(^^;;
つまり、デスクトップそのもののハンドルを取得して、それから領域を取得してました。
void Get_desktop_rect(G_rect &r){
DWORD i,j;
G_str s;
HWND  h=GetDesktopWindow();
   for(j=0,h=GetWindow(h,GW_CHILD),r.init();;){
      if(!h)break; //safety
      for(;;){ ///デスクトップな窓を探します。
         i=GetWindowLong(h,GWL_STYLE); if(!(i&WS_VISIBLE))break;
         GetWindowText(h,s,256); if(s!="Program Manager")break;
         GetClassName(h,s,256); if(s!="Progman")break;
         h=GetWindow(h,GW_CHILD); h=GetWindow(h,GW_CHILD); j=1; break; ///あった。
      }
      if(j)break; h=GetWindow(h,GW_HWNDNEXT);
   }
   if(h)GetWindowRect(h,r);
}

で、ふと、ある時知ってしまったんですが、実は、たった一行で済むんですね〜。(T-T)無駄な苦労をしてしまった...
void Get_desktop_rect(G_rect &r){
   SystemParametersInfo(SPI_GETWORKAREA,0,r,0);
}

たぶんいないと思いますが、密かに同じ事やってしまっている人はお試しください、、、