Exemple #1
0
//-----------------------------------------------------------------------------
IPlatformBitmap* IPlatformBitmap::createFromPath (UTF8StringPtr absolutePath)
{
	UTF8StringHelper path (absolutePath);
	IStream* stream = 0;
	if (SUCCEEDED (SHCreateStreamOnFileEx (path, STGM_READ|STGM_SHARE_DENY_WRITE, 0, false, 0, &stream)))
	{
#if VSTGUI_DIRECT2D_SUPPORT
		if (getD2DFactory ())
		{
			D2DBitmap* result = new D2DBitmap ();
			if (result->loadFromStream (stream))
			{
				stream->Release ();
				return result;
			}
			stream->Release ();
			result->forget ();
			return 0;
		}
#endif
		GdiplusBitmap* bitmap = new GdiplusBitmap ();
		if (bitmap->loadFromStream (stream))
		{
			stream->Release ();
			return bitmap;
		}
		bitmap->forget ();
		stream->Release ();
	}
	return 0;
}
//
// Function to create a readable IStream over the file whose name is the
// concatenation of the path and fileName parameters.  For simplicity, file
// names including path are assumed to be 100 characters or less.  A real
// application should be able to handle longer names and allocate the
// necessary buffer dynamically.
//
// Parameters:
// path - Path of the folder containing the file to be opened, ending with a
//        slash ('\') character
// fileName - Name, not including path, of the file to be opened
// stream - Output parameter pointing to the created instance of IStream over
//          the specified file when this function succeeds.
//
HRESULT GetFileStream(
    _In_ LPCWSTR path,
    _In_ LPCWSTR fileName,
    _Outptr_ IStream** stream)
{
    HRESULT hr = S_OK;
    const int MaxFileNameLength = 100;
    WCHAR fullFileName[MaxFileNameLength + 1];

    // Create full file name by concatenating path and fileName
    hr = StringCchCopyW(fullFileName, MaxFileNameLength, path);
    if (SUCCEEDED(hr))
    {
        hr = StringCchCat(fullFileName, MaxFileNameLength, fileName);
    }

    // Create stream for reading the file
    if (SUCCEEDED(hr))
    {
        hr = SHCreateStreamOnFileEx(
                fullFileName,
                STGM_READ | STGM_SHARE_EXCLUSIVE,
                0, // default file attributes
                FALSE, // do not create new file
                NULL, // no template
                stream);
    }
    return hr;
}
// Creates a set of sample files in the current user's Documents directory to use as items in the
// custom category inserted into the Jump List.
HRESULT CreateSampleFiles()
{
    PWSTR pszPathDocuments;
    HRESULT hr = SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_CREATE, NULL, &pszPathDocuments);
    if (SUCCEEDED(hr))
    {
        for (UINT i = 0; SUCCEEDED(hr) && i < ARRAYSIZE(c_rgpszFiles); i++)
        {
            WCHAR szPathSample[MAX_PATH];
            hr = PathCombine(szPathSample, pszPathDocuments, c_rgpszFiles[i]) ? S_OK : E_FAIL;
            if (SUCCEEDED(hr))
            {
                IStream *pstm;
                hr = SHCreateStreamOnFileEx(szPathSample, (STGM_WRITE | STGM_FAILIFTHERE), FILE_ATTRIBUTE_NORMAL, TRUE, NULL, &pstm);
                if (SUCCEEDED(hr))
                {
                    PCWSTR pszText = L"This is a sample file for the CustomJumpListSample.\r\n";
                    ULONG cb = (sizeof(pszText[0]) * (lstrlen(pszText) + 1));
                    hr = IStream_Write(pstm, pszText, cb);
                    pstm->Release();
                }
                else if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr)
                {
                    // If the file exists, we're ok, we'll just reuse it
                    hr = S_OK;
                }
            }
        }
        CoTaskMemFree(pszPathDocuments);
    }
    return hr;
}
HRESULT AssemblyStore::ReadFileToStream(LPCWSTR fileName, IStream* pStream) 
{
    HRESULT hr = SHCreateStreamOnFileEx
                    (fileName,
                     STGM_READ | STGM_SHARE_DENY_WRITE | STGM_FAILIFTHERE,
                     0, FALSE, NULL,
                     &pStream);
    return hr;
}
Exemple #5
0
/*************************************************************************
 * SHCreateStreamOnFileW   [SHLWAPI.@]
 *
 * See SHCreateStreamOnFileA.
 */
