Я написал что-то, чтобы загружать файлы PNG из пользовательского C++ IStream через GDI +. Он отлично работал, пока я не запустил его на машинах Vista. Сбой каждый раз.GDI + сбой при загрузке PNG из IStream
Когда скомпилировано на VS 2008, я обнаружил, что вставка кода в метод IStream::AddRef
, такой как cout
, заставила проблему уйти. При компиляции с VS 2010 он все равно выходит из строя независимо от этого.
Я убрал программу до ее основы. Я скопировал FileStream прямо из документации Microsoft. Он может загружать PNG при использовании Bitmap::FromFile
. Он может загружать JPEG, GIF и BMP через FromFile
или FromStream
.
Итак, вкратце: на Vista, файлы PNG загружены через Bitmap::FromStream
сбой.
#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. Он сбой внутри lastResult = DllExports::GdipCreateBitmapFromStream(stream, &bitmap);
от GdiPlusBitmap.h
, который является статической встроенной оболочкой поверх плоского API.
За исключением подсчета ссылок, единственным методом, который он называет, является stat
(для размера файла), read
и seek
.
стек вызовов выглядит:
- [email protected]() + 0x1 байт
- [email protected]() + 0x28 байт
- ntdll.dll _RtlpValidateHeapEntry @ 12() + 0x70a3c байты
- [email protected]() + 0x9A байты
- ntdll.dll! @ RtlpFreeHeap @ 16() + 0x13cdd байты
- ntdll.dll! _RtlF reeHeap @ 12() + 0x2e49 байт
- [email protected]() + 0x14 байт
- ole32.dll! CRetailMalloc_Free() + 0x1c байтов
- [email protected]() + 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 байт
Я не смог найти кого-то еще с той же проблемой, поэтому я предполагаю, что что-то не так с моей реализацией ...
Я не могу воспроизвести на Win7. Это происходит только в Vista. Вышеприведенный код - это все, что необходимо для сбоя. Мой реальный проект действительно устанавливает структуру 'stat'. Однако ... Я думаю, что ты прав. Преступник выглядит как 'pwcsName'. В моем реальном коде проекта я не устанавливаю это поле во время загрузки изображения, потому что Microsoft передает поле 'STATFLAG_NONAME', что означает, что они не хотят этого имени. Тем не менее, похоже, что они все еще используют имя при загрузке PNG, если 'pwcsName' не равно null. Я предполагаю, что у них есть что-то вроде 'if (s-> pwcsName) free (s-> pwcsName)', по крайней мере, в библиотеке GDI Vista. – Matthew
Установив указатель 'pwcsName' на значения, отличные от отладки, я пришел к выводу, что в Vista и Windows 7 GDI + есть другое поведение (или что-то связанное). Windows 7 никогда не освободит ее, если она пройдет STATFLAG_NONAME, но Vista всегда будет. При установке параметра «NULL», когда установлен параметр «STATFLAG_NONAME», исправлена проблема. – Matthew