プロが教える店舗&オフィスのセキュリティ対策術

環境はWinXP SP3 と VC++2010です。

あるウィンドウにアニメーションPNGを表示したいと考えているのですが、

Gdiplus::Image::SelectActiveFrame()やGdiplus::Image::DrawImage()のタイミングで
メモリアクセス違反(0xC0000005)となり、困っています。

ファイルによって大丈夫だったりエラーになったりします。
アニメじゃない画像の場合も正常に動きます。

エラーにならないアニメPNG:https://si0.twimg.com/profile_images/1256677017/ …
エラーになるアニメPNG:https://si0.twimg.com/profile_images/1256677017/ …

この問題とは別のようで、
低い頻度で、開発中のアプリの別の部分で実際にメモリが破壊されたようなバグり方をします。
http://support.microsoft.com/kb/961889/ja

コードは以下です。
元のコードは長いので検証用に小さくまとめました。
このコードで同様のバグの再現を確認しています。

WM_CREATEで_threadstartexでスレッドを立ち上げ
WM_DELETEでThreaadParam::finをtrueにしてスレッドの終了を待機ます。

/***************************************************
* ここから
**************************************************/
struct ThreadParams{
HWND hWnd;
bool fin;
IStream* stream;
Gdiplus::Image* image;
ThreadParams(HWND hWnd):hWnd(hWnd),fin(false),stream(NULL),image(NULL){}
};
bool InputFile(const TCHAR* infile,void** buf,DWORD* size){
DWORD filesize;
HANDLE hFile = CreateFile(infile,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if(hFile!=INVALID_HANDLE_VALUE){
filesize = GetFileSize(hFile,NULL);
*buf = new UCHAR[filesize+1];
// 読み込み
ReadFile(hFile,*buf,filesize,size,NULL);
FlushFileBuffers(hFile);
// 末尾NULL
(*((UCHAR**)buf))[filesize] = 0;
// ファイルハンドルの開放
CloseHandle(hFile);
return true;
}else{
*buf=NULL;
*size=0;
return false;
}
}

bool CreateByteStream(IStream** stream){
if(stream == NULL){
returnfalse;
}
HRESULThr = CreateStreamOnHGlobal(NULL,TRUE,stream);
if(SUCCEEDED(hr)){
returntrue;
}else{
*stream = NULL;
returnfalse;
}
}

bool CreateByteStream(IStream** stream,const void* buf,DWORD size){
if(CreateByteStream(stream)){
if(buf!=NULL){
DWORD wsize;
(*stream)->Write(buf,size,&wsize);
if(size==wsize){
return true;
}
}
}
if(*stream!=NULL){
(*stream)->Release();
*stream = NULL;
}
return false;
}

bool CreateByteStream(IStream** stream,const TCHAR* filename){
bool ret = false;
UCHAR *buf;
DWORD fsize;
if(InputFile(filename,(void**)&buf,&fsize)){
if(CreateByteStream(stream,buf,fsize)){
ret = true;
}
delete[] buf;
}
return ret;
}

static UINT GetFrameCount(Gdiplus::Image* image){
UINT count = image->GetFrameDimensionsCount();
GUID* pDimensionIDs = new GUID[count];

image->GetFrameDimensionsList(pDimensionIDs, count);
count = image->GetFrameCount(&pDimensionIDs[0]);

delete [] pDimensionIDs;
return count;
}

unsigned int __stdcall thread(void* _params){
ThreadParams* params = (ThreadParams*)_params;

HDC hdc = GetDC(params->hWnd);

IStream* ist = NULL;
CreateByteStream(&ist,IMAGE_PATH);

Gdiplus::Graphics g(hdc);
Gdiplus::Image *image = new Gdiplus::Image(ist);

params->stream = ist;
params->image = image;

// GUID
Gdiplus::PropertyItem* pItem = NULL;
Gdiplus::Status status = Gdiplus::Ok;

// フレーム数
UINT frm_cnt = GetFrameCount(image);

// アニメ
if(frm_cnt>1){
// 時間間隔
UINT pItemSize = image->GetPropertyItemSize(PropertyTagFrameDelay);

pItem = (Gdiplus::PropertyItem*)(new BYTE[pItemSize]);
status = image->GetPropertyItem(PropertyTagFrameDelay, pItemSize, pItem);

UINT w = image->GetWidth();
UINT h = image->GetHeight();

Gdiplus::SolidBrush brush(Gdiplus::Color(255,255,255));

for(UINT frm=0;!params->fin;frm++){
if(frm>=frm_cnt)frm = 0;

// Frame切り替え
Gdiplus::Status status = image->SelectActiveFrame(&Gdiplus::FrameDimensionTime, frm);

for(int i=0;status==Gdiplus::ObjectBusy;i++){
Sleep(10);
status = image->SelectActiveFrame(&Gdiplus::FrameDimensionTime, frm);
}

// 描画
g.FillRectangle(&brush,0,0,w,h);
g.DrawImage(image,0,0);

// Sleep
Sleep(((long*)pItem->value)[frm] * 10);
}
}
// 非アニメ
else{
// 描画
g.DrawImage(image,0,0);
}

if(ist!=NULL)ist->Release();
ReleaseDC(params->hWnd,hdc);

return 0;
}

A 回答 (3件)

www



>>Among all the image formats currently supported by GDI+, the only formats that support multiple-frame images are >>GIF and TIFF.
>>When you call the Image::SelectActiveFrame method on a GIF image, you should use FrameDimensionTime.
>>When you call the Image::SelectActiveFrame method on a TIFF image, you should use FrameDimensionPage.

MSDNで上のように書いてあったのなら単純にPNGアニメは使えないってことですね。
これで解決ではありませんか?w
私はGIFアニメもTIFFアニメも使ずGDI+も使わないのであれですが、本当にそれは必要なのですか?

下位互換性やDirectDraw又はDirect2D非対応環境にも対処するという必要性がないのであれば、いまどきGDI+にこだわる理由もないように思われますが。
いかがでしょう。
    • good
    • 0
この回答へのお礼

そうですね。この質問はここで一旦クローズします。
お付き合いいただきありがとうございました。

お礼日時:2011/07/20 02:44

全体のソースが見れないので難ではありますが、ThreadParamsをthreadの引数として渡していますよね。

そしてそのThreadParams::imageをthreadの内部で使っています。

おそらくその渡されたThreadParamsインスタンスのポインタはメインスレッドなどで用意されたものかと思われますが、アクセス侵害が起こる関数のどちらとも関係している変数が「ThreadParams::image」です。 つまりメインスレッドで用意したと思われるThreadParams内のimageの扱いになんらかの問題があると私は見ました。

メインスレッドとその子スレッド間でのそういったアクセス侵害はそれほどめずらしくないので一度お調べになられてはいかがでしょうか。

ちなみ例え質問サイトとはいえ、どのような回答が返ってこようとも悪態をとられるのはいかがなものかと。あなたは未熟なのですから。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

まずはサンプルプログラムへの訂正です。

済みません、ThreadParamsにImageとIStreamがありますが、
試行錯誤の末に残ってしまった消し忘れで本来要らない(使っていない)ものです。

struct ThreadParams{
HWND hWnd;
bool fin;
ThreadParams(HWND hWnd):hWnd(hWnd),fin(false){}
};

ThreaadParamsの定義は以上で十分です。

処理本体はthread()で、必要なファンクションはGetFrameCount()のみです。

thread()内で

>IStream* ist = NULL;
>CreateByteStream(&ist,IMAGE_PATH);
>
>Gdiplus::Graphics g(hdc);
>Gdiplus::Image *image = new Gdiplus::Image(ist);

という箇所がありますが、この箇所は

>Gdiplus::Graphics g(hdc);
>Gdiplus::Image *image = new Gdiplus::Image(IMAGE_PATH);

と、Gdiplus::Imageのコンストラクタにファイル名を直接指定しても同様の動きをします。
つまり3つのCreateByteStream()とInputFile()ファンクションは使用しなくていいことになります。
(のでファイル読み込み時のサイズ指定ミスという可能性が消えました)

ここから、回答への返答なんですが

Imageオブジェクトを使用しているのはthread()内部のみなので異なるスレッドから同時にアクセスすることによって起こるアクセス侵害という可能性は低いかなと思います。
(でもその場合はDCに対する操作が複数のスレッドから行われることになるので
そこでアクセス違反という可能性は考えられるかもしれませんが)

試しにthread()の処理を、別スレッドで処理する方法をやめてWM_PAINTで処理する方法
(WM_PAINT内で無限ループになるのでメインスレッドをロックしてしまうのですが)
でやったみたんですが、やはり同様の原因と思われるアクセス違反が発生しました。

後、いろいろ検証しているうちにわかった事があるので意味があるかどうかわかりませんが書いておきます。

1.Gdiplus::Image::SelectActiveFrame()メソッドで、GIFアニメの何枚目を対象に
操作を行うか、ということを設定するんですが、引数でn枚目を指定した場合
n>1の場合に、n+1回のメモリアクセス違反メッセージが連続して出力されます。
この場合「メモリ破壊っぽいバグり方」はまだ確認していません。

2.静止画像に対してGdiplus::Image::SelectActiveFrame()メソッドを実行した場合、
その場合は必ず1フレーム目を指定するんですが、20~30回ぐらい実行したあたりで
「heap corrupted」というメッセージが出てその後確実に「メモリ破壊っぽいバグり方」が発生します。
メソッド実行は様々な画像フォーマットの別々のImageオブジェクトに対して、各1度ずつ行われました。

これに関してはMSDNの以下の記述に違反しているのが原因かもしれません。
2.に関しては対処可能なのでこのトピックスの質問内容には静止画像は無関係とします。(もともとそうですが)
http://msdn.microsoft.com/en-us/library/ms535402 …
Among all the image formats currently supported by GDI+, the only formats that support multiple-frame images are GIF and TIFF.
When you call the Image::SelectActiveFrame method on a GIF image, you should use FrameDimensionTime.
When you call the Image::SelectActiveFrame method on a TIFF image, you should use FrameDimensionPage.

メモリ破壊っぽいバグり方とは、
1.他の領域で保持されているメモリ内容が破壊されているっぽい(画面の文字や画像が化けたり消えたりする)
2.プログラムが停止する、または入力を受付なくなる
ようなバグりかたです。

GDI+でサポートされているマルチフレーム画像はGIFとTIFFだけなんですね。
マルチフレームのPNGも使えるようにしたいのでGDI+以外のライブラリを使うか、
マルチフレーム画像はあとまわしにして一旦あきらめるか、
マルチフレーム画像を解析・分解して複数のシングルフレーム画像にすることで
マルチフレーム関係のメソッドを使用しなくていいようにするか、
みたいな選択肢も脳内にチラつきはじめました。

一応その他の方法も調査しつつ、方針が決まるまではこのトピックでの回答の受付を
継続します。

>ちなみ例え質問サイトとはいえ、どのような回答が返ってこようとも悪態をとられるのはいかがなものかと。あなたは未熟なのですから。

質問した日(=回答にレスした日)は一日中バグ探しやった後で脳みそがぐるんぐるんしていたんですが、
次の日に質問内容とソースコードと私の返信を見直してあまりの痛さに愕然としました。
このひどい内容ではあの回答が返ってきても文句は言えないなと。(これがずっと残ってしまうのがつらい・・・。)
申し訳ありませんでした。調子にのらないように気をつけます。

お礼日時:2011/07/19 00:42

http://code.msdn.microsoft.com/ERROR-CODE-tips-9 …

アクセス違反エラーなので、確保したPNGデータ領域よりも大きいアドレスを描画処理化何かで読みにいってしまっているというような感じではないでしょうか。上のコードは煩雑なので読んでいませんが、大体画像データサイズをプログラマが把握せずに、描画系関数呼び出しでサイズ指定の間違いとかでよく起こるエラーではあります。
ご参考までに・・・

この回答への補足

>アクセス違反エラーなので、確保したPNGデータ領域よりも大きいアドレスを描画処理化何かで読みにいってしまっているというような感じではないでしょうか。

そらそうでしょうよ。

>上のコードは煩雑なので読んでいませんが、

ソース読んでないのに回答しないでくださいよ。何の参考にもならないです。

>大体画像データサイズをプログラマが把握せずに、描画系関数呼び出しでサイズ指定の間違いとかでよく起こるエラーではあります。
>ご参考までに・・・

その可能性はふつうに真っ先に考えるところですよね。
GDI+はIStreamをコンストラクタに与えてやればオブジェクトを作ってくれるんで
サイズ指定の間違いだとするとファイル読み込みとか、IStreamを作るあたりなんでしょうが、
アニメーションじゃないPNGは読めたり、アニメPNGでもファイルによって
ちゃんと動いたりエラーが出たりするんでどうもそのあたりじゃない気がするんですよね。

どっちにしてもこのソースも100回ぐらい読んで自分では気付けないでいるんで
どこか間違っているんだとしたら具体的に指摘して欲しいです。

自分の感触としてはGDI+オブジェクトに何かの指定をしてやらなければならないのを
していないとか、そういう間違いの可能性が高いのかなと考えてます(が、わからないです)。

補足日時:2011/07/17 01:03
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!