HRESULT WINAPI SHCreateStreamOnFileW(LPCWSTR lpszPath, DWORD dwMode,
                                   IStream **lppStream)
{
  TRACE("(%s,%ld,%p)\n", debugstr_w(lpszPath), dwMode, lppStream);

  if (!lpszPath || !lppStream)
    return E_INVALIDARG;

  return SHCreateStreamOnFileEx(lpszPath, dwMode, 0, FALSE, NULL, lppStream);
}
HRESULT CNativeRibbonApp::LoadRibbonViewSettings(IUIRibbon* pRibbonView, const CString& fileName)
{
	CComPtr<IStream> stream;
	HRESULT hr = SHCreateStreamOnFileEx(fileName, STGM_READ, FILE_ATTRIBUTE_NORMAL, FALSE, nullptr, &stream);
	if (FAILED(hr))
		return hr;

	hr = pRibbonView->LoadSettingsFromStream(stream);

	return hr;
}
Exemple #7
0
/*************************************************************************
 * SHCreateStreamOnFileW   [SHLWAPI.@]
 *
 * See SHCreateStreamOnFileA.
 */
HRESULT WINAPI SHCreateStreamOnFileW(LPCWSTR lpszPath, DWORD dwMode,
                                   IStream **lppStream)
{
  TRACE("(%s,%d,%p)\n", debugstr_w(lpszPath), dwMode, lppStream);

  if (!lpszPath || !lppStream)
    return E_INVALIDARG;

  if ((dwMode & (STGM_CONVERT|STGM_DELETEONRELEASE|STGM_TRANSACTED)) != 0)
    return E_INVALIDARG;

  return SHCreateStreamOnFileEx(lpszPath, dwMode, 0, FALSE, NULL, lppStream);
}
//
// Function to create an Appx Bundle writer with default settings, given the
// output file name.
//
// Parameters:
// outputFileName - Name including path to the bundle (.appxbundle file) to
//                  be created.
// writer - Output parameter pointing to the created instance of
//          IAppxBundleWriter when this function succeeds.
//
HRESULT GetBundleWriter(
    _In_ LPCWSTR outputFileName,
    _Outptr_ IAppxBundleWriter** writer)
{
    HRESULT hr = S_OK;
    IStream* outputStream = NULL;
    IAppxBundleFactory* appxBundleFactory = NULL;

    // Create a stream over the output file where the bundle will be written
    hr = SHCreateStreamOnFileEx(
            outputFileName,
            STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
            0, // default file attributes
            TRUE, // create file if it does not exist
            NULL, // no template
            &outputStream);

    // Create a new Appx Bundle factory
    if (SUCCEEDED(hr))
    {
        hr = CoCreateInstance(
                __uuidof(AppxBundleFactory),
                NULL,
                CLSCTX_INPROC_SERVER,
                __uuidof(IAppxBundleFactory),
                (LPVOID*)(&appxBundleFactory));
    }

    // Create a new bundle writer using the factory
    if (SUCCEEDED(hr))
    {
        hr = appxBundleFactory->CreateBundleWriter(
                outputStream,
                0, // by specifying 0, the bundle will have an automatically
                   // generated version number based on the current time
                writer);
    }

    // Clean up allocated resources
    if (appxBundleFactory != NULL)
    {
        appxBundleFactory->Release();
        appxBundleFactory = NULL;
    }
    if (outputStream != NULL)
    {
        outputStream->Release();
        outputStream = NULL;
    }
    return hr;
}
//
// Function to create an Appx package reader given the input file name.
//
// Parameters:
// inputFileName - Name including path to the Appx package (.appx file) to
//                 be opened.
// reader - Output parameter pointing to the created instance of
//          IAppxPackageReader when this function succeeds.
//
HRESULT GetPackageReader(
    _In_ LPCWSTR inputFileName,
    _Outptr_ IAppxPackageReader** reader)
{
    HRESULT hr = S_OK;
    IAppxFactory* appxFactory = NULL;
    IStream* inputStream = NULL;

    // Create a new Appx factory
    hr = CoCreateInstance(
            __uuidof(AppxFactory),
            NULL,
            CLSCTX_INPROC_SERVER,
            __uuidof(IAppxFactory),
            (LPVOID*)(&appxFactory));

    // Create a stream over the input Appx package
    if (SUCCEEDED(hr))
    {
        hr = SHCreateStreamOnFileEx(
                inputFileName,
                STGM_READ | STGM_SHARE_EXCLUSIVE,
                0, // default file attributes
                FALSE, // do not create new file
                NULL, // no template
                &inputStream);
    }

    // Create a new package reader using the factory.
    if (SUCCEEDED(hr))
    {
        hr = appxFactory->CreatePackageReader(
                inputStream,
                reader);
    }

    // Clean up allocated resources
    if (inputStream != NULL)
    {
        inputStream->Release();
        inputStream = NULL;
    }
    if (appxFactory != NULL)
    {
        appxFactory->Release();
        appxFactory = NULL;
    }
    return hr;
}
HRESULT CNativeRibbonApp::SaveRibbonViewSettings(IUIRibbon* pRibbonView, const CString& fileName)
{
	CComPtr<IStream> stream;
	HRESULT hr = SHCreateStreamOnFileEx(fileName, STGM_WRITE | STGM_CREATE, FILE_ATTRIBUTE_NORMAL, TRUE, nullptr, &stream);
	if (FAILED(hr))
		return hr;

	hr = pRibbonView->SaveSettingsToStream(stream);
	if (FAILED(hr))
	{
		stream->Revert();
		return hr;
	}

	hr = stream->Commit(STGC_DEFAULT);

	return hr;
}
Exemple #11
0
/*************************************************************************
 * OpenStreamOnFile@24 (MAPI32.147)
 *
 * Create a stream on a file.
 *
 * PARAMS
 *  lpAlloc    [I] Memory allocation function
 *  lpFree     [I] Memory free function
 *  ulFlags    [I] Flags controlling the opening process
 *  lpszPath   [I] Path of file to create stream on
 *  lpszPrefix [I] Prefix of the temporary file name (if ulFlags includes SOF_UNIQUEFILENAME)
 *  lppStream  [O] Destination for created stream
 *
 * RETURNS
 * Success: S_OK. lppStream contains the new stream object
 * Failure: E_INVALIDARG if any parameter is invalid, or an HRESULT error code
 *          describing the error.
 */
