Leaked source code of windows server 2003
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.

720 lines
22 KiB

  1. /*****************************************************************************
  2. *
  3. * DIApHack.c
  4. *
  5. * Copyright (c) 1999 Microsoft Corporation. All Rights Reserved.
  6. *
  7. * Abstract:
  8. *
  9. * Support routines for app hacks
  10. *
  11. * Contents:
  12. *
  13. *****************************************************************************/
  14. #include "dinputpr.h"
  15. /*****************************************************************************
  16. *
  17. * The sqiffle for this file.
  18. *
  19. *****************************************************************************/
  20. //ISSUE-2001/03/29-timgill Need to sort out a prefixed version of of SquirtSqflPtszV
  21. #define sqfl sqflCompat
  22. typedef enum
  23. {
  24. DICOMPATID_REACQUIRE, // Perform auto reaquire if device lost
  25. DICOMPATID_NOSUBCLASS, // Do not use subclassing
  26. DICOMPATID_MAXDEVICENAMELENGTH
  27. } DIAPPHACKID, *LPDIAPPHACKID;
  28. typedef struct tagAPPHACKENTRY
  29. {
  30. LPCTSTR pszName;
  31. DWORD cbData;
  32. DWORD dwOSMask;
  33. } APPHACKENTRY, *LPAPPHACKENTRY;
  34. typedef struct tagAPPHACKTABLE
  35. {
  36. LPAPPHACKENTRY aEntries;
  37. ULONG cEntries;
  38. } APPHACKTABLE, *LPAPPHACKTABLE;
  39. #define BEGIN_DECLARE_APPHACK_ENTRIES(name) \
  40. APPHACKENTRY name[] = {
  41. #define DECLARE_APPHACK_ENTRY(name, type, osmask) \
  42. { TEXT(#name), sizeof(type), osmask },
  43. #define END_DECLARE_APPHACK_ENTRIES() \
  44. };
  45. #define BEGIN_DECLARE_APPHACK_TABLE(name) \
  46. APPHACKTABLE name =
  47. #define DECLARE_APPHACK_TABLE(entries) \
  48. { entries, cA(entries) }
  49. #define END_DECLARE_APPHACK_TABLE() \
  50. ;
  51. #define DIHACKOS_WIN2K (0x00000001L)
  52. #define DIHACKOS_WIN9X (0x00000002L)
  53. BEGIN_DECLARE_APPHACK_ENTRIES(g_aheAppHackEntries)
  54. DECLARE_APPHACK_ENTRY(ReAcquire, BOOL, DIHACKOS_WIN2K )
  55. DECLARE_APPHACK_ENTRY(NoSubClass, BOOL, DIHACKOS_WIN2K )
  56. DECLARE_APPHACK_ENTRY(MaxDeviceNameLength, DWORD, DIHACKOS_WIN2K | DIHACKOS_WIN9X )
  57. END_DECLARE_APPHACK_ENTRIES()
  58. BEGIN_DECLARE_APPHACK_TABLE(g_ahtAppHackTable)
  59. DECLARE_APPHACK_TABLE(g_aheAppHackEntries)
  60. END_DECLARE_APPHACK_TABLE()
  61. /***************************************************************************
  62. *
  63. * AhGetOSMask
  64. *
  65. * Description:
  66. * Gets the mask for the current OS
  67. * This mask should be used when we get app hacks for more than just
  68. * Win2k such that hacks can be applied selectively per OS.
  69. * For now just #define a value as constant.
  70. *
  71. * Arguments:
  72. * none
  73. *
  74. * Returns:
  75. * DWORD: Mask of flags applicable for the current OS.
  76. *
  77. ***************************************************************************/
  78. #ifdef WINNT
  79. #define AhGetOSMask() DIHACKOS_WIN2K
  80. #else
  81. #define AhGetOSMask() DIHACKOS_WIN9X
  82. #endif
  83. /***************************************************************************
  84. *
  85. * @doc INTERNAL
  86. *
  87. * @func BOOL | AhGetCurrentApplicationPath |
  88. *
  89. * Gets the full path to the current application's executable.
  90. *
  91. * @parm OUT LPTSTR | pszPath |
  92. *
  93. * The executable full path name
  94. *
  95. * @parm OUT LPTSTR * | ppszModule |
  96. *
  97. * Pointer to the first character within the above of the module name
  98. *
  99. * @returns
  100. *
  101. * TRUE on success.
  102. *
  103. ***************************************************************************/
  104. BOOL AhGetCurrentApplicationPath
  105. (
  106. LPTSTR pszPath,
  107. LPTSTR * ppszModule
  108. )
  109. {
  110. BOOL fSuccess = TRUE;
  111. TCHAR szOriginal[MAX_PATH];
  112. EnterProcI(AhGetCurrentApplicationPath, (_ ""));
  113. fSuccess = GetModuleFileName(GetModuleHandle(NULL), szOriginal, cA(szOriginal));
  114. if(fSuccess)
  115. {
  116. fSuccess = ( GetFullPathName(szOriginal, MAX_PATH, pszPath, ppszModule) != 0 );
  117. }
  118. ExitProcF(fSuccess);
  119. return fSuccess;
  120. }
  121. /*****************************************************************************
  122. *
  123. * @doc INTERNAL
  124. *
  125. * @func BOOL | AhGetAppDateAndFileLen |
  126. *
  127. * Gets the data time stamp and file length of the current
  128. * application as used to identify the application.
  129. *
  130. * @parm IN LPTSTR | pszExecutable |
  131. *
  132. * The executable full path name.
  133. *
  134. * @returns
  135. *
  136. * TRUE on success.
  137. *
  138. *****************************************************************************/
  139. BOOL AhGetAppDateAndFileLen
  140. (
  141. LPTSTR pszExecutable
  142. )
  143. {
  144. HANDLE hFile = NULL;
  145. IMAGE_NT_HEADERS nth;
  146. IMAGE_DOS_HEADER dh;
  147. DWORD cbRead;
  148. BOOL fSuccess;
  149. EnterProcI(AhGetApplicationId, (_ ""));
  150. // Open the executable
  151. hFile = CreateFile( pszExecutable, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
  152. if( hFile && ( hFile != INVALID_HANDLE_VALUE ) )
  153. {
  154. // Read the executable's DOS header
  155. fSuccess = ReadFile(hFile, &dh, sizeof(dh), &cbRead, NULL);
  156. if(!fSuccess || sizeof(dh) != cbRead)
  157. {
  158. SquirtSqflPtszV(sqfl | sqflError, TEXT("Unable to read DOS header") );
  159. fSuccess = FALSE;
  160. }
  161. if(fSuccess && IMAGE_DOS_SIGNATURE != dh.e_magic)
  162. {
  163. SquirtSqflPtszV(sqfl | sqflError, TEXT("Invalid DOS signature") );
  164. fSuccess = FALSE;
  165. }
  166. // Read the executable's PE header
  167. if(fSuccess)
  168. {
  169. cbRead = SetFilePointer(hFile, dh.e_lfanew, NULL, FILE_BEGIN);
  170. if((LONG)cbRead != dh.e_lfanew)
  171. {
  172. SquirtSqflPtszV(sqfl | sqflError, TEXT("Unable to seek to PE header") );
  173. fSuccess = FALSE;
  174. }
  175. }
  176. if(fSuccess)
  177. {
  178. fSuccess = ReadFile(hFile, &nth, sizeof(nth), &cbRead, NULL);
  179. if(!fSuccess || sizeof(nth) != cbRead)
  180. {
  181. SquirtSqflPtszV(sqfl | sqflError, TEXT("Unable to read PE header") );
  182. fSuccess = FALSE;
  183. }
  184. }
  185. if(fSuccess && IMAGE_NT_SIGNATURE != nth.Signature)
  186. {
  187. SquirtSqflPtszV(sqfl | sqflError, TEXT("Invalid PE signature") );
  188. fSuccess = FALSE;
  189. }
  190. // Get the executable's size
  191. if(fSuccess)
  192. {
  193. g_dwAppDate = nth.FileHeader.TimeDateStamp;
  194. // Assuming < 4 GB
  195. g_dwAppFileLen = GetFileSize(hFile, NULL);
  196. if( (DWORD)(-1) == g_dwAppFileLen )
  197. {
  198. SquirtSqflPtszV(sqfl | sqflError, TEXT("Unable to get file size") );
  199. fSuccess = FALSE;
  200. }
  201. }
  202. // Clean up
  203. CloseHandle( hFile );
  204. }
  205. else
  206. {
  207. SquirtSqflPtszV(sqfl | sqflError, TEXT("CreateFile failed to open %s with error %lu"),
  208. pszExecutable, GetLastError() );
  209. fSuccess = FALSE;
  210. }
  211. ExitProcF(fSuccess);
  212. return fSuccess;
  213. }
  214. /***************************************************************************
  215. *
  216. * AhOpenApplicationKey
  217. *
  218. * Description:
  219. * Opens or creates the application's root key.
  220. *
  221. * Arguments:
  222. * LPCTSTR [in]: application id.
  223. *
  224. * Returns:
  225. * HKEY: registry key handle.
  226. *
  227. ***************************************************************************/
  228. HKEY AhOpenApplicationKey
  229. (
  230. LPCTSTR pszAppId
  231. )
  232. {
  233. #ifdef DEBUG
  234. TCHAR szName[0x100] = { 0 };
  235. LONG cbName = sizeof(szName);
  236. #endif // DEBUG
  237. HKEY hkeyAll = NULL;
  238. HKEY hkeyApp = NULL;
  239. HRESULT hr;
  240. EnterProcI(AhOpenApplicationKey, (_ ""));
  241. // Open the parent key
  242. hr = hresMumbleKeyEx( HKEY_LOCAL_MACHINE,
  243. REGSTR_PATH_DINPUT TEXT("\\") REGSTR_KEY_APPHACK, KEY_READ, 0, &hkeyAll );
  244. if(SUCCEEDED(hr))
  245. {
  246. hr = hresMumbleKeyEx( hkeyAll, pszAppId, KEY_READ, 0, &hkeyApp );
  247. RegCloseKey( hkeyAll );
  248. #ifdef DEBUG
  249. // Query for the application description
  250. if(SUCCEEDED(hr))
  251. {
  252. JoyReg_GetValue( hkeyApp, NULL, REG_SZ, szName, cbName );
  253. SquirtSqflPtszV(sqfl | sqflTrace,
  254. TEXT( "Application description: %ls"), szName );
  255. }
  256. #endif // DEBUG
  257. }
  258. ExitProc();
  259. return hkeyApp;
  260. }
  261. /***************************************************************************
  262. *
  263. * AhGetHackValue
  264. *
  265. * Description:
  266. * Queries an apphack value.
  267. *
  268. * Arguments:
  269. * HKEY [in]: application registry key.
  270. * DSAPPHACKID [in]: apphack id.
  271. * LPVOID [out]: receives apphack data.
  272. * DWORD [in]: size of above data buffer.
  273. *
  274. * Returns:
  275. * BOOL: TRUE on success.
  276. *
  277. ***************************************************************************/
  278. BOOL AhGetHackValue
  279. (
  280. HKEY hkey,
  281. DWORD dwOSMask,
  282. DIAPPHACKID ahid,
  283. LPVOID pvData,
  284. DWORD cbData
  285. )
  286. {
  287. HRESULT hr;
  288. EnterProcI(AhGetHackValue, (_ ""));
  289. AssertF(ahid < (DIAPPHACKID)g_ahtAppHackTable.cEntries);
  290. AssertF(cbData == g_ahtAppHackTable.aEntries[ahid].cbData);
  291. if( !( dwOSMask & g_ahtAppHackTable.aEntries[ahid].dwOSMask ) )
  292. {
  293. hr = DI_OK;
  294. }
  295. else
  296. {
  297. hr = JoyReg_GetValue( hkey, g_ahtAppHackTable.aEntries[ahid].pszName,
  298. REG_BINARY, pvData, cbData );
  299. if( !SUCCEEDED( hr ) )
  300. {
  301. SquirtSqflPtszV(sqfl | sqflBenign,
  302. TEXT("failed to read value \"%s\", code 0x%08x"),
  303. g_ahtAppHackTable.aEntries[ahid].pszName, hr);
  304. }
  305. }
  306. ExitProcF(DI_OK == hr);
  307. return DI_OK == hr;
  308. }
  309. /***************************************************************************
  310. *
  311. * AhGetAppHacks
  312. *
  313. * Description:
  314. * Gets all app-hacks for the current application.
  315. *
  316. * Arguments:
  317. * LPDSAPPHACKS [out]: receives app-hack data.
  318. *
  319. * Returns:
  320. * BOOL: TRUE if any apphacks exist for the current application.
  321. *
  322. ***************************************************************************/
  323. BOOL AhGetAppHacks
  324. (
  325. LPTSTR tszAppId
  326. )
  327. {
  328. DIAPPHACKS ahTemp;
  329. HKEY hkey = NULL;
  330. BOOL fFound;
  331. DWORD dwOSMask;
  332. EnterProcI(AhGetAppHacks, (_ "s", tszAppId));
  333. /*
  334. * Assume defaults as most apps will have no registry entries
  335. */
  336. ahTemp = g_AppHacks;
  337. /*
  338. * Get the OS version mask
  339. */
  340. dwOSMask = AhGetOSMask();
  341. SquirtSqflPtszV(sqfl | sqflTrace, TEXT("Finding apphacks for %s..."), tszAppId);
  342. /*
  343. * Open the application key
  344. */
  345. hkey = AhOpenApplicationKey(tszAppId);
  346. #define GET_APP_HACK( hackid, field ) \
  347. if( !AhGetHackValue( hkey, dwOSMask, hackid, &ahTemp.##field, sizeof(ahTemp.##field) ) ) \
  348. { \
  349. ahTemp.##field = g_AppHacks.##field; \
  350. }
  351. /*
  352. * Query all apphack values
  353. */
  354. if( hkey && (hkey != INVALID_HANDLE_VALUE ) )
  355. {
  356. GET_APP_HACK( DICOMPATID_REACQUIRE, fReacquire );
  357. GET_APP_HACK( DICOMPATID_NOSUBCLASS, fNoSubClass );
  358. GET_APP_HACK( DICOMPATID_MAXDEVICENAMELENGTH, nMaxDeviceNameLength );
  359. /*
  360. * Copy back over the defaults
  361. */
  362. g_AppHacks = ahTemp;
  363. SquirtSqflPtszV(sqfl | sqflTrace, TEXT("fReacquire: %d"), g_AppHacks.fReacquire );
  364. SquirtSqflPtszV(sqfl | sqflTrace, TEXT("fNoSubClass: %d"), g_AppHacks.fNoSubClass );
  365. SquirtSqflPtszV(sqfl | sqflTrace, TEXT("nMaxDeviceNameLength: %d"), g_AppHacks.nMaxDeviceNameLength );
  366. RegCloseKey(hkey);
  367. fFound = TRUE;
  368. }
  369. else
  370. {
  371. SquirtSqflPtszV(sqfl | sqflTrace, TEXT("No apphacks exist") );
  372. fFound = FALSE;
  373. }
  374. #undef GET_APP_HACK
  375. ExitProc();
  376. return fFound;
  377. }
  378. /*****************************************************************************
  379. *
  380. * @doc INTERNAL
  381. *
  382. * @func HRESULT | AhAppRegister |
  383. *
  384. * Registers an app
  385. * ISSUE-2001/03/29-timgill Needs more function documentation
  386. *
  387. * @parm IN DWORD | dwVer |
  388. *
  389. * The version of DInput for which the application was compiled
  390. *
  391. * @parm IN DWORD | dwMapper |
  392. *
  393. * 0 if the caller
  394. *
  395. * @returns
  396. *
  397. * TRUE on success.
  398. *
  399. *****************************************************************************/
  400. HRESULT EXTERNAL AhAppRegister(DWORD dwVer, DWORD dwMapper)
  401. {
  402. HRESULT hres = S_OK;
  403. EnterProcI(AhAppRegister, (_ "xx", dwVer, dwMapper));
  404. /*
  405. * It is important to serialize this global processing here so that
  406. * elsewhere we can make read-only access to the data set up without
  407. * making any further checks. However if an application has already
  408. * been found to use the mapper we can safely test that. If another
  409. * thread is just about to set g_dwLastMsgSent to
  410. * DIMSGWP_DX8MAPPERAPPSTART then it will be done by the time we get
  411. * the critical section and we'll just find that there's nothing to
  412. * update and no point in sending a message.
  413. * DIMSGWP_DX8MAPPERAPPSTART is the only case that needs to be fast
  414. * as the other cases normally only happen once.
  415. */
  416. if( g_dwLastMsgSent == DIMSGWP_DX8MAPPERAPPSTART )
  417. {
  418. /*
  419. * Fast out if everything has been done already
  420. */
  421. }
  422. else
  423. {
  424. TCHAR szExecutable[MAX_PATH];
  425. LPTSTR pszModule;
  426. TCHAR szAppId[MAX_PATH + 8 +8];
  427. DWORD dwAppStat = 0;
  428. BOOL fSuccess;
  429. hres = E_FAIL;
  430. DllEnterCrit();
  431. // Get the application path
  432. if( AhGetCurrentApplicationPath( szExecutable, &pszModule ) )
  433. {
  434. if( !g_dwLastMsgSent )
  435. {
  436. SquirtSqflPtszV(sqfl | sqflVerbose, TEXT("Application executable path: %s"), szExecutable);
  437. SquirtSqflPtszV(sqfl | sqflVerbose, TEXT("Application module: %s"), pszModule);
  438. fSuccess = AhGetAppDateAndFileLen( szExecutable );
  439. }
  440. else
  441. {
  442. fSuccess = TRUE;
  443. }
  444. if( fSuccess )
  445. {
  446. HKEY hKeyMain;
  447. hres = hresMumbleKeyEx( HKEY_CURRENT_USER,
  448. REGSTR_PATH_DINPUT, KEY_READ | KEY_WRITE, 0, &hKeyMain );
  449. if( SUCCEEDED( hres ) )
  450. {
  451. HKEY hKey;
  452. DWORD dwAppIdFlag;
  453. DWORD dwAppDate = g_dwAppDate, dwAppSize = g_dwAppFileLen;
  454. DWORD cb = cbX(dwAppIdFlag);
  455. if( ERROR_SUCCESS == RegQueryValueEx( hKeyMain, DIRECTINPUT_REGSTR_VAL_APPIDFLAG,
  456. 0, 0, (PUCHAR)&dwAppIdFlag, &cb ) )
  457. {
  458. SquirtSqflPtszV(sqfl | sqflVerbose,
  459. TEXT("AppIdFlag: %d"), dwAppIdFlag );
  460. if( dwAppIdFlag & DIAPPIDFLAG_NOTIME ){
  461. dwAppDate = 0;
  462. }
  463. if( dwAppIdFlag & DIAPPIDFLAG_NOSIZE ){
  464. dwAppSize = 0;
  465. }
  466. }
  467. CharUpper( pszModule );
  468. wsprintf( szAppId, TEXT("%s%8.8lX%8.8lX"), pszModule, dwAppDate, dwAppSize );
  469. SquirtSqflPtszV( sqfl | sqflTrace, TEXT("Application id: %s"), szAppId );
  470. /*
  471. * We must only get app hacks once, otherwise a FF driver
  472. * that uses DInput will corrupt the application app hacks
  473. */
  474. if( !g_dwLastMsgSent )
  475. {
  476. AhGetAppHacks( szAppId );
  477. }
  478. /*
  479. * See if this app has been registered before.
  480. */
  481. if( ERROR_SUCCESS == RegOpenKeyEx( hKeyMain, szAppId, 0, KEY_READ, &hKey ) )
  482. {
  483. cb = cbX(dwAppStat);
  484. if( ERROR_SUCCESS == RegQueryValueEx( hKey, DIRECTINPUT_REGSTR_VAL_MAPPER,
  485. 0, 0, (PUCHAR)&dwAppStat, &cb ) )
  486. {
  487. SquirtSqflPtszV(sqfl | sqflVerbose,
  488. TEXT("Previous application mapper state: %d"), dwAppStat );
  489. if( dwAppStat )
  490. {
  491. dwAppStat = DIMSGWP_DX8MAPPERAPPSTART;
  492. dwMapper = 1;
  493. }
  494. else
  495. {
  496. dwAppStat = DIMSGWP_DX8APPSTART;
  497. }
  498. }
  499. else
  500. {
  501. SquirtSqflPtszV(sqfl | sqflBenign, TEXT("Missing ")
  502. DIRECTINPUT_REGSTR_VAL_MAPPER TEXT(" value for %s"), szAppId);
  503. dwAppStat = DIMSGWP_NEWAPPSTART;
  504. }
  505. RegCloseKey( hKey );
  506. }
  507. else
  508. {
  509. SquirtSqflPtszV(sqfl | sqflVerbose,
  510. TEXT("Unknown application") );
  511. dwAppStat = DIMSGWP_NEWAPPSTART;
  512. }
  513. /*
  514. * Write out the application key if none existed or if
  515. * we've just found out that this app uses the mapper.
  516. */
  517. if( ( dwAppStat == DIMSGWP_NEWAPPSTART )
  518. || ( ( dwAppStat == DIMSGWP_DX8APPSTART ) && dwMapper ) )
  519. {
  520. hres = hresMumbleKeyEx( hKeyMain, szAppId, KEY_WRITE, 0, &hKey );
  521. if( SUCCEEDED(hres) )
  522. {
  523. RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_NAME, 0x0, REG_SZ, (PUCHAR)pszModule, cbCtch(lstrlen(pszModule)+1) );
  524. RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_MAPPER, 0x0, REG_BINARY, (PUCHAR) &dwMapper, cbX(dwMapper));
  525. RegCloseKey(hKey);
  526. }
  527. /*
  528. * Make dwAppStat the state to be sent if all goes well
  529. */
  530. if( dwMapper )
  531. {
  532. dwAppStat = DIMSGWP_DX8MAPPERAPPSTART;
  533. }
  534. }
  535. if( SUCCEEDED(hres) && ( g_dwLastMsgSent != dwAppStat ) )
  536. {
  537. hres = hresMumbleKeyEx( hKeyMain, ( dwMapper ) ? DIRECTINPUT_REGSTR_KEY_LASTMAPAPP
  538. : DIRECTINPUT_REGSTR_KEY_LASTAPP,
  539. KEY_WRITE, 0, &hKey );
  540. if( SUCCEEDED(hres) )
  541. {
  542. FILETIME ftSysTime;
  543. GetSystemTimeAsFileTime( &ftSysTime );
  544. /*
  545. * Update g_dwLastMsgSent while we're still in the
  546. * critical section.
  547. */
  548. g_dwLastMsgSent = dwAppStat;
  549. RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_NAME, 0x0, REG_SZ, (PUCHAR)pszModule, cbCtch(lstrlen(pszModule)+1));
  550. RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_ID, 0x0, REG_SZ, (PUCHAR) szAppId, cbCtch(lstrlen(szAppId)+1) );
  551. RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_VERSION, 0x0, REG_BINARY, (PUCHAR) &dwVer, cbX(dwVer));
  552. RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_LASTSTART, 0x0, REG_BINARY, (PUCHAR)&ftSysTime, cbX(ftSysTime));
  553. RegCloseKey(hKey);
  554. }
  555. else
  556. {
  557. /*
  558. * Zero dwAppStat to indicate that no message
  559. * should be sent.
  560. */
  561. dwAppStat = 0;
  562. }
  563. }
  564. else
  565. {
  566. /*
  567. * Zero dwAppStat to indicate that no message
  568. * should be sent.
  569. */
  570. dwAppStat = 0;
  571. }
  572. RegCloseKey(hKeyMain);
  573. }
  574. else
  575. {
  576. SquirtSqflPtszV(sqfl | sqflError,
  577. TEXT("Failed to open DirectInput application status key (0x%08x)"), hres );
  578. }
  579. }
  580. else
  581. {
  582. SquirtSqflPtszV(sqfl | sqflError,
  583. TEXT("Failed to get application details") );
  584. }
  585. }
  586. else
  587. {
  588. SquirtSqflPtszV(sqfl | sqflError,
  589. TEXT("Failed to get application path") );
  590. }
  591. DllLeaveCrit();
  592. /*
  593. * If there's a message to send, broadcast it outside the critical section
  594. */
  595. if( dwAppStat )
  596. {
  597. PostMessage( HWND_BROADCAST, g_wmDInputNotify, dwAppStat, 0L);
  598. }
  599. }
  600. ExitOleProc();
  601. return hres;
  602. }