GDI+从 IStream 加载 PNG 时崩溃
我写了一些东西来通过 GDI+ 从自定义 C++ IStream 加载 PNG 文件。它运行得很好,直到我在 Vista 机器上运行它。每次都崩溃。
在 VS 2008 上编译时,我发现将代码插入到 IStream::AddRef 方法(例如 cout)中可以使问题消失。尽管如此,当用 VS 2010 编译时,它仍然崩溃。
我将程序精简到最基本的部分。我直接从 Microsoft 文档复制了 FileStream。使用 Bitmap::FromFile 时可以加载 PNG。它可以通过 FromFile
或 FromStream
加载 JPEG、GIF 和 BMP。
简而言之:在 Vista 上,通过 Bitmap::FromStream 加载的 PNG 文件会崩溃。
#pragma comment(lib, "gdiplus.lib")
#include <iostream>
#include <objidl.h>
#include <gdiplus.h>
class FileStream : public IStream
{
public:
FileStream(HANDLE hFile)
{
_refcount = 1;
_hFile = hFile;
}
~FileStream()
{
if (_hFile != INVALID_HANDLE_VALUE)
{
::CloseHandle(_hFile);
}
}
public:
HRESULT static OpenFile(LPCWSTR pName, IStream ** ppStream, bool fWrite)
{
HANDLE hFile = ::CreateFileW(pName, fWrite ? GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ,
NULL, fWrite ? CREATE_ALWAYS : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return HRESULT_FROM_WIN32(GetLastError());
*ppStream = new FileStream(hFile);
if(*ppStream == NULL)
CloseHandle(hFile);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject)
{
if (iid == __uuidof(IUnknown)
|| iid == __uuidof(IStream)
|| iid == __uuidof(ISequentialStream))
{
*ppvObject = static_cast<IStream*>(this);
AddRef();
return S_OK;
} else
return E_NOINTERFACE;
}
virtual ULONG STDMETHODCALLTYPE AddRef(void)
{
return (ULONG)InterlockedIncrement(&_refcount);
}
virtual ULONG STDMETHODCALLTYPE Release(void)
{
ULONG res = (ULONG) InterlockedDecrement(&_refcount);
if (res == 0)
delete this;
return res;
}
// ISequentialStream Interface
public:
virtual HRESULT STDMETHODCALLTYPE Read(void* pv, ULONG cb, ULONG* pcbRead)
{
ULONG local_pcbRead;
BOOL rc = ReadFile(_hFile, pv, cb, &local_pcbRead, NULL);
if (pcbRead) *pcbRead = local_pcbRead;
return (rc) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
virtual HRESULT STDMETHODCALLTYPE Write(void const* pv, ULONG cb, ULONG* pcbWritten)
{
BOOL rc = WriteFile(_hFile, pv, cb, pcbWritten, NULL);
return rc ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
// IStream Interface
public:
virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream*, ULARGE_INTEGER, ULARGE_INTEGER*,
ULARGE_INTEGER*)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Commit(DWORD)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Revert(void)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Clone(IStream **)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER liDistanceToMove, DWORD dwOrigin,
ULARGE_INTEGER* lpNewFilePointer)
{
DWORD dwMoveMethod;
switch(dwOrigin)
{
case STREAM_SEEK_SET:
dwMoveMethod = FILE_BEGIN;
break;
case STREAM_SEEK_CUR:
dwMoveMethod = FILE_CURRENT;
break;
case STREAM_SEEK_END:
dwMoveMethod = FILE_END;
break;
default:
return STG_E_INVALIDFUNCTION;
break;
}
if (SetFilePointerEx(_hFile, liDistanceToMove, (PLARGE_INTEGER) lpNewFilePointer,
dwMoveMethod) == 0)
return HRESULT_FROM_WIN32(GetLastError());
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG* pStatstg, DWORD grfStatFlag)
{
if (GetFileSizeEx(_hFile, (PLARGE_INTEGER) &pStatstg->cbSize) == 0)
return HRESULT_FROM_WIN32(GetLastError());
return S_OK;
}
private:
volatile HANDLE _hFile;
volatile LONG _refcount;
};
#define USE_STREAM
int main()
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Gdiplus::Bitmap *bmp;
#ifndef USE_STREAM
bmp = Gdiplus::Bitmap::FromFile(L"test.png", false);
if (!bmp)
{
std::cerr << " Unable to open image file." << std::endl;
return 1;
}
#else
IStream *s;
if (FileStream::OpenFile(L"test.png", &s, false) != S_OK)
{
std::cerr << "Unable to open image file." << std::endl;
return 1;
}
bmp = Gdiplus::Bitmap::FromStream(s, false);
#endif
std::cout << "Image is " << bmp->GetWidth() << " by " << bmp->GetHeight() << std::endl;
Gdiplus::GdiplusShutdown(gdiplusToken);
#ifdef USE_STREAM
s->Release();
#endif
return 0;
}
跟踪和调试表明它确实对 IStream 类进行了一些调用。它在 GdiPlusBitmap.h
的 lastResult = DllExports::GdipCreateBitmapFromStream(stream, &bitmap);
内部崩溃,这是平面 API 上的静态内联包装器。
除了引用计数之外,它调用的唯一 IStream 方法是 stat
(用于文件大小)、read
和 seek
。
调用堆栈如下所示:
- ntdll.dll!_DbgBreakPoint@0() + 0x1 字节
- ntdll.dll!_RtlpBreakPointHeap@4() + 0x28 字节
- ntdll.dll!_RtlpValidateHeapEntry@12() + 0x70a3c 字节
- ntdll.dll!_RtlDebugFreeHeap@12() + 0x9a 字节
- ntdll.dll!@RtlpFreeHeap@16() + 0x13cdd 字节
- ntdll.dll!_RtlFreeHeap@12() + 0x2e49 字节
- kernel32.dll!_HeapFree@12() + 0x14 字节
- ole32.dll!CRetailMalloc_Free() + 0x1c 字节
- ole32.dll!_CoTaskMemFree@4() + 0x13 字节
- GdiPlus.dll!GpPngDecoder::GetImageInfo() + 0x68 字节
- GdiPlus.dll!GpDecodedImage::InternalGetImageInfo() + 0x3c 字节
- GdiPlus.dll!GpDecodedImage::GetImageInfo() + 0x18字节
- GdiPlus.dll!CopyOnWriteBitmap::CopyOnWriteBitmap() + 0x49 字节
- GdiPlus.dll!CopyOnWriteBitmap::Create() + 0x1d 字节 GdiPlus.dll
- !GpBitmap::GpBitmap() + 0x2c 字节
我找不到其他人有相同的问题,所以我认为我的实现有问题......
I wrote something to load PNG files from a custom C++ IStream via GDI+. It worked great until I ran it on Vista machines. Crashes every time.
When compiled on VS 2008, I found that inserting code into the IStream::AddRef
method, such as a cout
, made the problem go away. When compiling with VS 2010, it still crashes regardless of that.
I stripped the program down to its basics. I copied a FileStream straight from Microsoft's documentation. It can load PNGs when using Bitmap::FromFile
. It can load JPEGs, GIFs, and BMPs via FromFile
or FromStream
.
So in short: on Vista, PNG files loaded via Bitmap::FromStream
crash.
#pragma comment(lib, "gdiplus.lib")
#include <iostream>
#include <objidl.h>
#include <gdiplus.h>
class FileStream : public IStream
{
public:
FileStream(HANDLE hFile)
{
_refcount = 1;
_hFile = hFile;
}
~FileStream()
{
if (_hFile != INVALID_HANDLE_VALUE)
{
::CloseHandle(_hFile);
}
}
public:
HRESULT static OpenFile(LPCWSTR pName, IStream ** ppStream, bool fWrite)
{
HANDLE hFile = ::CreateFileW(pName, fWrite ? GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ,
NULL, fWrite ? CREATE_ALWAYS : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return HRESULT_FROM_WIN32(GetLastError());
*ppStream = new FileStream(hFile);
if(*ppStream == NULL)
CloseHandle(hFile);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject)
{
if (iid == __uuidof(IUnknown)
|| iid == __uuidof(IStream)
|| iid == __uuidof(ISequentialStream))
{
*ppvObject = static_cast<IStream*>(this);
AddRef();
return S_OK;
} else
return E_NOINTERFACE;
}
virtual ULONG STDMETHODCALLTYPE AddRef(void)
{
return (ULONG)InterlockedIncrement(&_refcount);
}
virtual ULONG STDMETHODCALLTYPE Release(void)
{
ULONG res = (ULONG) InterlockedDecrement(&_refcount);
if (res == 0)
delete this;
return res;
}
// ISequentialStream Interface
public:
virtual HRESULT STDMETHODCALLTYPE Read(void* pv, ULONG cb, ULONG* pcbRead)
{
ULONG local_pcbRead;
BOOL rc = ReadFile(_hFile, pv, cb, &local_pcbRead, NULL);
if (pcbRead) *pcbRead = local_pcbRead;
return (rc) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
virtual HRESULT STDMETHODCALLTYPE Write(void const* pv, ULONG cb, ULONG* pcbWritten)
{
BOOL rc = WriteFile(_hFile, pv, cb, pcbWritten, NULL);
return rc ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
// IStream Interface
public:
virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream*, ULARGE_INTEGER, ULARGE_INTEGER*,
ULARGE_INTEGER*)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Commit(DWORD)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Revert(void)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Clone(IStream **)
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER liDistanceToMove, DWORD dwOrigin,
ULARGE_INTEGER* lpNewFilePointer)
{
DWORD dwMoveMethod;
switch(dwOrigin)
{
case STREAM_SEEK_SET:
dwMoveMethod = FILE_BEGIN;
break;
case STREAM_SEEK_CUR:
dwMoveMethod = FILE_CURRENT;
break;
case STREAM_SEEK_END:
dwMoveMethod = FILE_END;
break;
default:
return STG_E_INVALIDFUNCTION;
break;
}
if (SetFilePointerEx(_hFile, liDistanceToMove, (PLARGE_INTEGER) lpNewFilePointer,
dwMoveMethod) == 0)
return HRESULT_FROM_WIN32(GetLastError());
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG* pStatstg, DWORD grfStatFlag)
{
if (GetFileSizeEx(_hFile, (PLARGE_INTEGER) &pStatstg->cbSize) == 0)
return HRESULT_FROM_WIN32(GetLastError());
return S_OK;
}
private:
volatile HANDLE _hFile;
volatile LONG _refcount;
};
#define USE_STREAM
int main()
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Gdiplus::Bitmap *bmp;
#ifndef USE_STREAM
bmp = Gdiplus::Bitmap::FromFile(L"test.png", false);
if (!bmp)
{
std::cerr << " Unable to open image file." << std::endl;
return 1;
}
#else
IStream *s;
if (FileStream::OpenFile(L"test.png", &s, false) != S_OK)
{
std::cerr << "Unable to open image file." << std::endl;
return 1;
}
bmp = Gdiplus::Bitmap::FromStream(s, false);
#endif
std::cout << "Image is " << bmp->GetWidth() << " by " << bmp->GetHeight() << std::endl;
Gdiplus::GdiplusShutdown(gdiplusToken);
#ifdef USE_STREAM
s->Release();
#endif
return 0;
}
Tracing and debugging, shows that it does make some calls to the IStream class. It crashes inside of lastResult = DllExports::GdipCreateBitmapFromStream(stream, &bitmap);
from GdiPlusBitmap.h
, which is a static inline wrapper over the flat API.
Other than the reference counting, the only IStream methods it calls is stat
(for file size), read
, and seek
.
Call stack looks like:
- ntdll.dll!_DbgBreakPoint@0() + 0x1 bytes
- ntdll.dll!_RtlpBreakPointHeap@4() + 0x28 bytes
- ntdll.dll!_RtlpValidateHeapEntry@12() + 0x70a3c bytes
- ntdll.dll!_RtlDebugFreeHeap@12() + 0x9a bytes
- ntdll.dll!@RtlpFreeHeap@16() + 0x13cdd bytes
- ntdll.dll!_RtlFreeHeap@12() + 0x2e49 bytes
- kernel32.dll!_HeapFree@12() + 0x14 bytes
- ole32.dll!CRetailMalloc_Free() + 0x1c bytes
- ole32.dll!_CoTaskMemFree@4() + 0x13 bytes
- GdiPlus.dll!GpPngDecoder::GetImageInfo() + 0x68 bytes
- GdiPlus.dll!GpDecodedImage::InternalGetImageInfo() + 0x3c bytes
- GdiPlus.dll!GpDecodedImage::GetImageInfo() + 0x18 bytes
- GdiPlus.dll!CopyOnWriteBitmap::CopyOnWriteBitmap() + 0x49 bytes
- GdiPlus.dll!CopyOnWriteBitmap::Create() + 0x1d bytes
- GdiPlus.dll!GpBitmap::GpBitmap() + 0x2c bytes
I was unable to find anybody else with the same problem, so I assume there's something wrong with my implementation...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在 Win7 上没有使用给定的代码和我自己的 test.png 进行重现。我认为唯一错误的是你的 Stat() 函数,它没有完全初始化 STATSTG。第一次调用时它包含垃圾。
调用堆栈显示堆损坏。 Vista 有一个新的、经过改进的堆管理器,它比 XP 更快地诊断堆损坏。我只能假设损坏发生在未显示的代码中。
No repro with the given code and my own test.png on Win7. The only thing I see wrong is your Stat() function, it doesn't fully initialize the STATSTG. It contains garbage on the first call.
The call stack shows heap corruption. Vista has a new and much improved heap manager that diagnoses heap corruption much sooner than XP. I can only assume that the corruption happens in code not shown.