#include "shellprv.h" #pragma hdrstop #include "mtpt.h" #include "mtptl.h" #include "mtptr.h" #include "hwcmmn.h" #include "cdburn.h" #include "mixctnt.h" #include "regsuprt.h" // Misc comments: // (1) Do a DoAutorun for all new drives except network ones. These will be // generated externally... // static void CMountPoint::WantAutorunUI(LPCWSTR pszDrive) { int iDrive = DRIVEID(pszDrive); CMountPoint::_dwRemoteDriveAutorun |= (1 << iDrive); } BOOL _Shell32LoadedInDesktop() { static BOOL fLoadedInExplorer = -1; if (-1 == fLoadedInExplorer) { fLoadedInExplorer = BOOLFROMPTR(GetModuleHandle(TEXT("EXPLORER.EXE"))); } return fLoadedInExplorer; } // static void CMountPoint::OnNetShareArrival(LPCWSTR pszDrive) { _csDL.Enter(); if (!_fNetDrivesInited) { _InitNetDrives(); } _csDL.Leave(); if (_fNetDrivesInited) { WCHAR szDriveNoSlash[] = TEXT("A:"); WCHAR szRemoteName[MAX_PATH]; DWORD cchRemoteName = ARRAYSIZE(szRemoteName); HRESULT hr; int iDrive = DRIVEID(pszDrive); szDriveNoSlash[0] = *pszDrive; DWORD dw = WNetGetConnection(szDriveNoSlash, szRemoteName, &cchRemoteName); if (NO_ERROR == dw) { hr = CMtPtRemote::_CreateMtPtRemote(pszDrive, szRemoteName, TRUE); } else { DWORD dwGLD = GetLogicalDrives(); if (dwGLD & (1 << iDrive)) { // This must be a weird System mapped drive // which WNet... fcts don't like hr = CMtPtRemote::_CreateMtPtRemoteWithoutShareName(pszDrive); } else { hr = E_FAIL; } } if (SUCCEEDED(hr)) { SHChangeNotify(SHCNE_DRIVEADD, SHCNF_PATH, pszDrive, NULL); if (CMountPoint::_dwRemoteDriveAutorun & (1 << iDrive)) { DoAutorun(pszDrive, AUTORUNFLAG_MTPTARRIVAL); CMountPoint::_dwRemoteDriveAutorun &= ~(1 << iDrive); } } } } // static void CMountPoint::OnNetShareRemoval(LPCWSTR pszDrive) { _csDL.Enter(); if (!_fNetDrivesInited) { _InitNetDrives(); } _csDL.Leave(); if (_fNetDrivesInited) { _RemoveNetMountPoint(pszDrive); SHChangeNotify(SHCNE_DRIVEREMOVED, SHCNF_PATH, pszDrive, NULL); // There's a possibility that this net drive was covering a local drive // with the same drive letter CMountPoint* pmtpt = CMountPoint::GetMountPoint(pszDrive); if (pmtpt) { if (CMountPoint::_IsDriveLetter(pszDrive)) { CDBurn_OnDeviceChange(TRUE, pszDrive); } SHChangeNotify(SHCNE_DRIVEADD, SHCNF_PATH, pszDrive, NULL); pmtpt->Release(); } } } // static void CMountPoint::OnMediaArrival(LPCWSTR pszDrive) { // Check if this local drive letter is not "covered" by a net // drive letter if (!_LocalDriveIsCoveredByNetDrive(pszDrive)) { BOOL fDriveLetter = CMountPoint::_IsDriveLetter(pszDrive); if (fDriveLetter) { CDBurn_OnMediaChange(TRUE, pszDrive); } SHChangeNotify(SHCNE_MEDIAINSERTED, SHCNF_PATH, pszDrive, NULL); // for now do it only for drive letter mounted stuff if (fDriveLetter) { // Send one of these for all media arrival events DoAutorun(pszDrive, AUTORUNFLAG_MEDIAARRIVAL); } // for the non net case force these through right away to make those // cd-rom autorun things come up faster SHChangeNotify(0, SHCNF_FLUSH | SHCNF_FLUSHNOWAIT, NULL, NULL); } } // static void CMountPoint::OnMountPointArrival(LPCWSTR pszDrive) { // Check if this local drive letter is not "covered" by a net // drive letter _csDL.Enter(); if (!_IsDriveLetter(pszDrive)) { _rsMtPtsLocalMOF.RSSetTextValue(NULL, pszDrive, TEXT("")); } _csDL.Leave(); if (!_LocalDriveIsCoveredByNetDrive(pszDrive)) { BOOL fDriveLetter = CMountPoint::_IsDriveLetter(pszDrive); LONG lEvent; if (fDriveLetter) { CDBurn_OnDeviceChange(TRUE, pszDrive); lEvent = SHCNE_DRIVEADD; } else { lEvent = SHCNE_UPDATEITEM; } SHChangeNotify(lEvent, SHCNF_PATH, pszDrive, NULL); if (fDriveLetter) { // If the DBTF_MEDIA is not set, do not send this notification for CDROM // or Removable as they may have come from a new device and not have any // media in them. Also, when inserting a floppy drive (not media) in a // laptop, this would pop up a window. CMountPoint* pmtpt = CMountPoint::GetMountPoint(pszDrive); if (pmtpt) { if (pmtpt->_IsRemote() || pmtpt->_IsFixedDisk() || (pmtpt->_IsRemovableDevice() && !pmtpt->_IsFloppy())) { DoAutorun(pszDrive, AUTORUNFLAG_MTPTARRIVAL); } pmtpt->Release(); } } // for the non net case force these through right away to make those // cd-rom autorun things come up faster SHChangeNotify(0, SHCNF_FLUSH | SHCNF_FLUSHNOWAIT, NULL, NULL); } } void _CloseAutoplayPrompt(LPCWSTR pszDriveOrDeviceID) { HWND hwnd; if (_GetAutoplayPromptHWND(pszDriveOrDeviceID, &hwnd)) { _RemoveFromAutoplayPromptHDPA(pszDriveOrDeviceID); EndDialog(hwnd, IDCANCEL); } } // static void CMountPoint::OnMediaRemoval(LPCWSTR pszDrive) { // Check if this local drive letter is not "covered" by a net // drive letter if (!_LocalDriveIsCoveredByNetDrive(pszDrive)) { if (CMountPoint::_IsDriveLetter(pszDrive)) { CDBurn_OnMediaChange(FALSE, pszDrive); } SHChangeNotify(SHCNE_MEDIAREMOVED, SHCNF_PATH, pszDrive, NULL); _CloseAutoplayPrompt(pszDrive); } } // static void CMountPoint::OnMountPointRemoval(LPCWSTR pszDrive) { // Check if this local drive letter is not "covered" by a net // drive letter _csDL.Enter(); if (!_IsDriveLetter(pszDrive)) { _rsMtPtsLocalMOF.RSSetTextValue(NULL, pszDrive, TEXT("")); } _csDL.Leave(); if (!_LocalDriveIsCoveredByNetDrive(pszDrive)) { LONG lEvent; if (CMountPoint::_IsDriveLetter(pszDrive)) { CDBurn_OnDeviceChange(FALSE, pszDrive); lEvent = SHCNE_DRIVEREMOVED; } else { lEvent = SHCNE_UPDATEITEM; } SHChangeNotify(lEvent, SHCNF_PATH, pszDrive, NULL); _CloseAutoplayPrompt(pszDrive); } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // static HRESULT CMountPoint::_MediaArrivalRemovalHelper(LPCWSTR pszDeviceIDVolume, BOOL fArrived) { ASSERT(!_csDL.IsInside()); HRESULT hr; HDPA hdpaPaths = DPA_Create(4); if (hdpaPaths) { hr = _GetMountPointsForVolume(pszDeviceIDVolume, hdpaPaths); if (SUCCEEDED(hr)) { int n = DPA_GetPtrCount(hdpaPaths); for (int i = n - 1; i >= 0; --i) { LPCWSTR pszMtPt = (LPCWSTR)DPA_GetPtr(hdpaPaths, i); // We don't want to call OnMediaXxxal within the critical // sections ASSERT(!_csDL.IsInside()); if (fArrived) { CMountPoint::OnMediaArrival(pszMtPt); } else { CMountPoint::OnMediaRemoval(pszMtPt); } LocalFree((HLOCAL)pszMtPt); DPA_DeletePtr(hdpaPaths, i); } } DPA_Destroy(hdpaPaths); } else { hr = E_OUTOFMEMORY; } return hr; } // static HRESULT CMountPoint::_VolumeAddedOrUpdated(BOOL fAdded, VOLUMEINFO2* pvolinfo2) { HRESULT hr = S_FALSE; HDPA hdpaMtPtsOld = NULL; CVolume* pvolOld = NULL; CVolume* pvolNew = NULL; BOOL fMediaPresenceChanged = FALSE; BOOL fMediaArrived; _csDL.Enter(); if (!fAdded) { // Updated // That's a volume that some code might have a ptr to. We need to drop // it from the list and create a new one. hdpaMtPtsOld = DPA_Create(3); if (hdpaMtPtsOld) { hr = CMtPtLocal::_GetAndRemoveVolumeAndItsMtPts( pvolinfo2->szDeviceIDVolume, &pvolOld, hdpaMtPtsOld); } else { hr = E_OUTOFMEMORY; } } // Common to Add and Update if (SUCCEEDED(hr)) { CMtPtLocal::_UpdateVolumeRegInfo2(pvolinfo2); hr = CMtPtLocal::_CreateVolumeFromVOLUMEINFO2(pvolinfo2, &pvolNew); } if (SUCCEEDED(hr)) { if (!fAdded) { BOOL fLabelChanged; if (pvolOld && lstrcmp(pvolOld->pszLabel, pvolNew->pszLabel)) { fLabelChanged = TRUE; } else { fLabelChanged = FALSE; } if (hdpaMtPtsOld) { // Create new MtPts from old ones int n = DPA_GetPtrCount(hdpaMtPtsOld); for (int i = 0; i < n; ++i) { CMtPtLocal* pmtptl = (CMtPtLocal*)DPA_GetPtr(hdpaMtPtsOld, i); if (pmtptl) { WCHAR szMountPoint[MAX_PATH]; HRESULT hrTmp = pmtptl->GetMountPointName(szMountPoint, ARRAYSIZE(szMountPoint)); if (SUCCEEDED(hrTmp)) { CMtPtLocal::_CreateMtPtLocalWithVolume(szMountPoint, pvolNew); // for now don't care about return value if (fLabelChanged) { SHChangeNotify(SHCNE_RENAMEFOLDER, SHCNF_PATH, szMountPoint, szMountPoint); } if (pvolOld && (pvolOld->dwMediaState != pvolNew->dwMediaState)) { fMediaPresenceChanged = TRUE; fMediaArrived = !!(HWDMS_PRESENT & pvolNew->dwMediaState); } } // Get rid of old mtptl pmtptl->Release(); } } if (pvolOld) { SHChangeNotify(SHCNE_UPDATEIMAGE, SHCNF_DWORD, IntToPtr(pvolOld->iShellImageForUpdateImage), NULL); } DPA_Destroy(hdpaMtPtsOld); } if (pvolOld) { pvolOld->Release(); } } pvolNew->Release(); } _csDL.Leave(); // Outside of the crit sect. if (fMediaPresenceChanged) { _MediaArrivalRemovalHelper(pvolinfo2->szDeviceIDVolume, fMediaArrived); } return hr; } // In theory, we should do the same as for VolumeUpdated, i.e. remove the volume // from the DPA and all the mtpts, so that code which already has a pointer to it, // will not see a change. But the change we need to do is so tiny, that it would // be overkill. We're just going to flip a bit. // static HRESULT CMountPoint::_VolumeMountingEvent(LPCWSTR pszDeviceIDVolume, DWORD dwEvent) { _csDL.Enter(); CVolume* pvol = CMtPtLocal::_GetVolumeByID(pszDeviceIDVolume); _csDL.Leave(); if (pvol) { if (SHHARDWAREEVENT_VOLUMEDISMOUNTED == dwEvent) { pvol->dwVolumeFlags |= HWDVF_STATE_DISMOUNTED; _csDL.Enter(); // We need to set the label to nothing, so that when the volume // is mounted back, we compare the old and new label, find them to // be different and send an SHChangeNotify. if (pvol->pszLabel) { LPWSTR psz = StrDup(TEXT("")); if (psz) { LocalFree(pvol->pszLabel); // pvol->pszLabel is expected to not be NULL pvol->pszLabel = psz; } } _csDL.Leave(); _MediaArrivalRemovalHelper(pszDeviceIDVolume, FALSE); } else { ASSERT(SHHARDWAREEVENT_VOLUMEMOUNTED == dwEvent); pvol->dwVolumeFlags &= ~HWDVF_STATE_DISMOUNTED; } pvol->Release(); } return S_OK; } // static HRESULT CMountPoint::_VolumeRemoved( LPCWSTR pszDeviceIDVolume) { CVolume* pvol = CMtPtLocal::_GetAndRemoveVolumeByID(pszDeviceIDVolume); if (pvol) { CMtPtLocal::_rsVolumes.RSDeleteSubKey(pvol->pszVolumeGUID + OFFSET_GUIDWITHINVOLUMEGUID); // Final release pvol->Release(); } return S_OK; } HRESULT CMountPoint::_MountPointAdded( LPCWSTR pszMountPoint, // "c:\", or "d:\MountFolder\" LPCWSTR pszDeviceIDVolume)// \\?\STORAGE#Volume#...{...GUID...} { HRESULT hrCreateMtPt; BOOL fCallOnMountPointArrival = TRUE; _csDL.Enter(); CVolume* pvol = CMtPtLocal::_GetVolumeByID(pszDeviceIDVolume); CMtPtLocal* pMtPtLocal = CMountPoint::_rgMtPtDriveLetterLocal[DRIVEID(pszMountPoint)]; if (pMtPtLocal && pMtPtLocal->_IsMiniMtPt()) { // The WM_DEVICECHANGE message beated us, do not do the notif fCallOnMountPointArrival = FALSE; } if (pvol) { hrCreateMtPt = CMtPtLocal::_CreateMtPtLocalWithVolume(pszMountPoint, pvol); } _csDL.Leave(); if (pvol) { if (SUCCEEDED(hrCreateMtPt) && fCallOnMountPointArrival) { CMountPoint::OnMountPointArrival(pszMountPoint); } pvol->Release(); } else { hrCreateMtPt = E_FAIL; } return hrCreateMtPt; } HRESULT CMountPoint::_MountPointRemoved( LPCWSTR pszMountPoint) { HRESULT hr; BOOL fCallOnMountPointRemoval = TRUE; _csDL.Enter(); if (CMountPoint::_IsDriveLetter(pszMountPoint)) { CMtPtLocal* pmtptl = CMountPoint::_rgMtPtDriveLetterLocal[DRIVEID(pszMountPoint)]; if (!pmtptl || pmtptl->_IsMiniMtPt()) { // The WM_DEVICECHANGE message beated us, do not do the notif fCallOnMountPointRemoval = FALSE; } } hr = CMountPoint::_RemoveLocalMountPoint(pszMountPoint); _csDL.Leave(); if (SUCCEEDED(hr) && fCallOnMountPointRemoval) { CMountPoint::OnMountPointRemoval(pszMountPoint); } return hr; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //static void CMountPoint::HandleMountPointNetEvent(LPCWSTR pszDrive, BOOL fArrival) { // These we need to send even if Shell Service is running if (fArrival) { CMountPoint::OnNetShareArrival(pszDrive); } else { CMountPoint::OnNetShareRemoval(pszDrive); } } struct HANDLEMOUNTPOINTLOCALEVENTSTRUCT { WCHAR szDrive[4]; // can only be drive letter BOOL fMediaEvent; }; // static DWORD WINAPI CMountPoint::HandleMountPointLocalEventThreadProc(void* pv) { HANDLEMOUNTPOINTLOCALEVENTSTRUCT* phmle = (HANDLEMOUNTPOINTLOCALEVENTSTRUCT*)pv; Sleep(3000); if (phmle->fMediaEvent) { // Nothing to do, we're not doing anything fancy in safe boot // mode, so no cache to reset, icons no change... // This is common to both Shell Service and non-Shell Service // notification, so do anything non Shell Service notif sepcific // above BOOL fIsMiniMtPt = FALSE; _csDL.Enter(); CMtPtLocal* pMtPtLocal = CMountPoint::_rgMtPtDriveLetterLocal[DRIVEID(phmle->szDrive)]; if (pMtPtLocal) { fIsMiniMtPt = pMtPtLocal->_IsMiniMtPt(); } _csDL.Leave(); if (fIsMiniMtPt) { HRESULT hr = SHCoInitialize(); if (SUCCEEDED(hr)) { CMountPoint::OnMediaArrival(phmle->szDrive); } SHCoUninitialize(hr); } } else { _csDL.Enter(); CMtPtLocal* pMtPtLocal = CMountPoint::_rgMtPtDriveLetterLocal[DRIVEID(phmle->szDrive)]; if (!pMtPtLocal) { // New local drive CMtPtLocal::_CreateMtPtLocal(phmle->szDrive); } _csDL.Leave(); // Can check if pMtMtLocal is NULL or not, but cannot use it // might already have been freed. if (!pMtPtLocal) { HRESULT hr = SHCoInitialize(); if (SUCCEEDED(hr)) { // See comment above (This is common...) CMountPoint::OnMountPointArrival(phmle->szDrive); } SHCoUninitialize(hr); } } LocalFree((HLOCAL)phmle); return 0; } // fMedia: TRUE -> Media // FALSE -> Drive //static void CMountPoint::HandleMountPointLocalEvent(LPCWSTR pszDrive, BOOL fArrival, BOOL fMediaEvent) { if (fArrival) { // We might be racing with the shell service notification. HANDLEMOUNTPOINTLOCALEVENTSTRUCT* phmle = (HANDLEMOUNTPOINTLOCALEVENTSTRUCT*)LocalAlloc(LPTR, sizeof(HANDLEMOUNTPOINTLOCALEVENTSTRUCT)); if (phmle) { HRESULT hr = StringCchCopy(phmle->szDrive, ARRAYSIZE(phmle->szDrive), pszDrive); phmle->fMediaEvent = fMediaEvent; if (FAILED(hr) || !SHQueueUserWorkItem(HandleMountPointLocalEventThreadProc, phmle, 0, (DWORD_PTR)0, (DWORD_PTR*)NULL, NULL, 0)) { LocalFree((HLOCAL)phmle); } } } else { if (fMediaEvent) { // Nothing to do, we're not doing anything fancy in safe boot // mode, so no cache to reset, icons no change... // See comment above (This is common...) CMountPoint::OnMediaRemoval(pszDrive); } else { int iDrive = DRIVEID(pszDrive); BOOL fCallOnMountPointRemoval = TRUE; _csDL.Enter(); if (_rgMtPtDriveLetterLocal[iDrive]) { _rgMtPtDriveLetterLocal[iDrive]->Release(); _rgMtPtDriveLetterLocal[iDrive] = NULL; } else { fCallOnMountPointRemoval = FALSE; } _csDL.Leave(); // Can check if pMtMtLocal is NULL or not, but cannot use it // might already have been freed. if (fCallOnMountPointRemoval) { // See comment above (This is common...) CMountPoint::OnMountPointRemoval(pszDrive); } } } } //static void CMountPoint::HandleWMDeviceChange(ULONG_PTR code, DEV_BROADCAST_HDR* pbh) { if (DBT_DEVTYP_VOLUME == pbh->dbch_devicetype) { if ((DBT_DEVICEREMOVECOMPLETE == code) || (DBT_DEVICEARRIVAL == code)) { DEV_BROADCAST_VOLUME* pbv = (DEV_BROADCAST_VOLUME*)pbh; BOOL fIsNetEvent = !!(pbv->dbcv_flags & DBTF_NET); BOOL fIsMediaEvent = !!(pbv->dbcv_flags & DBTF_MEDIA); for (int iDrive = 0; iDrive < 26; ++iDrive) { if ((1 << iDrive) & pbv->dbcv_unitmask) { TCHAR szPath[4]; if (DBT_DEVICEARRIVAL == code) { // Subst drive have the netevent flag on: bad. PathBuildRoot(szPath, iDrive); // Check if this is the arrival of a subst drive if (DRIVE_REMOTE != GetDriveType(szPath)) { // Yep. fIsNetEvent = FALSE; } else { fIsNetEvent = TRUE; } } else { _csDL.Enter(); CMtPtLocal* pMtPtLocal = CMountPoint::_rgMtPtDriveLetterLocal[iDrive]; if (pMtPtLocal) { fIsNetEvent = FALSE; } _csDL.Leave(); } if (fIsNetEvent) { HandleMountPointNetEvent(PathBuildRoot(szPath, iDrive), DBT_DEVICEARRIVAL == code); } else { HandleMountPointLocalEvent(PathBuildRoot(szPath, iDrive), DBT_DEVICEARRIVAL == code, fIsMediaEvent); } } } } } } // static void CMountPoint::NotifyUnavailableNetDriveGone(LPCWSTR pszMountPoint) { CMountPoint::_RemoveNetMountPoint(pszMountPoint); } // static void CMountPoint::NotifyReconnectedNetDrive(LPCWSTR pszMountPoint) { CMtPtRemote::_NotifyReconnectedNetDrive(pszMountPoint); } // static DWORD CALLBACK CMountPoint::_EventProc(void* pv) { SHHARDWAREEVENT* pshhe = (SHHARDWAREEVENT*)pv; BOOL fLocalDrivesInited; _csDL.Enter(); fLocalDrivesInited = _fLocalDrivesInited; _csDL.Leave(); // If the Local Drives info was not initialized there's nothing to update. if (fLocalDrivesInited) { switch (pshhe->dwEvent) { case SHHARDWAREEVENT_VOLUMEARRIVED: case SHHARDWAREEVENT_VOLUMEUPDATED: { VOLUMEINFO2* pvolinfo2 = (VOLUMEINFO2*)pshhe->rgbPayLoad; CMountPoint::_VolumeAddedOrUpdated( (SHHARDWAREEVENT_VOLUMEARRIVED == pshhe->dwEvent), pvolinfo2); break; } case SHHARDWAREEVENT_VOLUMEREMOVED: { LPCWSTR pszDeviceIDVolume = (LPCWSTR)pshhe->rgbPayLoad; CMountPoint::_VolumeRemoved(pszDeviceIDVolume); break; } case SHHARDWAREEVENT_MOUNTPOINTARRIVED: { MTPTADDED* pmtptadded = (MTPTADDED*)pshhe->rgbPayLoad; CMountPoint::_MountPointAdded(pmtptadded->szMountPoint, pmtptadded->szDeviceIDVolume); break; } case SHHARDWAREEVENT_MOUNTPOINTREMOVED: { LPCWSTR pszMountPoint = (LPCWSTR)pshhe->rgbPayLoad; CMountPoint::_MountPointRemoved(pszMountPoint); break; } case SHHARDWAREEVENT_VOLUMEDISMOUNTED: case SHHARDWAREEVENT_VOLUMEMOUNTED: { LPCWSTR pszDeviceIDVolume = (LPCWSTR)pshhe->rgbPayLoad; CMountPoint::_VolumeMountingEvent(pszDeviceIDVolume, pshhe->dwEvent); break; } } } switch (pshhe->dwEvent) { case SHHARDWAREEVENT_DEVICEARRIVED: case SHHARDWAREEVENT_DEVICEUPDATED: case SHHARDWAREEVENT_DEVICEREMOVED: { HWDEVICEINFO* phwdevinfo = (HWDEVICEINFO*)pshhe->rgbPayLoad; if (SHHARDWAREEVENT_DEVICEARRIVED == pshhe->dwEvent) { if (HWDDF_HASDEVICEHANDLER & phwdevinfo->dwDeviceFlags) { CCrossThreadFlag* pDeviceGoneFlag = new CCrossThreadFlag(); if (pDeviceGoneFlag) { if (pDeviceGoneFlag->Init()) { AttachGoneFlagForDevice(phwdevinfo->szDeviceIntfID, pDeviceGoneFlag); DoDeviceNotification(phwdevinfo->szDeviceIntfID, TEXT("DeviceArrival"), pDeviceGoneFlag); } pDeviceGoneFlag->Release(); } } } else { if (SHHARDWAREEVENT_DEVICEREMOVED == pshhe->dwEvent) { CCrossThreadFlag* pDeviceGoneFlag; if (GetGoneFlagForDevice(phwdevinfo->szDeviceIntfID, &pDeviceGoneFlag)) { pDeviceGoneFlag->Signal(); pDeviceGoneFlag->Release(); } _CloseAutoplayPrompt(phwdevinfo->szDeviceIntfID); } } LPITEMIDLIST pidl; if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &pidl))) { // wait for WIA to do its stuff Sleep(5000); SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST, pidl, NULL); ILFree(pidl); } break; } default: // That's no good break; } VirtualFree(pv, 0, MEM_RELEASE); return 0; } // static void CALLBACK CMountPoint::_EventAPCProc(ULONG_PTR ulpParam) { if (!SHCreateThread(CMountPoint::_EventProc, (void*)ulpParam, CTF_COINIT | CTF_REF_COUNTED, NULL)) { VirtualFree((void*)ulpParam, 0, MEM_RELEASE); } }