问题描述
我有一个动画 GIF,我正在尝试显示它,但我无法让它工作。它不会运行并在行 0xC0000005: Access violation reading location 0x80000B00.
处抛出异常 SetTimer(hWnd,ID_TIMER,((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10,(TIMERPROC)NULL);
。
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WParaM wParam,LParaM lParam) {
Image* m_pImage = new Image(L"spinner.gif");
UINT m_iCurrentFrame = 0;
UINT m_FrameCount = 0;
PropertyItem* m_pItem = 0;
switch(message) {
...
case WM_TIMER: {
//Because there will be a new delay value
KillTimer(hWnd,ID_TIMER);
//Change Active frame
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid,m_iCurrentFrame);
//New timer
SetTimer(hWnd,NULL);
//Again move to the next
m_iCurrentFrame = (++m_iCurrentFrame) % m_FrameCount;
InvalidateRect(hWnd,NULL,FALSE);
break;
}
case WM_PAINT:{
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd,&ps);
Graphics graphics(hdc);
//First of all we should get the number of frame dimensions
//Images considered by GDI+ as:
//frames[animation_frame_index][how_many_animation];
UINT count = m_pImage->GetFrameDimensionsCount();
//Now we should get the identifiers for the frame dimensions
GUID* m_pDimensionIDs = new GUID[count];
m_pImage->GetFrameDimensionsList(m_pDimensionIDs,count);
//For gif image,we only care about animation set#0
WCHAR strGuid[40];
StringFromGUID2(m_pDimensionIDs[0],strGuid,40);
m_FrameCount = m_pImage->GetFrameCount(&m_pDimensionIDs[0]);
//PropertyTagFrameDelay is a pre-defined identifier
//to present frame-delays by GDI+
UINT TotalBuffer = m_pImage->GetPropertyItemSize(PropertyTagFrameDelay);
m_pItem = (PropertyItem*)malloc(TotalBuffer);
m_pImage->GetPropertyItem(PropertyTagFrameDelay,TotalBuffer,m_pItem);
//Set Current Frame at #0
m_iCurrentFrame = 0;
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid,m_iCurrentFrame);
//Use Timer
//NOTE HERE: frame-delay values should be multiply by 10
SetTimer(hWnd,(TIMERPROC)NULL);
//Move to the next frame
++m_iCurrentFrame;
InvalidateRect(hWnd,FALSE);
//Finally simply draw
graphics.DrawImage(m_pImage,120,100,100);
EndPaint(hWnd,&ps);
break;
}
...
}
return DefWindowProc(hWnd,message,wParam,lParam);
}
我发现的大多数示例都是 WCF 或函数折旧。
解决方法
解决方案:
您可以将 m_pItem
设置为全局。除了此错误之外,您还有一些其他错误。例如,m_iCurrentFrame
在 WM_PAINT
中始终初始化为 0。
而且我们只需要加载一次 GIF 图片文件,不需要把它放在 WM_PAINT
中。
GIF 循环播放,
#include <memory>
#include <vector>
#include <algorithm>
#include <windows.h>
#include <objidl.h>
#include <GdiPlus.h>
#include <gdiplusimaging.h>
#include <tchar.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
#define TIMER_ID 101
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);
int WINAPI _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow)
{
ULONG_PTR m_gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&m_gdiplusToken,&gdiplusStartupInput,NULL);
Image gif(_T("spinner.gif"));
MSG msg = { 0 };
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = NULL; // <= Do not provide a background brush.
wc.lpszClassName = L"anim_gif_player";
if (!RegisterClass(&wc))
return -1;
if (!CreateWindow(wc.lpszClassName,L"Animated GIF player",WS_OVERLAPPEDWINDOW | WS_VISIBLE,1080,800,hInstance,&gif))
return -2;
while (GetMessage(&msg,NULL,0) > 0)
DispatchMessage(&msg);
return 0;
}
std::vector<unsigned int> LoadGifFrameInfo(Image* image)
{
// I think animated gifs will always only have 1 frame dimension...
// the "dimension" being the frame count,but I could be wrong about this
int count = image->GetFrameDimensionsCount();
if (count != 1)
return std::vector<unsigned int>();
GUID guid;
if (image->GetFrameDimensionsList(&guid,1) != 0)
return std::vector<unsigned int>();
int frame_count = image->GetFrameCount(&guid);
auto sz = image->GetPropertyItemSize(PropertyTagFrameDelay);
if (sz == 0)
return std::vector<unsigned int>();
// copy the frame delay property into the buffer backing an std::vector
// of bytes and then get a pointer to its value,which will be an array of
// unsigned ints
std::vector<unsigned char> buffer(sz);
PropertyItem* property_item = reinterpret_cast<PropertyItem*>(&buffer[0]);
image->GetPropertyItem(PropertyTagFrameDelay,sz,property_item);
unsigned int* frame_delay_array = (unsigned int*)property_item[0].value;
// copy the delay values into an std::vector while converting to milliseconds.
std::vector<unsigned int> frame_delays(frame_count);
std::transform(frame_delay_array,frame_delay_array + frame_count,frame_delays.begin(),[](unsigned int n) {return n * 10; }
);
return frame_delays;
}
void GenerateFrame(Bitmap* bmp,Image* gif)
{
Graphics dest(bmp);
SolidBrush white(Color::White);
dest.FillRectangle(&white,bmp->GetWidth(),bmp->GetHeight());
if (gif)
dest.DrawImage(gif,0);
}
std::unique_ptr<Bitmap> CreateBackBuffer(HWND hWnd)
{
RECT r;
GetClientRect(hWnd,&r);
return std::make_unique<Bitmap>(r.right - r.left,r.bottom - r.top);
}
LRESULT CALLBACK WndProc(HWND hWnd,LPARAM lParam)
{
static Image* animated_gif;
static std::unique_ptr<Bitmap> back_buffer;
static std::vector<unsigned int> frame_delays;
static int current_frame;
switch (message) {
case WM_CREATE: {
animated_gif = reinterpret_cast<Image*>(
reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams
);
if (!animated_gif || animated_gif->GetLastStatus() != 0) {
MessageBox(hWnd,_T("Unable to load animated gif"),_T("error"),MB_ICONERROR);
return 0;
}
// Create a bitmap the size of the window's clent area
back_buffer = CreateBackBuffer(hWnd);
// get the frame delays and thereby test that this is really an animated gif
frame_delays = LoadGifFrameInfo(animated_gif);
if (frame_delays.empty()) {
MessageBox(hWnd,_T("Invalid gif or not an animated gif"),MB_ICONERROR);
return 0;
}
current_frame = 0;
animated_gif->SelectActiveFrame(&FrameDimensionTime,current_frame);
GenerateFrame(back_buffer.get(),animated_gif);
SetTimer(hWnd,TIMER_ID,frame_delays[0],nullptr);
InvalidateRect(hWnd,nullptr,FALSE);
}
break;
case WM_TIMER: {
KillTimer(hWnd,TIMER_ID);
current_frame = (current_frame + 1) % frame_delays.size();
animated_gif->SelectActiveFrame(&FrameDimensionTime,current_frame);
GenerateFrame(back_buffer.get(),animated_gif);
SetTimer(hWnd,frame_delays[current_frame],FALSE);
} break;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd,&ps);
Graphics g(hdc);
g.DrawImage(back_buffer.get(),0);
EndPaint(hWnd,&ps);
} break;
case WM_SIZE: {
back_buffer = CreateBackBuffer(hWnd);
GenerateFrame(back_buffer.get(),animated_gif);
} break;
case WM_CLOSE:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd,message,wParam,lParam);
}
return 0;
}
,
根据评论部分的阅读,似乎发生了第 0xC0000005: Access violation reading location 0x80000B00.
行的错误 SetTimer(hWnd,ID_TIMER,((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10,(TIMERPROC)NULL);
,因为 GIF 仅包含一帧。
例如,您可以通过仅在超过一帧时调用计时器来避免这种情况;
if(m_FrameCount > 1){
//Continue calling the timer.
}
这是另一个例子;
/** These are global **/
Image* m_pImage;
GUID* m_pDimensionIDs;
UINT m_FrameCount;
PropertyItem* m_pItem;
UINT m_iCurrentFrame;
UINT_PTR timer_id = 123456; //set your own timer id
BOOL m_bIsPlaying;
BOOL isPlayable;
/** Functions **/
void LoadImage() {
m_pImage = Image::FromFile(L"C:\\Users\\username\\location_of_file\\spinner.gif");
UINT count = m_pImage->GetFrameDimensionsCount();
m_pDimensionIDs = new GUID[count];
m_pImage->GetFrameDimensionsList(m_pDimensionIDs,count);
WCHAR strGuid[39];
(void)StringFromGUID2(m_pDimensionIDs[0],strGuid,39);
m_FrameCount = m_pImage->GetFrameCount(&m_pDimensionIDs[0]);
if(m_FrameCount > 1) { //frame of GIF is more than one,all good,we don't want the error of `Access violation reading location`
isPlayable = TRUE;
OutputDebugString(_T("Image file contains more than 1 frame,its playable.\n"));
}
UINT TotalBuffer = m_pImage->GetPropertyItemSize(PropertyTagFrameDelay);
m_pItem = (PropertyItem*)malloc(TotalBuffer);
m_pImage->GetPropertyItem(PropertyTagFrameDelay,TotalBuffer,m_pItem);
}
/** call this function on timer **/
void OnTimer() {
if(isPlayable) {
KillTimer(hwnd,timer_id);
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid,m_iCurrentFrame);
SetTimer(hwnd,120,NULL);
m_iCurrentFrame = (++m_iCurrentFrame) % m_FrameCount;
//Invalidate(FALSE);
InvalidateRect(hwnd,FALSE);
}
}
/** call this to animate the GIF image,should be call before drawing the image usually on WM_PAINT. **/
void AnimateGIF() {
if(m_bIsPlaying == TRUE) {
return;
}
if(isPlayable) {
m_iCurrentFrame = 0;
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid,NULL);
++m_iCurrentFrame;
InvalidateRect(hwnd,FALSE);
m_bIsPlaying = TRUE;
}
}
/** Function to draw the image in Window,must be call on WM_PAINT. **/
void DrawItem(HDC hdc,int xPosition,int yPosition,int width,int height) {
AnimateGIF(); //This will only works if the image has more than one frame. Otherwise,the single frame of image will be shown.
Graphics g(hdc);
g.DrawImage(m_pImage,xPosition,yPosition,width,height);
}
/** Function to destroy objects and arrays,call this function on WM_DESTROY of WinProc. **/
void Desrtroy() {
if(m_pDimensionIDs) {
delete[] m_pDimensionIDs;
}
if(m_pItem) {
free(m_pItem);
}
if(m_pImage) {
delete m_pImage;
}
}
然后在你的WndProc上,你可以调用上面的函数;
LRESULT CALLBACK WndProc(HWND hWnd,LPARAM lParam) {
switch(message) {
case WM_CREATE: {
LoadImage(); //1. Load the image
break;
}
case WM_TIMER: {
OnTimer(); //2. Call on timer.
break;
}
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd,&ps);
DrawItem(hdc,160,150,95,95); //3. Draw your image,specify your own x and y Position,width and height.
EndPaint(hWnd,&ps);
break;
}
case WM_DESTROY:
{
Desrtroy(); //4. Don't forget to delete the images and free up the arrays.
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hWnd,lParam);
}
使用这种方法,您不会遇到相同的错误。如果超过一帧,GIF 将永远播放。否则只会显示单帧。