2010-04-10 2 views
2

Я написал что-то, чтобы загружать файлы 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 байт

Я не смог найти кого-то еще с той же проблемой, поэтому я предполагаю, что что-то не так с моей реализацией ...

ответ

1

Нет воспроизведения с данным кодом и моим собственным test.png на Win7. Единственное, что я вижу неправильно, это ваша функция Stat(), она не полностью инициализирует STATSTG. Он содержит мусор при первом вызове.

В стеке вызовов отображается повреждение кучи.У Vista есть новый и значительно улучшенный менеджер кучи, который диагностирует повреждение кучи намного раньше, чем XP. Я могу только предположить, что коррупция происходит в коде, который не показан.

+0

Я не могу воспроизвести на Win7. Это происходит только в Vista. Вышеприведенный код - это все, что необходимо для сбоя. Мой реальный проект действительно устанавливает структуру 'stat'. Однако ... Я думаю, что ты прав. Преступник выглядит как 'pwcsName'. В моем реальном коде проекта я не устанавливаю это поле во время загрузки изображения, потому что Microsoft передает поле 'STATFLAG_NONAME', что означает, что они не хотят этого имени. Тем не менее, похоже, что они все еще используют имя при загрузке PNG, если 'pwcsName' не равно null. Я предполагаю, что у них есть что-то вроде 'if (s-> pwcsName) free (s-> pwcsName)', по крайней мере, в библиотеке GDI Vista. – Matthew

+0

Установив указатель 'pwcsName' на значения, отличные от отладки, я пришел к выводу, что в Vista и Windows 7 GDI + есть другое поведение (или что-то связанное). Windows 7 никогда не освободит ее, если она пройдет STATFLAG_NONAME, но Vista всегда будет. При установке параметра «NULL», когда установлен параметр «STATFLAG_NONAME», исправлена ​​проблема. – Matthew

Смежные вопросы