ドナドナされるプログラマのメモ

Windows用アプリのプログラミングメモ

フォルダ選択ダイアログの表示

フォルダ選択ダイアログを表示するため以前はSHBrowseForFolderを使っていたんだけど、MSDNによるとVista以降はIFileDialogを使うことが推奨されるらしい。

そこで、IFileDialogを使うよう、以下の通り実装した。

 

gistbfbc99d6b26024ca291f41e586f96900

なお、pDialog->Show(NULL)としているが、NULLのかわりに呼び出し元のhWndを指定してもよいようだ(MSDNより)。また、IFileDialogのインスタンスを示すCLSIDは存在しない?ようで、CoCreateInstanceではCLSID_FileOpenDialogを引数に指定している。

取得したインスタンスや、呼び出し先関数が確保したメモリは忘れずに開放すること。

error C2787 このオブジェクトに関連付けられた GUID はありません。

IFileDialogやIFileOpenDialogを使おうとしたら、「error C2787 このオブジェクトに関連付けられた GUID はありません。」というコンパイルエラーが出た。

原因は、元のソフトがWindowsXp以降で動作するようにしていたため。上記インターフェースは、WindowsVista以降でしか使えないのだ。

どのバージョン以降を動作対象にするか指定しているのは、stdafx.hの以下の箇所。

 

gist.github.com

 

 0x0501はWindowsXp以降を動作対象とする設定なので、これをVista以降となる0x0600に書き換えてやる。

 

gist.github.com

 

これでコンパイルが通るようになった。

グラフィックボード更新の検討

なんとなくドスパラに寄って商品眺めていたら、GTX1060か1070あたりを買いたくなった。でも、グラフィックボードの場合大きさの問題が有る。いずれも、ボードが長かったり厚かったり(2枚分からはみ出る)しているので、そもそも自宅PCに挿せるのか調べてから買うことにした。

ところで、そもそもグラフィックボードを買い換える必要があったのだろうか?そこで、今使っているGTX770と、性能比較を仕様書ベースでしてみた。

型番 GTX770 GTX1060 GTX1070
アーキテクチャ Kepler Pascal Pascal
コードネーム GK104 GP106 GP104
CUDAコア数 1536 1280 1920
コアクロック(MHz) 1046 1506 1506
メモリI/F(bit) 256 192 256
メモリ帯域(GB/s) 224.32 192 256

自分はメモリ帯域気にする派なので、GTX1060は無いな・・・。となると、GTX1070との比較だけど。コアクロック1.5倍は魅力的。でも、問題は価格。4~5万円台か・・。ちょっと保留だな。

CImageからcv::Matへの画像コピー

MFCで画像をお手軽に使えるクラスCImageから、画像処理の定番OpenCV用のクラスcv::Matへ画像をコピーするのは、一見簡単だが罠がある。CImageが保持している画像データへのポインタはCImage::GetBits()で取得できるのだが、このポインタ、データの先頭を指す場合と、最後の行の先頭を指す場合があるのだ。そのため、GetBits()で取得したポインタと画像サイズを元にmemcpyするとアクセス違反が発生しうる。

また、CImageはメモリアクセスを効率化するためか、行末にビットスタッフを行う。例えば、1行あたりのデータ量が405バイトの場合、実際には408バイトが使われる。一方、cv::Matはcreate()で作成したオブジェクトならば常に全データが連続する(ビットスタッフなし)(cv::Mat::isContinuous()のドキュメントに記載)。そのため、そもそもコピーするデータが異なっている。

以上を踏まえると、以下のようなコードが必要となる。

Critical error detected c0000374

昔作ったソフトの改造をしていたら、突然「Critical error detected c0000374」というエラーが出て止まるようになった。実行継続して例外を処理させてみたら、

「ハンドルされない例外が 0x00007FFB1E01E6FC (ntdll.dll) で発生しました(伏せ字.exe 内): 0xC0000374: ヒープは壊れています。 (パラメーター:0x00007FFB1E0722B0)。」

なんてダイアログが出てきた。ヒープ破損!?メモリ関係でやっちゃったかなあ・・・。とりあえず、stdafx.hに

#include <crtdbg.h>

を追加。そして、プログラムが停止したあたりに

_ASSERT(_CrtCheckMemory());

を仕込み、ヒープを破壊した箇所を絞り込む。・・・?なんでこんなコードで破壊が起きているんだ?

 ちょっとだけコード解説。このコードは、CImageクラスの画像をOpenCVのMatクラスにコピーしている。最初の数行で、それぞれに対し同一サイズの画像用メモリを確保し、memcpyでコピーしている。GetPitch()でmemcpyの仕方を場合分けしているのは、CImage型の変数が有する画像イメージへの生ポインタを取得するGetBits()関数が2通りのポインタを返すためだ。

CImageは、画像左上を原点とするフォーマットと左下を原点とするフォーマットをサポートする。GetPitch()やGetBits()は、ユーザーがその違いを気にしなくても済むよう、左下原点の場合はPitchとして負の値を、GetBitsの結果として左下の点に相当するメモリアドレスを返す。これにより、たとえばforループを書くときに場合分けをせずに済むようになる。しかし、この機能は今回のコードでは邪魔で、それぞれの場合用に書き分ける必要が生じる。それが、GetPitch()でmemcpyの仕方を場合分けしている理由だ。

さて、本題。同一サイズの画像をコピーしているだけなのに、なぜヒープが壊れるのだ?・・・もしかして、画像サイズは同一でも、メモリサイズは同一ではない?試しに、1ラインあたりのメモリサイズを見てみよう。CImageもcv::Matも、構造体内にデータとして持っていたはず・・・

  • CImage
    img.m_nPitch : -408
  • cv::Mat
    *tmp.step.p: 405

