当从IStream加载PNG时,GDI+崩溃了。

4
我写了一些代码,可以通过GDI+从自定义的C++ IStream中加载PNG文件。它在其他机器上都很好用,但在Vista机器上运行时总是崩溃。
在使用VS 2008编译时,我发现在IStream::AddRef方法中插入一些代码,比如cout,可以解决这个问题。但在使用VS 2010编译时,无论是否插入代码,它仍然会崩溃。
我将程序简化到了最基本的程度。我直接从Microsoft的文档中复制了一个FileStream。当使用Bitmap::FromFile时,它可以加载PNG文件。当使用FromFileFromStream时,它可以加载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(用于文件大小),readseek
调用堆栈如下:
- 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字节
我无法找到其他人有同样的问题,所以我认为我的实现有问题...
1个回答

2

在Win7上,给定的代码和我的own test.png没有任何问题。我唯一看到的问题是你的Stat()函数,它没有完全初始化STATSTG。第一次调用时它包含了垃圾数据。

调用堆栈显示堆损坏。Vista拥有一个新的、改进得多的堆管理器,比XP更早地诊断堆损坏。我只能假设损坏发生在未显示的代码中。


我在Win7上也无法重现。这只会发生在Vista上。上面的代码就足以导致崩溃。我的真实项目确实设置了stat结构体。然而...我认为你是对的。罪魁祸首看起来像是pwcsName。在我的真实项目代码中,我在图像加载期间没有设置该字段,因为Microsoft传递了STATFLAG_NONAME字段,这意味着他们不想要名称。然而,如果pwcsName非空,则似乎它们仍然在PNG加载时使用名称。我猜他们在Vista GDI库上至少有类似于if (s->pwcsName) free(s->pwcsName)的东西。 - Matthew
通过将 pwcsName 指针设置为非调试值,我得出结论:在Vista和Windows 7 GDI+库(或相关的某些东西)中存在不同的行为。如果通过 STATFLAG_NONAME 传递参数,则Windows 7永远不会释放它,但是Vista总是会释放它。当 STATFLAG_NONAME 被设置时,将其设为 NULL 可以解决此问题。 - Matthew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接