tags:

views:

176

answers:

2

We have a perfectly functional app that just broke on Windows 7 because (the GDI+ primitive) GdipCreateBitmapFromStream refuses JPEG images presented to it (without problem on XP and even Vista).

We don't directly call GDI+, we call the documented ATL CImage class, and it calls that, and gets the error.

It's our own OLE stream implementation.

Has anyone seen a way around this?

Here is a complete test case:

#include <atlbase.h>
#include <atlimage.h>
#include <gdiplus.h>
#include <time.h>
#include <sys/stat.h>

// GSMemoryStream.h : Declaration of the GSMemoryStream

/* No ATL or class factory support is needed here.  You get one of these via "new", with zero reference count...
    Image.Load(IStreamPtr(new GSMemoryStream(ptr, len));
    and the smart pointer will provoke its deletion at the right time....
    */

// GSMemoryStream

class  GSMemoryStream : 
    public IStream
{
    private:
        ULONG m_Length;
     ULONG m_CurPtr;
     PBYTE m_Base;
     int m_rc;
public:

    GSMemoryStream(PBYTE _p, DWORD _len) {
     m_Length = _len;
     m_CurPtr = 0;
     m_Base = _p;
     m_rc = 0;
    }

    GSMemoryStream () {
     m_Length = 0;
     m_CurPtr = 0;
     m_Base = NULL;
        m_rc = 0;
    }

        STDMETHODIMP Read(void *,ULONG,ULONG *);
     STDMETHODIMP Write(const void *,ULONG,ULONG *) {return E_FAIL;}
        STDMETHODIMP Seek(LARGE_INTEGER,DWORD,ULARGE_INTEGER *);
     STDMETHODIMP SetSize(ULARGE_INTEGER) {return E_FAIL;}
        STDMETHODIMP CopyTo(IStream *,ULARGE_INTEGER,ULARGE_INTEGER *,ULARGE_INTEGER *);
     STDMETHODIMP Commit(DWORD) {return S_OK;}
     STDMETHODIMP Revert(void) {return S_OK;}
     STDMETHODIMP LockRegion(ULARGE_INTEGER,ULARGE_INTEGER,DWORD) {return S_OK;}
     STDMETHODIMP UnlockRegion(ULARGE_INTEGER,ULARGE_INTEGER,DWORD) {return S_OK;}
        STDMETHODIMP Stat(STATSTG *,DWORD);
     STDMETHODIMP Clone(IStream ** ) {return E_FAIL;}

     STDMETHODIMP QueryInterface(const IID & iid,void ** d) throw() {
      if (IsEqualGUID(iid, IID_IUnknown) || IsEqualGUID (iid, __uuidof(IStream))) {
       *d = (PVOID)this;
       AddRef();
       return S_OK;
      }
      return E_FAIL;
     }

     ULONG STDMETHODCALLTYPE AddRef(void) throw() {
      m_rc++;
      return S_OK;
     }
     ULONG STDMETHODCALLTYPE Release(void) throw() {
      if (--m_rc == 0)
       delete this; // can never go negative, because the m_rc won't be around any more once it is 0.
      // so it's not even meaningful to test for it and breakpoint or throw.
      return S_OK;
     }
};



// CGSMemoryStream

STDMETHODIMP GSMemoryStream::Read(void * p,ULONG n, ULONG * pNread) {
    ATLTRACE(L"GSMS$Read p %p  bufct %d m_curptr %d\r\n", p, n, m_CurPtr);

    if ((n + m_CurPtr) > m_Length)
     n = m_Length - m_CurPtr;
    memcpy(p, m_Base + m_CurPtr, n);
    if (pNread)
     *pNread = n;
    m_CurPtr += n;
    ATLTRACE(L"GSMS$Read(final) n %d m_CurPtr %d\r\n", n, m_CurPtr);
    return S_OK;
}