ビンゴでした。1ラインあたりのメモリサイズが異なっていました。CImage::GetPitch()のドキュメントにも、「The pitch can also include additional memory, reserved for the bitmap.」なんて書いてありました。これを直せば、治るかな?

艦これで備蓄しておきたい装備数

艦これ、保有装備枠きつい。でも、レア装備を廃棄するのは怖い。使うかもしれないし。

・・・本当に使うかな?使うとしたらいくつだろう?

ということで、必要な数がいくつか考察&メモ。なお、必要数はプレイスタイルに依存するので、これはあくまでも自分の場合です。

対潜水艦装備

潜水艦に対する装備は、1艦隊分あれば充分そう。連合艦隊でも、潜水艦に攻撃するのは第2戦隊だけですし。対潜装備が3枠/艦、とすると、1艦あたり水中探信儀×2、三式爆雷投射機×1かな。水中探信儀は、艦のLVが充分あると仮定すると、攻撃力の有る四式水中探信儀優先で。

ということで、必要な装備数は以下の通り。

  • 三式水中探信儀、四式水中探信儀×12
  • 三式爆雷投射機×6

水上電探

大型艦用と小型艦用で分ける。

連合艦隊でも、大型艦はせいぜい8隻。でも支援艦隊には4隻×2いるかもしれない。1隻につき1個必要とすると、合計16個。

一方、小型艦は連合艦隊だと12隻ありえそう。支援艦隊は2隻×2かな。合計・・あら、こちらも16だ。

  • 32号水上電探×16
  • 33号水上電探×16

対空電探

大型艦用と小型艦用で分ける。

連合艦隊でも、大型艦はせいぜい8隻。支援艦隊に装備することは無い。1隻につき1個必要とすると、合計8個。

一方、小型艦は連合艦隊だと12隻ありえそう。合計12。

  • FuMO、14号対空電探×8
  • 13号対空電探改×12

 艦載機

艦隊、支援艦隊には、最大でもそれぞれ空母4とする。

戦闘機は、空母4隻に各3スロ搭載、陸上基地に4スロ×4配備すると合計28機。多いな。

艦爆は、支援×2と艦隊のみ、陸上配備なしとし、艦隊は各3スロ、支援は各4スロとすると3×4+4×4×2=12+32=44機。多いな。

艦攻は、艦隊のみとして、艦隊は各3スロだと12機。リーズナブルだ。

偵察機は、連合艦隊のみに必要として、6+3=9機。

水上機は、航空戦艦と航空巡洋艦に載せるとしてもせいぜい6隻で、1隻あたり2スロとすると12機。

  • 烈風とその仲間たち×28
  • 星一二型×44
  • 流星改×12
  • 零式水上観測機×9
  • 瑞雲系×12

主砲

20cm系主砲は、連合艦隊考慮しても9隻×2スロ=18。

46cm系主砲は、連合艦隊(5隻?)とか支援を考慮するより、搭載艦考慮したほうが良さそう。せいぜい8ぐらい?

10cm連装高角砲+高射装置は、連合艦隊(12隻)と支援(2×2隻)を考えると、32欲しい。

他、色々ある気がするけど省略。

  • 20.3cm(2号)連装砲×18
  • 46cm砲×8
  • 10cm連装高角砲+高射装置×32

その他

ドラム缶について。遠征系は、 北方鼠:1個×4隻、東急1:1個×5隻、東急2:2個×5隻。連合艦隊で3スロ×6隻としても18。連合艦隊+東急1,2としても33個あればOK。

三式弾は連合艦隊考慮で9隻×1スロ=9個。

  • ドラム缶×33
  • 三式弾×9
  • 一式徹甲弾×9

とりあえずは以上かな?

OpenCVのmatchTemplateがうまく動かない!

 以下のコードが上手く動かず、しばし悩む。

まったく同じ画像のはずなのに、どうしてもminMaxLocの結果が0.85になる。なぜだ。そこで、各画像のメモリを覗いてみた。

 まずはイメージの探索対象側。イメージサイズは2×4, 32bit。0xf0f0f000 というパターンが7回続いた後に0x00000000が来ている。

f:id:donadonasan:20161022185535p:plain

次に、探索イメージ(pattern)。こちらもイメージサイズは2×4, 32bit。

f:id:donadonasan:20161022185930p:plain

なんと、こちらは0xf0f0f0ffというパターンが続き、その後0x000000ffが来ている。各ピクセルの4バイト目が、両者で食い違っていたのだ。OpenCVのmatchTemplateって、アルファチャンネルをサポートしていないから、結果に影響しないはずなんだがなあ。でも、影響するなら仕方ない。

もう少し調べてみたところ、探索対象側はMFCのCImageで作成されており、探索イメージはOpenCVにより作成していることがわかった。どうやら、両者でアルファチャンネルのデフォルト値が異なっているようだ。

そもそも、なんでアルファチャンネルを使っていないのに、使うコードになっているのだろう?元コードを書いた5年前の自分に聞いてみたいが、そんなことはできない。おそらく、CImageに関係する自分の8年ぐらい前のコードがアルファチャンネルを使っていたから、とかそんな理由だと思うが・・・。

ってわけで、ピクセル辺り32bitだったものを24bitのアルファチャンネルを使わないコードに書き換えてみた。その結果、無事minMaxLocの結果が1.0に。問題解決。