HRESULT WINAPI OpenStreamOnFile(LPALLOCATEBUFFER lpAlloc, LPFREEBUFFER lpFree,
                                ULONG ulFlags, LPWSTR lpszPath, LPWSTR lpszPrefix,
                                LPSTREAM *lppStream)
{
    WCHAR szBuff[MAX_PATH];
    DWORD dwMode = STGM_READWRITE, dwAttributes = 0;
    HRESULT hRet;

    TRACE("(%p,%p,0x%08x,%s,%s,%p)\n", lpAlloc, lpFree, ulFlags,
          debugstr_a((LPSTR)lpszPath), debugstr_a((LPSTR)lpszPrefix), lppStream);

    if (mapiFunctions.OpenStreamOnFile)
        return mapiFunctions.OpenStreamOnFile(lpAlloc, lpFree, ulFlags, lpszPath, lpszPrefix, lppStream);

    if (lppStream)
        *lppStream = NULL;

    if (ulFlags & SOF_UNIQUEFILENAME)
    {
        FIXME("Should generate a temporary name\n");
        return E_INVALIDARG;
    }

    if (!lpszPath || !lppStream)
        return E_INVALIDARG;

    /* FIXME: Should probably munge mode and attributes, and should handle
     *        Unicode arguments (I assume MAPI_UNICODE is set in ulFlags if
     *        we are being passed Unicode strings; MSDN doesn't say).
     *        This implementation is just enough for Outlook97 to start.
     */
    MultiByteToWideChar(CP_ACP, 0, (LPSTR)lpszPath, -1, szBuff, MAX_PATH);
    hRet = SHCreateStreamOnFileEx(szBuff, dwMode, dwAttributes, TRUE,
                                  NULL, lppStream);
    return hRet;
}
//
// Function to create a writable IStream over a file with the specified name
// under the specified path.  This function will also create intermediate
// subdirectories if necessary.  For simplicity, file names including path are
// assumed to be 200 characters or less.  A real application should be able to
// handle longer names and allocate the necessary buffer dynamically.
//
// Parameters:
// path - Path of the folder containing the file to be opened.  This should NOT
//        end with a slash ('\') character.
// fileName - Name, not including path, of the file to be opened
// stream - Output parameter pointing to the created instance of IStream over
//          the specified file when this function succeeds.
//
HRESULT GetOutputStream(
    _In_ LPCWSTR path,
    _In_ LPCWSTR fileName,
    _Outptr_ IStream** stream)
{
    HRESULT hr = S_OK;
    const int MaxFileNameLength = 200;
    WCHAR fullFileName[MaxFileNameLength + 1];

    // Create full file name by concatenating path and fileName
    hr = StringCchCopyW(fullFileName, MaxFileNameLength, path);

    if (SUCCEEDED(hr))
    {
        hr = StringCchCat(fullFileName, MaxFileNameLength, L"\\");
    }
    if (SUCCEEDED(hr))
    {
        hr = StringCchCat(fullFileName, MaxFileNameLength, fileName);
    }

    // Search through fullFileName for the '\' character which denotes
    // subdirectory and create each subdirectory in order of depth.
    for (int i = 0; SUCCEEDED(hr) && (i < MaxFileNameLength); i++)
    {
        if (fullFileName[i] == L'\0')
        {
            break;
        }
        else if (fullFileName[i] == L'\\')
        {
            // Temporarily set string to terminate at the '\' character
            // to obtain name of the subdirectory to create
            fullFileName[i] = L'\0';

            if (!CreateDirectory(fullFileName, NULL))
            {
                DWORD lastError = GetLastError();

                // It is normal for CreateDirectory to fail if the subdirectory
                // already exists.  Other errors should not be ignored.
                if (lastError != ERROR_ALREADY_EXISTS)
                {
                    hr = HRESULT_FROM_WIN32(lastError);
                }
            }

            // Restore original string
            fullFileName[i] = L'\\';
        }
    }

    // Create stream for writing the file
    if (SUCCEEDED(hr))
    {
        hr = SHCreateStreamOnFileEx(
                fullFileName,
                STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
                0, // default file attributes
                TRUE, // create new file if it does not exist
                NULL, // no template
                stream);
    }
    return hr;
}
Exemple #13
0
//////////////////////////////////////////////////////////////////////////
// IDataObject
//////////////////////////////////////////////////////////////////////////
STDMETHODIMP GitDataObject::GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium)
{
	if (!pformatetcIn)
		return E_INVALIDARG;
	if (!pmedium)
		return E_POINTER;
	pmedium->hGlobal = nullptr;

	if ((pformatetcIn->tymed & TYMED_ISTREAM) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILECONTENTS))
	{
		// supports the IStream format.
		// The lindex param is the index of the file to return
		CString filepath;
		IStream* pIStream = nullptr;

		// Note: we don't get called for directories since those are simply created and don't
		// need to be fetched.

		// Note2: It would be really nice if we could get a stream from the subversion library
		// from where we could read the file contents. But the Subversion lib is not implemented
		// to *read* from a remote stream but so that the library *writes* to a stream we pass.
		// Since we can't get such a read stream, we have to fetch the file in whole first to
		// a temp location and then pass the shell an IStream for that temp file.

		if (m_revision.IsEmpty())
		{
			if ((pformatetcIn->lindex >= 0) && (pformatetcIn->lindex < (LONG)m_allPaths.size()))
				filepath = g_Git.CombinePath(m_allPaths[pformatetcIn->lindex]);
		}
		else
		{
			filepath = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
			if ((pformatetcIn->lindex >= 0) && (pformatetcIn->lindex < (LONG)m_allPaths.size()))
			{
				if (g_Git.GetOneFile(m_revision.ToString(), m_allPaths[pformatetcIn->lindex], filepath))
				{
					DeleteFile(filepath);
					return STG_E_ACCESSDENIED;
				}
			}
		}

		HRESULT res = SHCreateStreamOnFileEx(filepath, STGM_READ, FILE_ATTRIBUTE_NORMAL, FALSE, nullptr, &pIStream);
		if (res == S_OK)
		{
			// http://blogs.msdn.com/b/oldnewthing/archive/2014/09/18/10558763.aspx
			LARGE_INTEGER liZero = { 0, 0 };
			pIStream->Seek(liZero, STREAM_SEEK_END, nullptr);

			pmedium->pstm = pIStream;
			pmedium->tymed = TYMED_ISTREAM;
			return S_OK;
		}
		return res;
	}
	else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILEDESCRIPTOR))
	{
		for (int i = 0; i < m_gitPaths.GetCount(); ++i)
		{
			if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
				continue;
			m_allPaths.push_back(m_gitPaths[i]);
		}

		size_t dataSize = sizeof(FILEGROUPDESCRIPTOR) + ((max(1, m_allPaths.size()) - 1) * sizeof(FILEDESCRIPTOR));
		HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, dataSize);

		FILEGROUPDESCRIPTOR* files = (FILEGROUPDESCRIPTOR*)GlobalLock(data);
		files->cItems = static_cast<UINT>(m_allPaths.size());
		int index = 0;
		for (auto it = m_allPaths.cbegin(); it != m_allPaths.cend(); ++it)
		{
			CString temp(m_iStripLength > 0 ? it->GetWinPathString().Mid(m_iStripLength + 1) : (m_iStripLength == 0 ? it->GetWinPathString() : it->GetUIFileOrDirectoryName()));
			if (temp.GetLength() < MAX_PATH)
				wcscpy_s(files->fgd[index].cFileName, (LPCTSTR)temp);
			else
			{
				files->cItems--;
				continue;
			}
			files->fgd[index].dwFlags = FD_ATTRIBUTES | FD_PROGRESSUI | FD_FILESIZE | FD_LINKUI;
			files->fgd[index].dwFileAttributes = FILE_ATTRIBUTE_NORMAL;

			// Always set the file size to 0 even if we 'know' the file size (infodata.size64).
			// Because for text files, the file size is too low due to the EOLs getting converted
			// to CRLF (from LF as stored in the repository). And a too low file size set here
			// prevents the shell from reading the full file later - it only reads the stream up
			// to the number of bytes specified here. Which means we would end up with a truncated
			// text file (binary files are still ok though).
			files->fgd[index].nFileSizeLow = 0;
			files->fgd[index].nFileSizeHigh = 0;

			++index;
		}

		GlobalUnlock(data);

		pmedium->hGlobal = data;
		pmedium->tymed = TYMED_HGLOBAL;
		return S_OK;
	}
	// handling CF_PREFERREDDROPEFFECT is necessary to tell the shell that it should *not* ask for the
	// CF_FILEDESCRIPTOR until the drop actually occurs. If we don't handle CF_PREFERREDDROPEFFECT, the shell
	// will ask for the file descriptor for every object (file/folder) the mouse pointer hovers over and is
	// a potential drop target.
	else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->cfFormat == CF_PREFERREDDROPEFFECT))
	{
		HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, sizeof(DWORD));
		DWORD* effect = (DWORD*)GlobalLock(data);
		(*effect) = DROPEFFECT_COPY;
		GlobalUnlock(data);
		pmedium->hGlobal = data;
		pmedium->tymed = TYMED_HGLOBAL;
		return S_OK;
	}
	else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_TEXT))
	{
		// caller wants text
		// create the string from the path list
		CString text;
		if (!m_gitPaths.IsEmpty())
		{
			// create a single string where the URLs are separated by newlines
			for (int i = 0; i < m_gitPaths.GetCount(); ++i)
			{
				text += m_gitPaths[i].GetWinPathString();
				text += L"\r\n";
			}
		}
		CStringA texta = CUnicodeUtils::GetUTF8(text);
		pmedium->tymed = TYMED_HGLOBAL;
		pmedium->hGlobal = GlobalAlloc(GHND, (texta.GetLength() + 1) * sizeof(char));
		if (pmedium->hGlobal)
		{
			char* pMem = (char*)GlobalLock(pmedium->hGlobal);
			strcpy_s(pMem, texta.GetLength() + 1, (LPCSTR)texta);
			GlobalUnlock(pmedium->hGlobal);
		}
		pmedium->pUnkForRelease = nullptr;
		return S_OK;
	}
	else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && ((pformatetcIn->cfFormat == CF_UNICODETEXT) || (pformatetcIn->cfFormat == CF_INETURL) || (pformatetcIn->cfFormat == CF_SHELLURL)))
	{
		// caller wants Unicode text
		// create the string from the path list
		CString text;
		if (!m_gitPaths.IsEmpty())
		{
			// create a single string where the URLs are separated by newlines
			for (int i = 0; i < m_gitPaths.GetCount(); ++i)
			{
				if (pformatetcIn->cfFormat == CF_UNICODETEXT)
					text += m_gitPaths[i].GetWinPathString();
				else
					text += g_Git.CombinePath(m_gitPaths[i]);
				text += L"\r\n";
			}
		}
		pmedium->tymed = TYMED_HGLOBAL;
		pmedium->hGlobal = GlobalAlloc(GHND, (text.GetLength() + 1) * sizeof(TCHAR));
		if (pmedium->hGlobal)
		{
			TCHAR* pMem = (TCHAR*)GlobalLock(pmedium->hGlobal);
			wcscpy_s(pMem, text.GetLength() + 1, (LPCTSTR)text);
			GlobalUnlock(pmedium->hGlobal);
		}
		pmedium->pUnkForRelease = nullptr;
		return S_OK;
	}
	else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_HDROP))
	{
		int nLength = 0;

		for (int i = 0; i < m_gitPaths.GetCount(); ++i)
		{
			if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
				continue;

			nLength += g_Git.CombinePath(m_gitPaths[i]).GetLength();
			nLength += 1; // '\0' separator
		}

		int nBufferSize = sizeof(DROPFILES) + (nLength + 1) * sizeof(TCHAR);
		auto pBuffer = std::make_unique<char[]>(nBufferSize);
		SecureZeroMemory(pBuffer.get(), nBufferSize);

		DROPFILES* df = (DROPFILES*)pBuffer.get();
		df->pFiles = sizeof(DROPFILES);
		df->fWide = 1;

		TCHAR* pFilenames = (TCHAR*)(pBuffer.get() + sizeof(DROPFILES));
		TCHAR* pCurrentFilename = pFilenames;

		for (int i = 0; i < m_gitPaths.GetCount(); ++i)
		{
			if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
				continue;
			CString str = g_Git.CombinePath(m_gitPaths[i]);
			wcscpy_s(pCurrentFilename, str.GetLength() + 1, (LPCWSTR)str);
			pCurrentFilename += str.GetLength();
			*pCurrentFilename = '\0'; // separator between file names
			pCurrentFilename++;
		}
		*pCurrentFilename = '\0'; // terminate array

		pmedium->tymed = TYMED_HGLOBAL;
		pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize);
		if (pmedium->hGlobal)
		{
			LPVOID pMem = ::GlobalLock(pmedium->hGlobal);
			if (pMem)
				memcpy(pMem, pBuffer.get(), nBufferSize);
			GlobalUnlock(pmedium->hGlobal);
		}
		pmedium->pUnkForRelease = nullptr;
		return S_OK;
	}
	else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILE_ATTRIBUTES_ARRAY))
	{
		int nBufferSize = sizeof(FILE_ATTRIBUTES_ARRAY) + m_gitPaths.GetCount() * sizeof(DWORD);
		auto pBuffer = std::make_unique<char[]>(nBufferSize);
		SecureZeroMemory(pBuffer.get(), nBufferSize);

		FILE_ATTRIBUTES_ARRAY* cf = (FILE_ATTRIBUTES_ARRAY*)pBuffer.get();
		cf->cItems = m_gitPaths.GetCount();
		cf->dwProductFileAttributes = DWORD_MAX;
		cf->dwSumFileAttributes = 0;
		for (int i = 0; i < m_gitPaths.GetCount(); ++i)
		{
			DWORD fileattribs = FILE_ATTRIBUTE_NORMAL;
			cf->rgdwFileAttributes[i] = fileattribs;
			cf->dwProductFileAttributes &= fileattribs;
			cf->dwSumFileAttributes |= fileattribs;
		}

		pmedium->tymed = TYMED_HGLOBAL;
		pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize);
		if (pmedium->hGlobal)
		{
			LPVOID pMem = ::GlobalLock(pmedium->hGlobal);
			if (pMem)
				memcpy(pMem, pBuffer.get(), nBufferSize);
			GlobalUnlock(pmedium->hGlobal);
		}
		pmedium->pUnkForRelease = nullptr;
		return S_OK;
	}

	for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
	{
		if ((pformatetcIn->tymed == m_vecFormatEtc[i]->tymed) &&
			(pformatetcIn->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
			(pformatetcIn->cfFormat == m_vecFormatEtc[i]->cfFormat))
		{
			CopyMedium(pmedium, m_vecStgMedium[i], m_vecFormatEtc[i]);
			return S_OK;
		}
	}

	return DV_E_FORMATETC;
}