2025-11-28 00:35:46 +09:00

612 lines
16 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
#include <windows.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <new>
#include <shlobj.h>
#include "resource.h"
// Setup common controls v6 the easy way
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// window message to inform main window to close a file
#define WM_FILEINUSE_CLOSEFILE (WM_USER + 1)
// this class implements the interface necessary to negotiate with the explorer
// when it hits sharing violations due to the file being open
class CFileIsInUseImpl : public IFileIsInUse
{
public:
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv);
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release();
// IFileIsInUse
IFACEMETHODIMP GetAppName(PWSTR *ppszName);
IFACEMETHODIMP GetUsage(FILE_USAGE_TYPE *pfut);
IFACEMETHODIMP GetCapabilities(DWORD *pdwCapabilitiesFlags);
IFACEMETHODIMP GetSwitchToHWND(HWND *phwnd);
IFACEMETHODIMP CloseFile();
static HRESULT s_CreateInstance(HWND hwnd, PCWSTR pszFilePath, FILE_USAGE_TYPE fut, DWORD dwCapabilities, REFIID riid, void **ppv);
private:
CFileIsInUseImpl();
~CFileIsInUseImpl();
HRESULT _Initialize(HWND hwnd, PCWSTR pszFilePath, FILE_USAGE_TYPE fut, DWORD dwCapabilities);
HRESULT _AddFileToROT();
HRESULT _RemoveFileFromROT();
long _cRef;
WCHAR _szFilePath[MAX_PATH];
HWND _hwnd;
DWORD _dwCapabilities;
DWORD _dwCookie;
FILE_USAGE_TYPE _fut;
};
CFileIsInUseImpl::CFileIsInUseImpl(): _cRef(1), _hwnd(NULL), _fut(FUT_GENERIC), _dwCapabilities(0), _dwCookie(0)
{
_szFilePath[0] = '\0';
}
CFileIsInUseImpl::~CFileIsInUseImpl()
{
_RemoveFileFromROT();
}
HRESULT CFileIsInUseImpl::_Initialize(HWND hwnd, PCWSTR pszFilePath, FILE_USAGE_TYPE fut, DWORD dwCapabilities)
{
_hwnd = hwnd;
_fut = fut;
_dwCapabilities = dwCapabilities;
HRESULT hr = StringCchCopy(_szFilePath, ARRAYSIZE(_szFilePath), pszFilePath);
if (SUCCEEDED(hr))
{
hr = _AddFileToROT();
}
return hr;
}
HRESULT CFileIsInUseImpl::s_CreateInstance(HWND hwnd, PCWSTR pszFilePath, FILE_USAGE_TYPE fut, DWORD dwCapabilities, REFIID riid, void **ppv)
{
CFileIsInUseImpl *pfiu = new (std::nothrow) CFileIsInUseImpl();
HRESULT hr = (pfiu) ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pfiu->_Initialize(hwnd, pszFilePath, fut, dwCapabilities);
if (SUCCEEDED(hr))
{
hr = pfiu->QueryInterface(riid, ppv);
}
pfiu->Release();
}
return hr;
}
HRESULT CFileIsInUseImpl::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CFileIsInUseImpl, IFileIsInUse),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG CFileIsInUseImpl::AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG CFileIsInUseImpl::Release()
{
ULONG cRef = InterlockedDecrement(&_cRef);
if (!cRef)
{
delete this;
}
return cRef;
}
// IFileIsInUse
HRESULT CFileIsInUseImpl::CloseFile()
{
// Notify main application window that we need to close
// the file handle associated with this entry. We do
// not pass anything with this message since this sample
// will only have a single file open at a time.
SendMessage(_hwnd, WM_FILEINUSE_CLOSEFILE, (WPARAM)NULL, (LPARAM)NULL);
_RemoveFileFromROT();
return S_OK;
}
// IFileIsInUse
HRESULT CFileIsInUseImpl::GetAppName(PWSTR *ppszName)
{
HRESULT hr = E_FAIL;
WCHAR szModule[MAX_PATH];
UINT cch = GetModuleFileName(NULL, szModule, ARRAYSIZE(szModule));
if (cch != 0)
{
hr = SHStrDup(PathFindFileName(szModule), ppszName);
}
return hr;
}
// IFileIsInUse
HRESULT CFileIsInUseImpl::GetUsage(FILE_USAGE_TYPE *pfut)
{
*pfut = _fut;
return S_OK;
}
// IFileIsInUse
HRESULT CFileIsInUseImpl::GetCapabilities(DWORD *pdwCapabilitiesFlags)
{
*pdwCapabilitiesFlags = _dwCapabilities;
return S_OK;
}
// IFileIsInUse
HRESULT CFileIsInUseImpl::GetSwitchToHWND(HWND *phwnd)
{
*phwnd = _hwnd;
return S_OK;
}
HRESULT CFileIsInUseImpl::_AddFileToROT()
{
IRunningObjectTable *prot;
HRESULT hr = GetRunningObjectTable(NULL, &prot);
if (SUCCEEDED(hr))
{
IMoniker *pmk;
hr = CreateFileMoniker(_szFilePath, &pmk);
if (SUCCEEDED(hr))
{
// Add ROTFLAGS_ALLOWANYCLIENT to make this work accross security boundaries
hr = prot->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE | ROTFLAGS_ALLOWANYCLIENT,
static_cast<IFileIsInUse *>(this), pmk, &_dwCookie);
if (hr == CO_E_WRONG_SERVER_IDENTITY)
{
// this failure is due to ROTFLAGS_ALLOWANYCLIENT and the fact that we don't
// have the AppID registered for our CLSID. Try again without ROTFLAGS_ALLOWANYCLIENT
// knowing that this means this can only work in the scope of apps running with the
// same MIC level.
hr = prot->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE,
static_cast<IFileIsInUse *>(this), pmk, &_dwCookie);
}
pmk->Release();
}
prot->Release();
}
return hr;
}
HRESULT CFileIsInUseImpl::_RemoveFileFromROT()
{
IRunningObjectTable *prot;
HRESULT hr = GetRunningObjectTable(NULL, &prot);
if (SUCCEEDED(hr))
{
if (_dwCookie)
{
hr = prot->Revoke(_dwCookie);
_dwCookie = 0;
}
prot->Release();
}
return hr;
}
// Text for instructions on dialog
wchar_t const c_szInstructions[] = L"Drag and Drop a file here or click Open File... from the File menu";
// Default usage type flag to use with our IFileIsInUse implementation
#define FUT_DEFAULT FUT_EDITING
// Default capability flags to use with our IFileIsInUse implementation
#define OF_CAP_DEFAULT OF_CAP_CANCLOSE | OF_CAP_CANSWITCHTO
// GUID associated with our application used to register
// as the AppID to run as InteractiveUser. This should be
// unique to your application.
wchar_t const c_szClassGUID[] = L"{E9B568E4-297B-4576-A0DE-ACD9B229CCF3}";
class CFileInUseApp : public IDropTarget
{
public:
CFileInUseApp() : _cRef(1), _hwnd(NULL), _hFile(INVALID_HANDLE_VALUE), _pfiu(NULL)
{
}
HRESULT DoModal(HWND hwnd)
{
HRESULT hr = CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&_pdth));
if (SUCCEEDED(hr))
{
DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_MAINDLG), hwnd, s_DlgProc, (LPARAM)this);
}
return hr;
}
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CFileInUseApp, IDropTarget),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
ULONG cRef = InterlockedDecrement(&_cRef);
if (!cRef)
{
delete this;
}
return cRef;
}
// IDropTarget
IFACEMETHODIMP DragEnter(IDataObject *pdtobj, DWORD /*grfKeyState*/, POINTL pt, DWORD *pdwEffect)
{
*pdwEffect &= DROPEFFECT_COPY | DROPEFFECT_LINK | DROPEFFECT_MOVE;
if (_pdth)
{
POINT ptT = { pt.x, pt.y };
_pdth->DragEnter(_hwnd, pdtobj, &ptT, *pdwEffect);
}
return S_OK;
}
IFACEMETHODIMP DragOver(DWORD /*grfKeyState*/, POINTL pt, DWORD *pdwEffect)
{
if (_pdth)
{
POINT ptT = { pt.x, pt.y };
_pdth->DragOver(&ptT, *pdwEffect);
}
return S_OK;
}
IFACEMETHODIMP DragLeave()
{
if (_pdth)
{
_pdth->DragLeave();
}
return S_OK;
}
IFACEMETHODIMP Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
private:
~CFileInUseApp()
{
_CloseFile();
if (_pdth)
{
_pdth->Release();
}
}
static INT_PTR CALLBACK s_DlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CFileInUseApp *pcd;
if (uMsg == WM_INITDIALOG)
{
pcd = reinterpret_cast<CFileInUseApp *>(lParam);
pcd->_hwnd = hdlg;
SetWindowLongPtr(hdlg, DWLP_USER, reinterpret_cast<LONG_PTR>(pcd));
}
else
{
pcd = reinterpret_cast<CFileInUseApp *>(GetWindowLongPtr(hdlg, DWLP_USER));
}
return pcd ? pcd->_DlgProc(uMsg, wParam, lParam) : FALSE;
}
INT_PTR _DlgProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
void _OnInitDlg();
void _OnDestroy();
void _OnOpenFile();
void _OnCommand(UINT uCmd, HWND hwndNotify, UINT uCode);
HRESULT _OpenFile(PCWSTR pszPath);
void _CloseFile();
long _cRef;
HWND _hwnd;
HANDLE _hFile;
IDropTargetHelper *_pdth;
IFileIsInUse *_pfiu;
};
// IDropTarget methods
IFACEMETHODIMP CFileInUseApp::Drop(IDataObject *pdtobj, DWORD /*grfKeyState*/, POINTL pt, DWORD *pdwEffect)
{
if (_pdth)
{
POINT ptT = { pt.x, pt.y };
_pdth->Drop(pdtobj, &ptT, *pdwEffect);
}
// Create a IShellItemArray from the IDataObject
IShellItemArray *psia;
HRESULT hr = SHCreateShellItemArrayFromDataObject(pdtobj, IID_PPV_ARGS(&psia));
if (SUCCEEDED(hr))
{
// For this sample, we only open the first item that
// was dragged and dropped to our application.
IShellItem *psi;
hr = psia->GetItemAt(0, &psi);
if (SUCCEEDED(hr))
{
// Get the full path of the file
PWSTR pszPath;
hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
if (SUCCEEDED(hr))
{
// Open the file
hr = _OpenFile(pszPath);
CoTaskMemFree(pszPath);
}
psi->Release();
}
psia->Release();
}
DragLeave();
return S_OK;
}
HRESULT CFileInUseApp::_OpenFile(PCWSTR pszPath)
{
// Close the file if it is already opened
_CloseFile();
// Initialize the IFileIsInUse object. We use some default flags here
// as an example. If you modify these you will notice Windows Explorer
// modify its File In Use dialog contents accordingly to match the usage
// type and available capabilities.
HRESULT hr = CFileIsInUseImpl::s_CreateInstance(_hwnd, pszPath, FUT_DEFAULT, OF_CAP_DEFAULT, IID_PPV_ARGS(&_pfiu));
if (SUCCEEDED(hr))
{
// The lack of FILE_SHARE_READ or FILE_SHARE_WRITE attributes for the dwShareMode
// parameter will cause the file to be locked from other processes.
_hFile = CreateFile(pszPath, GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (INVALID_HANDLE_VALUE != _hFile)
{
// Display the path in the
PathSetDlgItemPath(_hwnd, IDC_INFO, pszPath);
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (FAILED(hr))
{
// We failed somewhere above. Cleanup
// our file in use object.
_pfiu->Release();
_pfiu = NULL;
}
}
return hr;
}
void CFileInUseApp::_CloseFile()
{
// Close the file handle
if (INVALID_HANDLE_VALUE != _hFile)
{
CloseHandle(_hFile);
_hFile = INVALID_HANDLE_VALUE;
}
// Release the IFileIsInUse instance which will
// remove it from the Running Object Table
if (_pfiu)
{
_pfiu->Release();
_pfiu = NULL;
}
// Remove the file path from the dialog
SetDlgItemText(_hwnd, IDC_INFO, c_szInstructions);
}
void CFileInUseApp::_OnInitDlg()
{
// Initialize instructions
SetDlgItemText(_hwnd, IDC_INFO, c_szInstructions);
// Setup the application window
// for drag and drop
RegisterDragDrop(_hwnd, this);
}
void CFileInUseApp::_OnDestroy()
{
// Remove drag and drop capabilities
// from the application window
RevokeDragDrop(_hwnd);
}
void CFileInUseApp::_OnOpenFile()
{
WCHAR szPath[MAX_PATH] = {};
OPENFILENAME ofn = {sizeof(ofn)};
ofn.hwndOwner = _hwnd;
ofn.lpstrFilter = L"All Files\0*.*\0";
ofn.lpstrFile = szPath;
ofn.nMaxFile = ARRAYSIZE(szPath);
BOOL fOk = GetOpenFileName(&ofn);
if (fOk)
{
// Open the file that was selected in
// the Open File dialog
_OpenFile(szPath);
}
}
void CFileInUseApp::_OnCommand(UINT uCmd, HWND /*hwndNotify*/, UINT /*uCode*/)
{
// The user has clicked a menu item
switch (uCmd)
{
case IDM_FILE_OPENFILE:
_OnOpenFile();
break;
case IDM_FILE_EXIT:
_CloseFile();
EndDialog(_hwnd, TRUE);
break;
case IDM_FILE_CLOSEFILE:
_CloseFile();
break;
}
}
INT_PTR CFileInUseApp::_DlgProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
INT_PTR lret = 0;
switch (uMsg)
{
case WM_INITDIALOG:
_OnInitDlg();
break;
case WM_CLOSE:
_CloseFile();
EndDialog(_hwnd, FALSE);
break;
case WM_INITMENUPOPUP:
{
// Disable the Close File menu item if it is not open
UINT const uState = (INVALID_HANDLE_VALUE == _hFile) ? MF_GRAYED : MF_ENABLED;
EnableMenuItem(GetMenu(_hwnd), IDM_FILE_CLOSEFILE, MF_BYCOMMAND | uState);
}
break;
case WM_FILEINUSE_CLOSEFILE:
// We have been notified from the IFileIsInUse object
// to close the file
_CloseFile();
break;
case WM_COMMAND:
_OnCommand(LOWORD(wParam), (HWND)lParam, HIWORD(wParam));
break;
case WM_DESTROY:
_OnDestroy();
break;
}
return lret;
}
//
// NOTE:
// IRunningObjectTable::Register(.., ROTFLAGS_ALLOWANYCLIENT) requires AppID
// registration so COM can inspect our security seetings. without this the call
// Register() will fail with CO_E_WRONG_SERVER_IDENTITY(0x80004015) "The class is configured to run as a security
// id different from the caller"
//
// HKLM\Software\Classes\AppID\app.exe
// AppID = "{app clsid}"
//
// HKLM\Software\Classes\AppID\{app clsid}
// RunAs = "Interactive User"
//
// NOTE: this code requires write access to HKLM
HRESULT _RegisterThisAppRunAsInteractiveUser(PCWSTR pszCLSID)
{
HRESULT hr = E_INVALIDARG;
WCHAR szModule[MAX_PATH];
if (GetModuleFileName(NULL, szModule, ARRAYSIZE(szModule)))
{
WCHAR szKey[MAX_PATH];
hr = StringCchPrintf(szKey, ARRAYSIZE(szKey), L"Software\\Classes\\AppID\\%s", PathFindFileName(szModule));
if (SUCCEEDED(hr))
{
HKEY hk;
LSTATUS ls = RegCreateKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, NULL, 0, KEY_WRITE, NULL, &hk, NULL);
hr = HRESULT_FROM_WIN32(ls);
if (SUCCEEDED(hr))
{
RegSetValueEx(hk, L"AppID", 0, REG_SZ, (BYTE *)pszCLSID, sizeof(*pszCLSID) * (lstrlen(pszCLSID) + 1));
RegCloseKey(hk);
hr = StringCchPrintf(szKey, ARRAYSIZE(szKey), L"Software\\Classes\\AppID\\%s", pszCLSID);
if (SUCCEEDED(hr))
{
ls = RegCreateKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, NULL, 0, KEY_WRITE, NULL, &hk, NULL);
hr = HRESULT_FROM_WIN32(ls);
if (SUCCEEDED(hr))
{
RegSetValueEx(hk, L"RunAs", 0, REG_SZ, (BYTE *)L"Interactive User", sizeof(L"Interactive User"));
RegCloseKey(hk);
}
}
}
}
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
return hr;
}
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
// Initialize for drag and drop
HRESULT hr = OleInitialize(0);
if (SUCCEEDED(hr))
{
_RegisterThisAppRunAsInteractiveUser(c_szClassGUID);
CFileInUseApp *pdlg = new (std::nothrow) CFileInUseApp();
if (pdlg)
{
pdlg->DoModal(NULL);
pdlg->Release();
}
OleUninitialize();
}
return 0;
}