環境は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;
}
No.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+にこだわる理由もないように思われますが。
いかがでしょう。
No.2
- 回答日時:
全体のソースが見れないので難ではありますが、ThreadParamsをthreadの引数として渡していますよね。
そしてそのThreadParams::imageをthreadの内部で使っています。おそらくその渡されたThreadParamsインスタンスのポインタはメインスレッドなどで用意されたものかと思われますが、アクセス侵害が起こる関数のどちらとも関係している変数が「ThreadParams::image」です。 つまりメインスレッドで用意したと思われるThreadParams内のimageの扱いになんらかの問題があると私は見ました。
メインスレッドとその子スレッド間でのそういったアクセス侵害はそれほどめずらしくないので一度お調べになられてはいかがでしょうか。
ちなみ例え質問サイトとはいえ、どのような回答が返ってこようとも悪態をとられるのはいかがなものかと。あなたは未熟なのですから。
回答ありがとうございます。
まずはサンプルプログラムへの訂正です。
済みません、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+以外のライブラリを使うか、
マルチフレーム画像はあとまわしにして一旦あきらめるか、
マルチフレーム画像を解析・分解して複数のシングルフレーム画像にすることで
マルチフレーム関係のメソッドを使用しなくていいようにするか、
みたいな選択肢も脳内にチラつきはじめました。
一応その他の方法も調査しつつ、方針が決まるまではこのトピックでの回答の受付を
継続します。
>ちなみ例え質問サイトとはいえ、どのような回答が返ってこようとも悪態をとられるのはいかがなものかと。あなたは未熟なのですから。
質問した日(=回答にレスした日)は一日中バグ探しやった後で脳みそがぐるんぐるんしていたんですが、
次の日に質問内容とソースコードと私の返信を見直してあまりの痛さに愕然としました。
このひどい内容ではあの回答が返ってきても文句は言えないなと。(これがずっと残ってしまうのがつらい・・・。)
申し訳ありませんでした。調子にのらないように気をつけます。
No.1
- 回答日時:
アクセス違反エラーなので、確保したPNGデータ領域よりも大きいアドレスを描画処理化何かで読みにいってしまっているというような感じではないでしょうか。上のコードは煩雑なので読んでいませんが、大体画像データサイズをプログラマが把握せずに、描画系関数呼び出しでサイズ指定の間違いとかでよく起こるエラーではあります。
ご参考までに・・・
この回答への補足
>アクセス違反エラーなので、確保したPNGデータ領域よりも大きいアドレスを描画処理化何かで読みにいってしまっているというような感じではないでしょうか。
そらそうでしょうよ。
>上のコードは煩雑なので読んでいませんが、
ソース読んでないのに回答しないでくださいよ。何の参考にもならないです。
>大体画像データサイズをプログラマが把握せずに、描画系関数呼び出しでサイズ指定の間違いとかでよく起こるエラーではあります。
>ご参考までに・・・
その可能性はふつうに真っ先に考えるところですよね。
GDI+はIStreamをコンストラクタに与えてやればオブジェクトを作ってくれるんで
サイズ指定の間違いだとするとファイル読み込みとか、IStreamを作るあたりなんでしょうが、
アニメーションじゃないPNGは読めたり、アニメPNGでもファイルによって
ちゃんと動いたりエラーが出たりするんでどうもそのあたりじゃない気がするんですよね。
どっちにしてもこのソースも100回ぐらい読んで自分では気付けないでいるんで
どこか間違っているんだとしたら具体的に指摘して欲しいです。
自分の感触としてはGDI+オブジェクトに何かの指定をしてやらなければならないのを
していないとか、そういう間違いの可能性が高いのかなと考えてます(が、わからないです)。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- JavaScript clear機能を失わずにファイルアップロード機能を作成したい 3 2023/06/10 16:12
- PHP imageフォルダに、画像をリサイズして保存する時のファイル名を変更したい 1 2023/05/30 11:39
- JavaScript プログラムがうまく動きませんレビューお願いします 1 2022/07/10 05:08
- JavaScript コードレビューをお願いします。 1 2022/07/16 05:38
- Visual Basic(VBA) Excel vbaについての質問 3 2023/04/18 16:14
- JavaScript 画像の表示位置 3 2022/12/23 08:25
- FX・外国為替取引 mql4のコンパイルエラー箇所の修正お願いします。 1 2023/03/15 16:14
- JavaScript アップロードファイルの種類によって処理を分岐させたいのですが書き方が分からずアドバイスお願いします 4 2023/06/17 19:12
- PHP PHP MySql 画像を取得 1 2022/06/04 14:05
- C言語・C++・C# c言語の問題の説明、各所ごとに 5 2023/07/26 11:03
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
DOSのdirコマンドで思うように...
-
UTF-8で5~6バイトになる文字コ...
-
Excel VBA メール作成について ...
-
10Mバイトて文字数に すると何...
-
char str[256]の256の意味は?
-
バイト列とバイナリ列の違いが...
-
Excel 1セル当りの文字数が2...
-
エクセルシート名の制限を変更...
-
ビットスワップとバイトスワッ...
-
URLは最高何文字まで可能なので...
-
COBOLのCOMP形式について
-
「1TB」のHDDに日本語は何字入...
-
ポインター引数の関数でコンパ...
-
バイナリとBCDコード
-
DataGridViewの特定列に入力さ...
-
Javaで日本語1文字のバイト数
-
windowsのファイルパス最大文字数
-
なんでブラウザでPHPを動かすた...
-
この関数はどのプログラミング...
-
VBAでUnicodeしか存在しない文...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
DOSのdirコマンドで思うように...
-
JavaScriptの条件分岐について(...
-
デバイスコンテキストに書いた...
-
C言語でwin32apiを使ってnotepa...
-
PNGを24bppBMPに変換したいので...
-
python についての質問です
-
エラー「書き込み中にアクセス...
-
このプログラムはどういった事...
-
segmentation fault
-
UTF-8で5~6バイトになる文字コ...
-
10Mバイトて文字数に すると何...
-
Excel VBA メール作成について ...
-
stable diffusionのエラー
-
DataGridViewの特定列に入力さ...
-
エクセルシート名の制限を変更...
-
なんでブラウザでPHPを動かすた...
-
COBOLのCOMP形式について
-
ポインター引数の関数でコンパ...
-
char str[256]の256の意味は?
-
メールの件名をデコードしたい
おすすめ情報