Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

580 lines
19 KiB

  1. // wiaacmgr.cpp : Implementation of WinMain
  2. // Note: Proxy/Stub Information
  3. // To build a separate proxy/stub DLL,
  4. // run nmake -f wiaacmgrps.mk in the project directory.
  5. #include "precomp.h"
  6. #include "resource.h"
  7. #include "wiaacmgr.h"
  8. #include <shpriv.h>
  9. #include <shlguid.h>
  10. #include <stdarg.h>
  11. #include "wiaacmgr_i.c"
  12. #include "acqmgr.h"
  13. #include "eventprompt.h"
  14. #include "runwiz.h"
  15. #include <initguid.h>
  16. HINSTANCE g_hInstance;
  17. #if defined(DBG_GENERATE_PRETEND_EVENT)
  18. extern "C" int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int /*nShowCmd*/)
  19. {
  20. WIA_DEBUG_CREATE( hInstance );
  21. SHFusionInitializeFromModuleID( hInstance, 123 );
  22. g_hInstance = hInstance;
  23. HRESULT hr = CoInitialize(NULL);
  24. if (SUCCEEDED(hr))
  25. {
  26. IWiaDevMgr *pIWiaDevMgr = NULL;
  27. hr = CoCreateInstance( CLSID_WiaDevMgr, NULL, CLSCTX_LOCAL_SERVER, IID_IWiaDevMgr, (void**)&pIWiaDevMgr );
  28. if (SUCCEEDED(hr))
  29. {
  30. BSTR bstrDeviceID;
  31. hr = pIWiaDevMgr->SelectDeviceDlgID( NULL, 0, 0, &bstrDeviceID );
  32. if (hr == S_OK)
  33. {
  34. CEventParameters EventParameters;
  35. EventParameters.EventGUID = WIA_EVENT_DEVICE_CONNECTED;
  36. EventParameters.strEventDescription = L"";
  37. EventParameters.strDeviceID = static_cast<LPCWSTR>(bstrDeviceID);
  38. EventParameters.strDeviceDescription = L"";
  39. EventParameters.ulEventType = 0;
  40. EventParameters.ulReserved = 0;
  41. EventParameters.hwndParent = NULL;
  42. HANDLE hThread = CAcquisitionThread::Create( EventParameters );
  43. if (hThread)
  44. WaitForSingleObject(hThread,INFINITE);
  45. SysFreeString(bstrDeviceID);
  46. }
  47. pIWiaDevMgr->Release();
  48. }
  49. CoUninitialize();
  50. }
  51. SHFusionUninitialize();
  52. WIA_REPORT_LEAKS();
  53. WIA_DEBUG_DESTROY();
  54. return 0;
  55. }
  56. #else // !defined(DBG_GENERATE_PRETEND_EVENT)
  57. const DWORD dwTimeOut = 5000; // time for EXE to be idle before shutting down
  58. const DWORD dwPause = 1000; // time to wait for threads to finish up
  59. // Passed to CreateThread to monitor the shutdown event
  60. static DWORD WINAPI MonitorProc(void* pv)
  61. {
  62. CExeModule* p = (CExeModule*)pv;
  63. p->MonitorShutdown();
  64. return 0;
  65. }
  66. LONG CExeModule::Unlock()
  67. {
  68. LONG l = CComModule::Unlock();
  69. if (l == 0)
  70. {
  71. bActivity = true;
  72. SetEvent(hEventShutdown); // tell monitor that we transitioned to zero
  73. }
  74. return l;
  75. }
  76. //Monitors the shutdown event
  77. void CExeModule::MonitorShutdown()
  78. {
  79. while (1)
  80. {
  81. WaitForSingleObject(hEventShutdown, INFINITE);
  82. DWORD dwWait=0;
  83. do
  84. {
  85. bActivity = false;
  86. dwWait = WaitForSingleObject(hEventShutdown, dwTimeOut);
  87. } while (dwWait == WAIT_OBJECT_0);
  88. // timed out
  89. if (!bActivity && m_nLockCnt == 0) // if no activity let's really bail
  90. {
  91. #if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
  92. CoSuspendClassObjects();
  93. if (!bActivity && m_nLockCnt == 0)
  94. #endif
  95. break;
  96. }
  97. }
  98. CloseHandle(hEventShutdown);
  99. PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
  100. }
  101. bool CExeModule::StartMonitor()
  102. {
  103. hEventShutdown = CreateEvent(NULL, false, false, NULL);
  104. if (hEventShutdown == NULL)
  105. return false;
  106. DWORD dwThreadID;
  107. HANDLE h = CreateThread(NULL, 0, MonitorProc, this, 0, &dwThreadID);
  108. return (h != NULL);
  109. }
  110. CExeModule _Module;
  111. BEGIN_OBJECT_MAP(ObjectMap)
  112. OBJECT_ENTRY(CLSID_AcquisitionManager, CAcquisitionManager)
  113. OBJECT_ENTRY(CLSID_MinimalTransfer, CMinimalTransfer)
  114. OBJECT_ENTRY(WIA_EVENT_HANDLER_PROMPT, CEventPrompt)
  115. OBJECT_ENTRY(CLSID_StiEventHandler, CStiEventHandler)
  116. END_OBJECT_MAP()
  117. LPCTSTR FindOneOf(LPCTSTR p1, LPCTSTR p2)
  118. {
  119. while (p1 != NULL && *p1 != NULL)
  120. {
  121. LPCTSTR p = p2;
  122. while (p != NULL && *p != NULL)
  123. {
  124. if (*p1 == *p)
  125. return CharNext(p1);
  126. p = CharNext(p);
  127. }
  128. p1 = CharNext(p1);
  129. }
  130. return NULL;
  131. }
  132. //
  133. // This MUST be removed before we ship. To remove it, remove
  134. // the define ENABLE_SETUP_LOGGING from the sources file
  135. //
  136. #if defined(ENABLE_SETUP_LOGGING)
  137. void _WizardPrintLogFileMessage( LPCTSTR pszFormat, ... )
  138. {
  139. //
  140. // The name of the log file
  141. //
  142. static const TCHAR c_szLogFileName[] = TEXT("wiaacmgr.log");
  143. //
  144. // The path to the log file
  145. //
  146. static TCHAR szLogFilePathName[MAX_PATH] = {0};
  147. //
  148. // If we don't have a pathname, create it
  149. //
  150. if (!lstrlen(szLogFilePathName))
  151. {
  152. if (GetWindowsDirectory( szLogFilePathName, ARRAYSIZE(szLogFilePathName) ))
  153. {
  154. lstrcat( szLogFilePathName, TEXT("\\") );
  155. lstrcat( szLogFilePathName, c_szLogFileName );
  156. }
  157. }
  158. //
  159. // If we still don't have a pathname, return
  160. //
  161. if (!lstrlen(szLogFilePathName))
  162. {
  163. return;
  164. }
  165. //
  166. // Add the date and time to the output string
  167. //
  168. TCHAR szMessage[1500] = {0};
  169. GetDateFormat( LOCALE_SYSTEM_DEFAULT, 0, NULL, TEXT("MM'/'dd'/'yy' '"), szMessage+lstrlen(szMessage), ARRAYSIZE(szMessage)-lstrlen(szMessage) );
  170. GetTimeFormat( LOCALE_SYSTEM_DEFAULT, 0, NULL, TEXT("HH':'mm':'ss' '"), szMessage+lstrlen(szMessage), ARRAYSIZE(szMessage)-lstrlen(szMessage) );
  171. //
  172. // Add the process ID and thread ID to the output string
  173. //
  174. wsprintf( szMessage+lstrlen(szMessage), TEXT("%08X %08X : "), GetCurrentProcessId(), GetCurrentThreadId() );
  175. //
  176. // Format the output string
  177. //
  178. va_list pArgs;
  179. va_start( pArgs, pszFormat );
  180. wvsprintf( szMessage+lstrlen(szMessage), pszFormat, pArgs );
  181. va_end( pArgs );
  182. lstrcat( szMessage, TEXT("\r\n" ) );
  183. //
  184. // Open the file
  185. //
  186. HANDLE hFile = CreateFile( szLogFilePathName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
  187. if (hFile)
  188. {
  189. //
  190. // Go to the end of the file
  191. //
  192. if (INVALID_SET_FILE_POINTER != SetFilePointer(hFile,0,NULL,FILE_END))
  193. {
  194. //
  195. // Write out the string
  196. //
  197. DWORD dwWritten;
  198. WriteFile(hFile,szMessage,lstrlen(szMessage)*sizeof(TCHAR),&dwWritten,NULL);
  199. }
  200. //
  201. // Close the file
  202. //
  203. CloseHandle(hFile);
  204. }
  205. }
  206. #define LOG_MESSAGE(x) _WizardPrintLogFileMessage x
  207. #else
  208. #define LOG_MESSAGE(x)
  209. #endif ENABLE_SETUP_LOGGING
  210. static HRESULT RegisterForWiaEvents( LPCWSTR pszDevice, const CLSID &clsidHandler, const IID &iidEvent, int nName, int nDescription, int nIcon, bool bDefault, bool bRegister )
  211. {
  212. WIA_PUSH_FUNCTION((TEXT("RegisterForWiaEvents( device: %ws, default: %d, register: %d )"), pszDevice, bDefault, bRegister ));
  213. WIA_PRINTGUID((clsidHandler,TEXT("Handler:")));
  214. WIA_PRINTGUID((iidEvent,TEXT("Event:")));
  215. CComPtr<IWiaDevMgr> pWiaDevMgr;
  216. HRESULT hr = CoCreateInstance( CLSID_WiaDevMgr, NULL, CLSCTX_LOCAL_SERVER, IID_IWiaDevMgr, (void**)&pWiaDevMgr );
  217. LOG_MESSAGE((TEXT(" 0x%08X CoCreateInstance( CLSID_WiaDevMgr )"), hr ));
  218. if (SUCCEEDED(hr))
  219. {
  220. CSimpleBStr bstrDeviceId(pszDevice);
  221. CSimpleBStr bstrName(CSimpleString(nName,_Module.m_hInst));
  222. CSimpleBStr bstrDescription(CSimpleString(nDescription,_Module.m_hInst));
  223. CSimpleBStr bstrIcon(CSimpleString(nIcon,_Module.m_hInst));
  224. WIA_TRACE((TEXT("device: %ws"), pszDevice ));
  225. WIA_TRACE((TEXT("name : %ws"), bstrName.BString() ));
  226. WIA_TRACE((TEXT("desc : %ws"), bstrDescription.BString() ));
  227. WIA_TRACE((TEXT("icon : %ws"), bstrIcon.BString() ));
  228. if (bRegister)
  229. {
  230. if (bstrName.BString() && bstrDescription.BString() && bstrIcon.BString())
  231. {
  232. hr = pWiaDevMgr->RegisterEventCallbackCLSID(
  233. WIA_REGISTER_EVENT_CALLBACK,
  234. pszDevice ? bstrDeviceId.BString() : NULL,
  235. &iidEvent,
  236. &clsidHandler,
  237. bstrName,
  238. bstrDescription,
  239. bstrIcon
  240. );
  241. LOG_MESSAGE((TEXT(" 0x%08X RegisterEventCallbackCLSID( WIA_REGISTER_EVENT_CALLBACK, \"%ws\" )"), hr, bstrDeviceId.BString() ));
  242. if (SUCCEEDED(hr) && bDefault)
  243. {
  244. hr = pWiaDevMgr->RegisterEventCallbackCLSID(
  245. WIA_SET_DEFAULT_HANDLER,
  246. pszDevice ? bstrDeviceId.BString() : NULL,
  247. &iidEvent,
  248. &clsidHandler,
  249. bstrName,
  250. bstrDescription,
  251. bstrIcon
  252. );
  253. LOG_MESSAGE((TEXT(" 0x%08X RegisterEventCallbackCLSID( WIA_SET_DEFAULT_HANDLER, \"%ws\" )"), hr, bstrDeviceId.BString() ));
  254. if (FAILED(hr))
  255. {
  256. WIA_PRINTHRESULT((hr,TEXT("WIA_SET_DEFAULT_HANDLER failed")));
  257. }
  258. }
  259. else if (FAILED(hr))
  260. {
  261. WIA_PRINTHRESULT((hr,TEXT("WIA_REGISTER_EVENT_CALLBACK failed")));
  262. }
  263. }
  264. }
  265. else
  266. {
  267. hr = pWiaDevMgr->RegisterEventCallbackCLSID(
  268. WIA_UNREGISTER_EVENT_CALLBACK,
  269. pszDevice ? bstrDeviceId.BString() : NULL,
  270. &iidEvent,
  271. &clsidHandler,
  272. bstrName,
  273. bstrDescription,
  274. bstrIcon
  275. );
  276. LOG_MESSAGE((TEXT(" 0x%08X RegisterEventCallbackCLSID( WIA_UNREGISTER_EVENT_CALLBACK, \"%ws\" )"), hr, bstrDeviceId.BString() ));
  277. if (FAILED(hr))
  278. {
  279. WIA_PRINTHRESULT((hr,TEXT("WIA_SET_DEFAULT_HANDLER failed")));
  280. }
  281. }
  282. }
  283. if (FAILED(hr))
  284. {
  285. WIA_PRINTHRESULT((hr,TEXT("Unable to register for the event!")));
  286. }
  287. return hr;
  288. }
  289. struct CRegistryEntry
  290. {
  291. HKEY hKeyParent;
  292. LPCTSTR pszKey;
  293. LPCTSTR pszValueName;
  294. DWORD dwType;
  295. LPARAM nValue;
  296. DWORD dwSize;
  297. bool bOverwrite;
  298. };
  299. bool SetRegistryValues( CRegistryEntry *pRegistryEntries, int nEntryCount )
  300. {
  301. if (!pRegistryEntries)
  302. {
  303. return FALSE;
  304. }
  305. //
  306. // Assume success
  307. //
  308. bool bResult = true;
  309. //
  310. // Create the registry entries
  311. //
  312. for (int i=0;i<nEntryCount && bResult;i++)
  313. {
  314. //
  315. // Create or open the key
  316. //
  317. HKEY hKey = NULL;
  318. if (ERROR_SUCCESS == RegCreateKeyEx( pRegistryEntries[i].hKeyParent, pRegistryEntries[i].pszKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ|KEY_WRITE, NULL, &hKey, NULL ))
  319. {
  320. //
  321. // If we have a value
  322. //
  323. if (pRegistryEntries[i].dwType && pRegistryEntries[i].pszValueName)
  324. {
  325. //
  326. // If we are preventing existing values from being overwritten, check to see if the value exists
  327. //
  328. if (!pRegistryEntries[i].bOverwrite)
  329. {
  330. //
  331. // Is the value present?
  332. //
  333. DWORD dwType = 0, dwSize = 0;
  334. if (ERROR_SUCCESS == RegQueryValueEx( hKey, pRegistryEntries[i].pszValueName, NULL, &dwType, NULL, &dwSize ))
  335. {
  336. //
  337. // The value exists, so continue
  338. //
  339. continue;
  340. }
  341. }
  342. if (ERROR_SUCCESS != RegSetValueEx( hKey, pRegistryEntries[i].pszValueName, NULL, pRegistryEntries[i].dwType, reinterpret_cast<CONST BYTE *>(pRegistryEntries[i].nValue), pRegistryEntries[i].dwSize ))
  343. {
  344. bResult = false;
  345. }
  346. }
  347. }
  348. }
  349. return bResult;
  350. }
  351. /////////////////////////////////////////////////////////////////////////////
  352. //
  353. extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
  354. HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int /*nShowCmd*/)
  355. {
  356. //
  357. // Save the global hInstance
  358. //
  359. g_hInstance = hInstance;
  360. LOG_MESSAGE((TEXT("Entering main: %s"), GetCommandLine()));
  361. //
  362. // Create the debugger
  363. //
  364. WIA_DEBUG_CREATE( hInstance );
  365. //
  366. // this line necessary for _ATL_MIN_CRT
  367. //
  368. lpCmdLine = GetCommandLine();
  369. //
  370. // Initialize fusion
  371. //
  372. SHFusionInitializeFromModuleID( hInstance, 123 );
  373. #if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
  374. HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
  375. #else
  376. HRESULT hRes = CoInitialize(NULL);
  377. #endif
  378. int nRet = 0;
  379. if (SUCCEEDED(hRes))
  380. {
  381. _Module.Init(ObjectMap, hInstance, &LIBID_WIAACMGRLib);
  382. _Module.dwThreadID = GetCurrentThreadId();
  383. TCHAR szTokens[] = _T("-/");
  384. //
  385. // Assume we'll be running as a server
  386. //
  387. BOOL bRun = TRUE;
  388. //
  389. // Assume we won't be selecting a device and running the wizard
  390. //
  391. BOOL bRunWizard = FALSE;
  392. //
  393. // If there are no switches, we are not going to run the server
  394. //
  395. int nSwitchCount = 0;
  396. LPCTSTR lpszToken = FindOneOf( lpCmdLine, szTokens );
  397. while (lpszToken != NULL)
  398. {
  399. //
  400. // One more switch. If we don't have any, we are going to do the selection dialog instead.
  401. //
  402. nSwitchCount++;
  403. if (lstrcmpi(lpszToken, _T("RegServer"))==0)
  404. {
  405. //
  406. // Register the server
  407. //
  408. LOG_MESSAGE((TEXT("Begin handling /RegServer")));
  409. hRes = _Module.UpdateRegistryFromResource(IDR_ACQUISITIONMANAGER, TRUE);
  410. LOG_MESSAGE((TEXT(" 0x%08X _Module.UpdateRegistryFromResource(IDR_ACQUISITIONMANAGER)"), hRes ));
  411. _Module.UpdateRegistryFromResource(IDR_MINIMALTRANSFER,TRUE);
  412. LOG_MESSAGE((TEXT(" 0x%08X _Module.UpdateRegistryFromResource(IDR_MINIMALTRANSFER)"), hRes ));
  413. _Module.UpdateRegistryFromResource(IDR_STIEVENTHANDLER,TRUE);
  414. LOG_MESSAGE((TEXT(" 0x%08X _Module.UpdateRegistryFromResource(IDR_STIEVENTHANDLER)"), hRes ));
  415. nRet = _Module.RegisterServer(TRUE);
  416. LOG_MESSAGE((TEXT(" 0x%08X _Module.RegisterServer(TRUE)"), nRet ));
  417. hRes = RegisterForWiaEvents( NULL, CLSID_AcquisitionManager, WIA_EVENT_DEVICE_CONNECTED, IDS_DOWNLOADMANAGER_NAME, IDS_DOWNLOADMANAGER_DESC, IDS_DOWNLOADMANAGER_ICON, false, true );
  418. LOG_MESSAGE((TEXT(" 0x%08X RegisterForWiaEvents( WIA_EVENT_DEVICE_CONNECTED )"), hRes ));
  419. #if defined(TESTING_STI_EVENT_HANDLER)
  420. hRes = RegisterForWiaEvents( NULL, CLSID_StiEventHandler, WIA_EVENT_DEVICE_CONNECTED, IDS_DOWNLOADMANAGER_NAME, IDS_DOWNLOADMANAGER_DESC, IDS_DOWNLOADMANAGER_ICON, false, true );
  421. MessageBox( NULL, TEXT("Test-only code"), TEXT("DEBUG"), 0 );
  422. #endif
  423. RegisterForWiaEvents( NULL, CLSID_AcquisitionManager, GUID_ScanImage, IDS_DOWNLOADMANAGER_NAME, IDS_DOWNLOADMANAGER_DESC, IDS_DOWNLOADMANAGER_ICON, false, true );
  424. LOG_MESSAGE((TEXT(" 0x%08X RegisterForWiaEvents( GUID_ScanImage )"), hRes ));
  425. bRun = FALSE;
  426. LOG_MESSAGE((TEXT("End handling /RegServer\r\n")));
  427. break;
  428. }
  429. if (lstrcmpi(lpszToken, _T("UnregServer"))==0)
  430. {
  431. _Module.UpdateRegistryFromResource(IDR_ACQUISITIONMANAGER, FALSE);
  432. _Module.UpdateRegistryFromResource(IDR_MINIMALTRANSFER,FALSE);
  433. nRet = _Module.UnregisterServer(TRUE);
  434. RegisterForWiaEvents( NULL, CLSID_AcquisitionManager, WIA_EVENT_DEVICE_CONNECTED, IDS_DOWNLOADMANAGER_NAME, IDS_DOWNLOADMANAGER_DESC, IDS_DOWNLOADMANAGER_ICON, false, false );
  435. RegisterForWiaEvents( NULL, CLSID_AcquisitionManager, GUID_ScanImage, IDS_DOWNLOADMANAGER_NAME, IDS_DOWNLOADMANAGER_DESC, IDS_DOWNLOADMANAGER_ICON, false, false );
  436. bRun = FALSE;
  437. break;
  438. }
  439. if (CSimpleString(lpszToken).ToUpper().Left(12)==CSimpleString(TEXT("SELECTDEVICE")))
  440. {
  441. bRunWizard = TRUE;
  442. bRun = FALSE;
  443. break;
  444. }
  445. if (CSimpleString(lpszToken).ToUpper().Left(10)==CSimpleString(TEXT("REGCONNECT")))
  446. {
  447. lpszToken = FindOneOf(lpszToken, TEXT(" "));
  448. WIA_TRACE((TEXT("Handling RegConnect for %s"), lpszToken ));
  449. hRes = RegisterForWiaEvents( CSimpleStringConvert::WideString(CSimpleString(lpszToken)), CLSID_MinimalTransfer, WIA_EVENT_DEVICE_CONNECTED, IDS_MINIMALTRANSFER_NAME, IDS_MINIMALTRANSFER_DESC, IDS_MINIMALTRANSFER_ICON, true, true );
  450. bRun = FALSE;
  451. break;
  452. }
  453. if (CSimpleString(lpszToken).ToUpper().Left(12)==CSimpleString(TEXT("UNREGCONNECT")))
  454. {
  455. lpszToken = FindOneOf(lpszToken, TEXT(" "));
  456. WIA_TRACE((TEXT("Handling RegUnconnect for %s"), lpszToken ));
  457. hRes = RegisterForWiaEvents( CSimpleStringConvert::WideString(CSimpleString(lpszToken)), CLSID_MinimalTransfer, WIA_EVENT_DEVICE_CONNECTED, IDS_MINIMALTRANSFER_NAME, IDS_MINIMALTRANSFER_DESC, IDS_MINIMALTRANSFER_ICON, false, false );
  458. bRun = FALSE;
  459. break;
  460. }
  461. lpszToken = FindOneOf(lpszToken, szTokens);
  462. }
  463. //
  464. // if /SelectDevice was specified, or no arguments were specified, we want to start the wizard
  465. //
  466. if (bRunWizard || !nSwitchCount)
  467. {
  468. HRESULT hr = RunWiaWizard::RunWizard( NULL, NULL, TEXT("WiaWizardSingleInstanceDeviceSelection") );
  469. if (FAILED(hr))
  470. {
  471. MessageBox( NULL, CSimpleString( IDS_NO_DEVICE_TEXT, g_hInstance ), CSimpleString( IDS_ERROR_TITLE, g_hInstance ), MB_ICONHAND );
  472. }
  473. }
  474. //
  475. // Otherwise run embedded
  476. //
  477. else if (bRun)
  478. {
  479. _Module.StartMonitor();
  480. #if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
  481. hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED);
  482. _ASSERTE(SUCCEEDED(hRes));
  483. hRes = CoResumeClassObjects();
  484. #else
  485. hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE);
  486. #endif
  487. _ASSERTE(SUCCEEDED(hRes));
  488. MSG msg;
  489. while (GetMessage(&msg, 0, 0, 0))
  490. DispatchMessage(&msg);
  491. _Module.RevokeClassObjects();
  492. }
  493. _Module.Term();
  494. CoUninitialize();
  495. }
  496. //
  497. // Uninitialize fusion
  498. //
  499. SHFusionUninitialize();
  500. WIA_REPORT_LEAKS();
  501. WIA_DEBUG_DESTROY();
  502. LOG_MESSAGE((TEXT("Exiting main\r\n")));
  503. return nRet;
  504. }
  505. #endif // DBG_GENERATE_PRETEND_EVENT