0

I've been trying to get string representation of HICON. I was able to get the HICON from here. I'm able to save it to a file using this answer. I know I can save to to a temp file and read and get the bytes, but I want to directly convert the stream into byte array. When I do a IStream::Read or IStream_Read, I consistently get Zero bytes. Can you tell me what I'm doing wrong or point me to some documentation on the same?

The following is the code that I've been working on:

std::wstring get_str(HICON hIcon) {
    // Create the IPicture intrface
    PICTDESC desc = {sizeof(PICTDESC)};
    desc.picType = PICTYPE_ICON;
    desc.icon.hicon = hIcon;
    IPicture *pPicture = 0;
    HRESULT hr = OleCreatePictureIndirect(&desc, IID_IPicture, FALSE, (void **)&pPicture);
    if (FAILED(hr)) return L"Erorr creating empty pic";

    // Create a stream and save the image
    IStream *pStream = 0;
    // Use SHCreateMemStream?
    // https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shcreatememstream
    CreateStreamOnHGlobal(0, TRUE, &pStream);
    LONG cbSize = 0;
    hr = pPicture->SaveAsFile(pStream, TRUE, &cbSize);

    // Write the stream content to the file
    if (!FAILED(hr)) {
        HGLOBAL hBuf = 0;
        std::wstring ret;
        char buffer[4096];
        hr = IStream_Read(pStream, &buffer, cbSize);
        if (FAILED(hr))
            return L"Uneven reading from buffer";  // + std::to_wstring(cbSize) + L" " + std::to_wstring(read_size);
        return L"Success";                         // std::wstring(buffer);
    }
    // Cleanup
    pStream->Release();
    pPicture->Release();
    return L"Fail fail fail";
}
Vasantha Ganesh
  • 4,570
  • 3
  • 25
  • 33
  • 2
    After SaveAsFile, you should check cbSize is not zero, and seek to the beginning of the stream (using IStream_Reset for example). – Simon Mourier Sep 25 '20 at 08:52
  • @SimonMourier Cbsize was not zero, I remember that it was 766 or something. I'll try seek and report back. Thanks so much. – Vasantha Ganesh Sep 25 '20 at 09:26

2 Answers2

1

You need to seek the stream's read position to the beginning of the stream after saving and before reading.

The following code works for me:

std::wstring get_str(HICON hIcon) {
    // Create the IPicture intrface
    PICTDESC desc = { sizeof(PICTDESC) };
    desc.picType = PICTYPE_ICON;
    desc.icon.hicon = hIcon;
    IPicture* pPicture = 0;
    HRESULT hr = OleCreatePictureIndirect(&desc, IID_IPicture, FALSE, (void**)&pPicture);
    if (FAILED(hr)) return L"Erorr creating empty pic";
    // Create a stream and save the image
    IStream* pStream;
    // Use SHCreateMemStream?
    // https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shcreatememstream

    if (SUCCEEDED(CreateStreamOnHGlobal(0, TRUE, &pStream)))
    {
        LONG cbSize = 0;
        hr = pPicture->SaveAsFile(pStream, TRUE, &cbSize);
        LARGE_INTEGER li;
        li.HighPart = 0;
        li.LowPart = 0;
        HRESULT hrr = pStream->Seek(li, STREAM_SEEK_SET, NULL);
        // Write the stream content to the file
        if (SUCCEEDED(hr)) {
            HGLOBAL hBuf = 0;
            std::wstring ret;
            char buffer[4096] = "";
            ULONG ll = 0;
            hr = pStream->Read(buffer, cbSize, &ll);
            if (FAILED(hr))
            {
                pStream->Release();
                pPicture->Release();
                return L"Uneven reading from buffer";  // + std::to_wstring(cbSize) + L" " + std::to_wstring(read_size);
            }
            pStream->Release();
            pPicture->Release();
            return L"Success";                         // std::wstring(buffer);
        }
        // Cleanup
        pStream->Release();
        pPicture->Release();
        
    }
    else
    {
        pPicture->Release();
        return L"Fail fail fail";
    }
}
Zeus
  • 3,703
  • 3
  • 7
  • 20
  • 1
    You are leaking the `IPicture` and `IStream` if `Seek()` is successful. Better to use smart pointers instead, like `CComPtr`. And you are not checking `CreateStreamOnHGlobal()` for failure. – Remy Lebeau Sep 25 '20 at 16:08
  • @RemyLebeau Thanks for your reminder, I will update my answer. – Zeus Sep 27 '20 at 01:03
  • your “cleanup” is in the wrong place. If `CreateStreamOnHGlobal()` fails, the `IPicture` is still being leaked, and worse the function result is *undefined*. Also, `if (!FAILED(hr))` can be `if (SUCCEEDED(hr))` – Remy Lebeau Sep 27 '20 at 01:05
0

@SimonMourier Gave the answer in the comments. For anyone looking for code:

std::wstring get_str(HICON hIcon) {
    // Create the IPicture interface
    PICTDESC desc = {sizeof(PICTDESC)};
    desc.picType = PICTYPE_ICON;
    desc.icon.hicon = hIcon;
    // Create an ATL smart pointer.
    CComPtr<IPicture> pPicture;
    LONG cbSize = 0;
    BYTE buffer[4096];
    // Create a Smart pointer stream and save the image
    IStreamPtr pStream;

    if (FAILED(OleCreatePictureIndirect(&desc, IID_IPicture, FALSE, (void **)&pPicture))) {
        LOG_EXCEPTION(L"Unable to create an empty image");
        return L"";
    }
    // Use SHCreateMemStream?
    // https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shcreatememstream
    if (FAILED(CreateStreamOnHGlobal(0, TRUE, &pStream))) {
        LOG_EXCEPTION(L"Unable to create stream for icon.");
        return L"";
    }
    if (FAILED(pPicture->SaveAsFile(pStream, TRUE, &cbSize))) {
        LOG_EXCEPTION(L"Error saving as IPicture.");
        return L"";
    }
    if (IStream_Reset(pStream) != S_OK) {
        LOG_EXCEPTION(L"Error Seeking");
        return L"";
    }

    if (IStream_Read(pStream, &buffer, cbSize) != S_OK) {
        LOG_EXCEPTION(L"Uneven reading from buffer.");
        return L"";
    }
    std::vector<BYTE> data(buffer, buffer + (cbSize / sizeof(BYTE)));

    // Here some function could be type conversion or post processing
    std::wstring ans = some_function(data);

    return ans;
}
Vasantha Ganesh
  • 4,570
  • 3
  • 25
  • 33
  • 1
    You are leaking the `IPicture` and `IStream` on failure. Better to use smart pointers instead, like `CComPtr`. And you are not checking `CreateStreamOnHGlobal()` for failure. – Remy Lebeau Sep 25 '20 at 16:09
  • @RemyLebeau Thank you very much for the pointers. I'll change it. I'm a bit new to c++. – Vasantha Ganesh Sep 26 '20 at 16:49
  • 1
    this isn't a C++ issue. It is a COM resource management issue. COM interfaces need to be `Release()`'d when you are done using them, even if failures occur. When you obtain a non-null COM interface pointer, in this case from `OleCreatePictureIndirect()` and `CreateStreamonHGlobal()`, you need to call `Release()` on it at some point. See [Rules for Managing Reference Counts](https://learn.microsoft.com/en-us/windows/win32/com/rules-for-managing-reference-counts). – Remy Lebeau Sep 26 '20 at 16:57
  • @RemyLebeau, `CComPtr`s are nice. Thanks. – Vasantha Ganesh Sep 28 '20 at 11:43