STDMETHODIMP GSMemoryStream::Seek(LARGE_INTEGER pos,DWORD type,ULARGE_INTEGER * newpos) {
    LONG lpos = (LONG)pos.LowPart;
    ATLTRACE(L"GSMS$Seek type %d lpos %d m_CurPtr %d\r\n", type, lpos, m_CurPtr);
    switch (type) {
     case STREAM_SEEK_SET:
      if (lpos < 0 || lpos > (LONG) m_Length)
       return E_POINTER;
      m_CurPtr = (ULONG)lpos;
      break;

     case STREAM_SEEK_CUR:
      if (lpos + m_CurPtr < 0 || lpos + m_CurPtr > m_Length)
       return E_POINTER;
      m_CurPtr += lpos;
      break;

     case STREAM_SEEK_END:
      if (lpos > 0)
       lpos = -lpos;
      if (lpos + m_Length < 0)
       return E_POINTER;
      m_CurPtr = m_Length + lpos;
      break;
     default:
      return E_FAIL;

    }
    ATLTRACE(L"GSMS$Seek end m_CurPtr %d\r\n", m_CurPtr);
    if (newpos) {
     newpos->HighPart = 0;
     newpos->LowPart = m_CurPtr;
    }

    return S_OK;
}

STDMETHODIMP GSMemoryStream::CopyTo(IStream * pstm,ULARGE_INTEGER cb,ULARGE_INTEGER * pNread,ULARGE_INTEGER * pNwritten){
    ATLTRACE("GSMS$CopyTo\r\n");
    if (cb.HighPart)
     return E_INVALIDARG;
    ULONG n = cb.LowPart;
    if ((n + m_CurPtr) > m_Length)
     n = m_Length - m_CurPtr;
    ULONG nwritten = 0;
    HRESULT hr = pstm->Write(m_Base+m_CurPtr, n, &nwritten);
    if (nwritten < n)
     nwritten = n;

    if (pNread) {
     pNread->HighPart = 0;
     pNread->LowPart = n;
    }
    if (pNwritten) {
     pNwritten->HighPart = 0;
     pNwritten->LowPart = nwritten;
    }
    m_CurPtr += n;
    return hr;
}

STDMETHODIMP GSMemoryStream::Stat(STATSTG * ps,DWORD krazyflag) {
    ATLTRACE(L"GSMS$Stat kf %d\r\n", krazyflag);
    memset(ps, 0, sizeof(STATSTG));
    ps->type = STGTY_STREAM;
    ps->cbSize.LowPart = m_Length;
    ps->cbSize.HighPart = 0;
#if 0
    ps->mtime = (DWORD)time(NULL);
    ps->ctime = (DWORD)time(NULL);
    ps->atime = (DWORD)time(NULL);
#endif
    return S_OK;
}






int main (int argc, char ** argv) {

    if (argc < 2) {
        fprintf(stderr, "Need image file pathname\n");
        exit(2);
    }
    struct _stat SSTAT;
    const char* fn = argv[1];
    int failed = _stat(fn, &SSTAT);
    if (failed) {
        fprintf(stderr, "Can't open file: %s\n", fn);
        exit(3);
    }
    size_t len = SSTAT.st_size;
    printf ("Len = %d\n", len);
    FILE* f = fopen(fn, "rb");
    unsigned char * buf = new unsigned char [len];
    size_t got = fread (buf, 1, len, f);
    printf ("Got = %d\n", got);
    fclose(f);

    CoInitialize(NULL);
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;

    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    GSMemoryStream G(buf, len);
    CImage cim;
    HRESULT hr = cim.Load(&G);
    printf("HRESULT = 0x%08X\n", hr);
    delete [] buf;
    CoUninitialize();
    return 0;
}
A: 

Have you tried using the documented Bitmap object instead of using the undocumented GDI+ entrypoints?

Larry Osterman
See my edit. CImage makes this choice, we don't.
bmargulies
A: 

Further research reveals that W7 queries an additional optional interface on the stream, and it's essential to return E_NOINTERFACE instead of E_NOTIMPL for it.

bmargulies