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

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

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

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

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

(2018/06/23追記:IFileDialogはWindowsVista以降でしか使えない。そのため、stdafx.hのWINVER等を0x600以上にする必要がある。後述。また、マルチバイト文字セットでは操作できない。Unicode必要。)

 

gistbfbc99d6b26024ca291f41e586f96900

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

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

2018/06/23追記

下記エラーが出る場合は、stdafx.hに記述した動作OS情報が間違っている可能性があります。

・__uuidofのオペランドには__declspec(uuid('...'))が指定されているクラス型または列挙型を使用する必要があります

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

解決方法は下記。解決方法調べたら、自分のページが引っかかった・・・過去にも同じことで困ったのか、自分・・・。

donadona.hatenablog.jp

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

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

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

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

 

gist.github.com

 

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

 

gist.github.com

 

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

2018/06/23追記

WINVER等とWindowsバージョンの関係は、下記に公式情報がある。

Using the Windows Headers (Windows)

英語だが、各表右上の"Value for なんとか"という部分に、どの定数に関する表か書いてある。

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

なんとなくドスパラに寄って商品眺めていたら、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に。問題解決。