Windows NT 4.0 source code leak
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.

719 lines
20 KiB

4 years ago
  1. /**********************************************************************
  2. Copyright (C) 1992-1993 Microsoft Corporation. All Rights Reserved.
  3. mididev.c
  4. DESCRIPTION:
  5. Code to match device ID's with associated registry entries
  6. HISTORY:
  7. 02/24/95 [jimge] created.
  8. *********************************************************************/
  9. #include <windows.h>
  10. #include <windowsx.h>
  11. #include <winerror.h>
  12. #include <regstr.h>
  13. #include <mmsystem.h>
  14. #include <mmddk.h>
  15. #include "idf.h"
  16. #include "midimap.h"
  17. #include "debug.h"
  18. typedef struct tagMDEV_NODE *PMDEV_NODE;
  19. typedef struct tagMDEV_NODE
  20. {
  21. PMDEV_NODE pNext;
  22. TCHAR szAlias[CB_MAXALIAS];
  23. DWORD dwDevNode;
  24. TCHAR szDriver[CB_MAXDRIVER];
  25. UINT uDeviceID;
  26. UINT uPort;
  27. BOOL fNewDriver;
  28. } MDEV_NODE;
  29. static TCHAR BCODE gszMediaRsrcKey[] =
  30. REGSTR_PATH_MEDIARESOURCES TEXT ("\\MIDI");
  31. static TCHAR BCODE gszDriverKey[] =
  32. REGSTR_PATH_MEDIARESOURCES TEXT ("\\MIDI\\%s");
  33. static TCHAR BCODE gszDriverValue[] = TEXT ("Driver");
  34. static TCHAR BCODE gszDevNodeValue[] = TEXT ("DevNode");
  35. static TCHAR BCODE gszPortValue[] = TEXT ("Port");
  36. static TCHAR BCODE gszActiveValue[] = TEXT ("Active");
  37. static TCHAR BCODE gszMapperConfig[] = TEXT ("MapperConfig");
  38. static PMDEV_NODE gpMDevList = NULL;
  39. static DWORD gdwNewDrivers = (DWORD)-1L;
  40. PRIVATE BOOL FNLOCAL mdev_BuildRegList(
  41. void);
  42. PRIVATE BOOL FNLOCAL mdev_SyncDeviceIDs(
  43. void);
  44. PRIVATE BOOL FNLOCAL mdev_MarkActiveDrivers(
  45. void);
  46. #ifdef DEBUG
  47. PRIVATE VOID FNLOCAL mdev_ListActiveDrivers(
  48. void);
  49. #endif
  50. #ifdef WINNT
  51. PRIVATE VOID FNLOCAL mdev_Migrate(
  52. void);
  53. #endif
  54. BOOL FNGLOBAL mdev_Init(
  55. void)
  56. {
  57. if (gpMDevList)
  58. mdev_Free();
  59. #ifdef WINNT
  60. // Make sure registry keys are correct
  61. mdev_Migrate();
  62. #endif // End WINNT
  63. if ((!mdev_BuildRegList()) ||
  64. (!mdev_SyncDeviceIDs()) ||
  65. (!mdev_MarkActiveDrivers()))
  66. {
  67. mdev_Free();
  68. return FALSE;
  69. }
  70. #ifdef DEBUG
  71. mdev_ListActiveDrivers();
  72. #endif
  73. return TRUE;
  74. }
  75. //
  76. // mdev_BuildRegList
  77. //
  78. // Builds the base device list out of the registry
  79. //
  80. // Assumes the list has been cleared
  81. //
  82. // For each alias (key) under MediaResources\MIDI
  83. // Make sure the Active value exists and is '1'
  84. // Allocate a list node
  85. // Try to read the alias's devnode
  86. // If the alias's devnode is 0 or missing,
  87. // Read the alias's driver name
  88. // Read the alias's port number
  89. // Add the alias to the global list
  90. //
  91. // The uDeviceID member will not be initialized by this routine;
  92. // mdev_SyncDeviceIDs must be called to figure out the current
  93. // device ID mapping.
  94. //
  95. PRIVATE BOOL FNLOCAL mdev_BuildRegList(
  96. void)
  97. {
  98. BOOL fRet = FALSE;
  99. HKEY hKeyMediaRsrc = NULL;
  100. HKEY hKeyThisAlias = NULL;
  101. DWORD dwEnumAlias = 0;
  102. LPTSTR pstrAlias = NULL;
  103. PMDEV_NODE pmd = NULL;
  104. TCHAR szActive[2];
  105. DWORD dwPort;
  106. DWORD cbValue;
  107. DWORD dwType;
  108. DWORD dwMapperConfig;
  109. cbValue = CB_MAXALIAS * sizeof(TCHAR);
  110. pstrAlias = (LPTSTR)LocalAlloc(LPTR, CB_MAXALIAS);
  111. if (NULL == pstrAlias)
  112. {
  113. DPF(1, TEXT ("mdev_Init: Out of memory"));
  114. goto mBRL_Cleanup;
  115. }
  116. if (ERROR_SUCCESS != RegOpenKey(HKEY_LOCAL_MACHINE,
  117. gszMediaRsrcKey,
  118. &hKeyMediaRsrc))
  119. {
  120. DPF(1, TEXT ("mdev_Init: Could not open ...\\MediaResoruces\\MIDI"));
  121. goto mBRL_Cleanup;
  122. }
  123. while (ERROR_SUCCESS == RegEnumKey(hKeyMediaRsrc,
  124. dwEnumAlias++,
  125. pstrAlias,
  126. CB_MAXALIAS))
  127. {
  128. if (ERROR_SUCCESS != (RegOpenKey(hKeyMediaRsrc,
  129. pstrAlias,
  130. &hKeyThisAlias)))
  131. {
  132. DPF(1, TEXT ("mdev_Init: Could not open enum'ed key %s"), (LPTSTR)pstrAlias);
  133. continue;
  134. }
  135. // MUST have Active == "1" to be running
  136. //
  137. cbValue = sizeof(szActive);
  138. if (ERROR_SUCCESS != (RegQueryValueEx(hKeyThisAlias,
  139. gszActiveValue,
  140. NULL,
  141. &dwType,
  142. (LPSTR)szActive,
  143. &cbValue)) ||
  144. *szActive != '1')
  145. {
  146. DPF(2, TEXT ("mdev_Init: Device %s exists but is not loaded."),
  147. (LPTSTR)pstrAlias);
  148. RegCloseKey(hKeyThisAlias);
  149. continue;
  150. }
  151. // Determine if we have ever configured with this driver before
  152. //
  153. cbValue = sizeof(dwMapperConfig);
  154. if (ERROR_SUCCESS != (RegQueryValueEx(hKeyThisAlias,
  155. gszMapperConfig,
  156. NULL,
  157. &dwType,
  158. (LPSTR)&dwMapperConfig,
  159. &cbValue)))
  160. dwMapperConfig = 0;
  161. #ifdef DEBUG
  162. if (!dwMapperConfig)
  163. DPF(1, TEXT ("Alias '%s' is a new driver."),
  164. (LPTSTR)pstrAlias);
  165. #endif
  166. // We have a running driver, go ahead and alloc a node
  167. // for it
  168. //
  169. pmd = (PMDEV_NODE)LocalAlloc(LPTR, sizeof(*pmd));
  170. if (NULL == pmd)
  171. {
  172. DPF(1, TEXT ("mdev_Init: Out of memory allocating node for %s"),
  173. (LPTSTR)pstrAlias);
  174. RegCloseKey(hKeyThisAlias);
  175. continue;
  176. }
  177. lstrcpyn(pmd->szAlias, pstrAlias, sizeof(pmd->szAlias) - 1);
  178. pmd->szAlias[sizeof(pmd->szAlias) - 1] = '\0';
  179. pmd->fNewDriver = (dwMapperConfig ? FALSE : TRUE);
  180. // Try to get the DevNode value
  181. //
  182. cbValue = sizeof(pmd->dwDevNode);
  183. if (ERROR_SUCCESS != RegQueryValueEx(hKeyThisAlias,
  184. gszDevNodeValue,
  185. NULL,
  186. &dwType,
  187. (LPSTR)(LPDWORD)&pmd->dwDevNode,
  188. &cbValue))
  189. {
  190. // Ok to not have a devnode value, 3.1 drivers don't
  191. //
  192. DPF(2, TEXT ("mdev_Init: Device %s has no devnode; must be 3.1"),
  193. (LPTSTR)pstrAlias);
  194. pmd->dwDevNode = 0;
  195. }
  196. // Leave something reasonable in driver even if we don't
  197. // expect to use it
  198. //
  199. *pmd->szDriver = '\0';
  200. // If we didn't get a devnode or it was 0, and we can't find the
  201. // driver name to match against, we can't use this entry. (If it
  202. // has no ring 3 driver, it can't be running anyway).
  203. //
  204. if (!pmd->dwDevNode)
  205. {
  206. cbValue = sizeof(pmd->szDriver);
  207. if (ERROR_SUCCESS != RegQueryValueEx(
  208. hKeyThisAlias,
  209. gszDriverValue,
  210. NULL,
  211. &dwType,
  212. (LPSTR)pmd->szDriver,
  213. &cbValue))
  214. {
  215. DPF(1, TEXT ("mdev_Init: Device %s has no ring 3 driver entry"),
  216. (LPTSTR)pstrAlias);
  217. LocalFree((HLOCAL)pmd);
  218. RegCloseKey(hKeyThisAlias);
  219. continue;
  220. }
  221. }
  222. // Success! Now try to figure out the port number
  223. //
  224. cbValue = sizeof(dwPort);
  225. // Guard against INF's which only specify a byte's worth of
  226. // port value
  227. //
  228. dwPort = 0;
  229. if (ERROR_SUCCESS != RegQueryValueEx(hKeyThisAlias,
  230. gszPortValue,
  231. NULL,
  232. &dwType,
  233. (LPSTR)(LPDWORD)&dwPort,
  234. &cbValue))
  235. {
  236. DPF(2, TEXT ("mdev_Init: Device %s has no port entry; using 0."),
  237. (LPTSTR)pstrAlias);
  238. dwPort = 0;
  239. }
  240. pmd->uPort = (UINT)dwPort;
  241. // We have a valid node, put it into the list
  242. //
  243. pmd->pNext = gpMDevList;
  244. gpMDevList = pmd;
  245. RegCloseKey(hKeyThisAlias);
  246. }
  247. fRet = TRUE;
  248. mBRL_Cleanup:
  249. if (hKeyMediaRsrc) RegCloseKey(hKeyMediaRsrc);
  250. if (pstrAlias) LocalFree((HLOCAL)pstrAlias);
  251. return fRet;
  252. }
  253. //
  254. // mdev_SyncDeviceIDs
  255. //
  256. // Traverse the device list and bring the uDeviceID members up to date.
  257. // Also remove any devices which MMSYSTEM claims are not really running.
  258. //
  259. // NOTE: The uDeviceID member is actually the device ID of the base driver.
  260. // If you want to open the device, you have to add uDeviceID and uPort for
  261. // the node you want to open.
  262. //
  263. // Set all uDeviceID's to NO_DEVICEID
  264. //
  265. // For each base device ID in MMSYSTEM (i.e. port 0 on each loaded driver)
  266. // Get the matching alias from MMSYSTEM
  267. // Locate the node with that alias in the device list
  268. // Set that node's uDeviceID
  269. //
  270. // For each node in the device list with non-zero port
  271. // If this node has a DevNode
  272. // Find a matching node by DevNode with port == 0 and get its device ID
  273. // else
  274. // Find a matching node by driver name with port == 0 and get its device ID
  275. //
  276. // NOTE: We match by driver name on DevNode == 0 (3.1 devices) because it
  277. // isn't possible to have multiple instances of a 3.1 driver loaded.
  278. //
  279. // For each node in the device list,
  280. // If the node's uDeviceID is still not set,
  281. // Remove and free the node
  282. //
  283. PRIVATE BOOL FNLOCAL mdev_SyncDeviceIDs(
  284. void)
  285. {
  286. BOOL fRet = FALSE;
  287. LPTSTR pstrAlias = NULL;
  288. PMDEV_NODE pmdCurr;
  289. PMDEV_NODE pmdPrev;
  290. PMDEV_NODE pmdEnum;
  291. UINT cDev;
  292. UINT idxDev;
  293. DWORD cPort;
  294. MMRESULT mmr;
  295. DWORD cbSize;
  296. cbSize = CB_MAXALIAS * sizeof(TCHAR);
  297. pstrAlias = (LPTSTR)LocalAlloc(LPTR, cbSize);
  298. if (NULL == pstrAlias)
  299. {
  300. goto mSDI_Cleanup;
  301. }
  302. // The device list has been built and the uPort member is valid.
  303. // Now update the uDeviceID field to be proper. First, walk the list
  304. // and set them all to NO_DEVICEID.
  305. for (pmdCurr = gpMDevList; pmdCurr; pmdCurr = pmdCurr->pNext)
  306. pmdCurr->uDeviceID = NO_DEVICEID;
  307. // Now walk MMSYSTEM's list of loaded drivers and fill in all the port 0
  308. // nodes with their proper device ID
  309. //
  310. cDev = midiOutGetNumDevs();
  311. for (idxDev = 0; idxDev < cDev; )
  312. {
  313. mmr = (MMRESULT)midiOutMessage((HMIDIOUT)(UINT)idxDev,
  314. #ifdef WINNT
  315. DRV_QUERYNUMPORTS,
  316. #else
  317. MODM_GETNUMDEVS,
  318. #endif // End WINNT
  319. (DWORD)(LPDWORD)&cPort,
  320. 0);
  321. if (mmr)
  322. {
  323. DPF(1, TEXT ("mdev_Sync: Device ID %u returned %u for MODM_GETNUMDEVS"),
  324. (UINT)idxDev,
  325. (UINT)mmr);
  326. ++idxDev;
  327. continue;
  328. }
  329. mmr = (MMRESULT)midiOutMessage((HMIDIOUT)(UINT)idxDev,
  330. DRV_QUERYDRVENTRY,
  331. #ifdef WINNT
  332. (DWORD)(LPTSTR)pstrAlias,
  333. #else
  334. (DWORD)(LPTSTR)pstrPath,
  335. #endif // End Winnt
  336. CB_MAXALIAS);
  337. if (!mmr)
  338. {
  339. for (pmdCurr = gpMDevList; pmdCurr; pmdCurr = pmdCurr->pNext)
  340. {
  341. if ((0 == pmdCurr->uPort) &&
  342. (! lstrcmpi(pstrAlias, pmdCurr->szAlias)))
  343. {
  344. pmdCurr->uDeviceID = idxDev;
  345. break;
  346. }
  347. }
  348. #ifdef DEBUG
  349. if (!pmdCurr)
  350. {
  351. DPF(1, TEXT ("mdev_Sync: Device ID %u not found in device list."),
  352. (UINT)idxDev);
  353. }
  354. #endif
  355. }
  356. else
  357. {
  358. DPF(1, TEXT ("mdev_Sync: Device ID %u returned %u for DRV_QUERYDRVENTRY"),
  359. (UINT)idxDev,
  360. (UINT)mmr);
  361. }
  362. idxDev += (UINT)cPort;
  363. }
  364. // Now walk the list again. This time we catch all the non-zero ports
  365. // and set their uDeviceID properly.
  366. //
  367. for (pmdCurr = gpMDevList; pmdCurr; pmdCurr = pmdCurr->pNext)
  368. {
  369. if (!pmdCurr->uPort)
  370. continue;
  371. if (pmdCurr->dwDevNode)
  372. {
  373. for (pmdEnum = gpMDevList; pmdEnum; pmdEnum = pmdEnum->pNext)
  374. if (0 == pmdEnum->uPort &&
  375. pmdEnum->dwDevNode == pmdCurr->dwDevNode)
  376. {
  377. pmdCurr->uDeviceID = pmdEnum->uDeviceID;
  378. break;
  379. }
  380. }
  381. else
  382. {
  383. for (pmdEnum = gpMDevList; pmdEnum; pmdEnum = pmdEnum->pNext)
  384. if (0 == pmdEnum->uPort &&
  385. !lstrcmpi(pmdEnum->szDriver, pmdCurr->szDriver))
  386. {
  387. pmdCurr->uDeviceID = pmdEnum->uDeviceID;
  388. break;
  389. }
  390. }
  391. #ifdef DEBUG
  392. if (!pmdEnum)
  393. {
  394. DPF(1, TEXT ("mdev_Sync: No parent driver found for %s"),
  395. (LPTSTR)pmdCurr->szAlias);
  396. }
  397. #endif
  398. }
  399. // Now we walk the list one more time and discard anyone without a device
  400. // ID assigned.
  401. //
  402. pmdPrev = NULL;
  403. pmdCurr = gpMDevList;
  404. while (pmdCurr)
  405. {
  406. if (NO_DEVICEID == pmdCurr->uDeviceID)
  407. {
  408. DPF(1, TEXT ("mdev_Sync: Removing %s; never found a device ID"),
  409. (LPTSTR)pmdCurr->szAlias);
  410. if (pmdPrev)
  411. pmdPrev->pNext = pmdCurr->pNext;
  412. else
  413. gpMDevList = pmdCurr->pNext;
  414. LocalFree((HLOCAL)pmdCurr);
  415. pmdCurr = (pmdPrev ? pmdPrev->pNext : gpMDevList);
  416. }
  417. else
  418. {
  419. pmdPrev = pmdCurr;
  420. pmdCurr = pmdCurr->pNext;
  421. }
  422. }
  423. fRet = TRUE;
  424. mSDI_Cleanup:
  425. if (pstrAlias) LocalFree((HLOCAL)pstrAlias);
  426. return fRet;
  427. }
  428. //
  429. // mdev_MarkActiveDrivers
  430. //
  431. // Mark drivers which are loaded and have not been seen before by
  432. // mapper configuration as seen. Also flag that we want to run
  433. // RunOnce if there are any of these
  434. //
  435. PRIVATE BOOL FNLOCAL mdev_MarkActiveDrivers(
  436. void)
  437. {
  438. BOOL fRet = FALSE;
  439. HKEY hKeyMediaRsrc = NULL;
  440. HKEY hKeyThisAlias = NULL;
  441. DWORD dwMapperConfig;
  442. PMDEV_NODE pmd;
  443. if (ERROR_SUCCESS != RegOpenKey(HKEY_LOCAL_MACHINE,
  444. gszMediaRsrcKey,
  445. &hKeyMediaRsrc))
  446. {
  447. DPF(1, TEXT ("mdev_MarkActiveDrivers: Could not open ")
  448. TEXT ("...\\MediaResources\\MIDI"));
  449. goto mMAD_Cleanup;
  450. }
  451. gdwNewDrivers = 0;
  452. for (pmd = gpMDevList; pmd; pmd = pmd->pNext)
  453. if (pmd->fNewDriver)
  454. {
  455. ++gdwNewDrivers;
  456. // Mark this driver as seen
  457. //
  458. if (ERROR_SUCCESS != (RegOpenKey(hKeyMediaRsrc,
  459. pmd->szAlias,
  460. &hKeyThisAlias)))
  461. {
  462. DPF(1, TEXT ("mdev_MarkActiveDrivers: Could not open alias '%s'"),
  463. (LPTSTR)pmd->szAlias);
  464. goto mMAD_Cleanup;
  465. }
  466. dwMapperConfig = 1;
  467. RegSetValueEx(hKeyThisAlias,
  468. gszMapperConfig,
  469. 0,
  470. REG_DWORD,
  471. (LPSTR)&dwMapperConfig,
  472. sizeof(dwMapperConfig));
  473. RegCloseKey(hKeyThisAlias);
  474. }
  475. fRet = TRUE;
  476. mMAD_Cleanup:
  477. if (hKeyMediaRsrc) RegCloseKey(hKeyMediaRsrc);
  478. return fRet;
  479. }
  480. //
  481. // mdev_ListActiveDrivers
  482. //
  483. // List the currently loaded drivers to debug output
  484. //
  485. #ifdef DEBUG
  486. PRIVATE VOID FNLOCAL mdev_ListActiveDrivers(
  487. void)
  488. {
  489. PMDEV_NODE pmd;
  490. static TCHAR BCODE szNo[] = TEXT ("No");
  491. static TCHAR BCODE szYes[] = TEXT ("Yes");
  492. DPF(2, TEXT ("=== mdev_ListActiveDrivers start ==="));
  493. for (pmd = gpMDevList; pmd; pmd = pmd->pNext)
  494. {
  495. DPF(2, TEXT ("Alias %-31.31s Driver %-31.31s"),
  496. (LPTSTR)pmd->szAlias,
  497. (LPTSTR)pmd->szDriver);
  498. DPF(2, TEXT (" dwDevNode %08lX uDeviceID %u uPort %u fNewDriver %s"),
  499. pmd->dwDevNode,
  500. pmd->uDeviceID,
  501. pmd->uPort,
  502. (LPTSTR)(pmd->fNewDriver ? szYes : szNo));
  503. }
  504. DPF(2, TEXT ("=== mdev_ListActiveDrivers end ==="));
  505. }
  506. #endif
  507. //
  508. // mdev_Free
  509. //
  510. // Discard the current device list
  511. //
  512. void FNGLOBAL mdev_Free(
  513. void)
  514. {
  515. PMDEV_NODE pmdNext;
  516. PMDEV_NODE pmdCurr;
  517. pmdCurr = gpMDevList;
  518. while (pmdCurr)
  519. {
  520. pmdNext = pmdCurr->pNext;
  521. LocalFree((HLOCAL)pmdCurr);
  522. pmdCurr = pmdNext;
  523. }
  524. gpMDevList = NULL;
  525. gdwNewDrivers = (DWORD)-1L;
  526. }
  527. //
  528. // mdev_GetDeviceID
  529. //
  530. // Get the current device ID for the given alias.
  531. //
  532. UINT FNGLOBAL mdev_GetDeviceID(
  533. LPTSTR lpstrAlias)
  534. {
  535. PMDEV_NODE pmd;
  536. for (pmd = gpMDevList; pmd; pmd = pmd->pNext)
  537. if (!lstrcmpi(pmd->szAlias, lpstrAlias))
  538. return pmd->uDeviceID + pmd->uPort;
  539. DPF(1, TEXT ("mdev_GetDeviceID: Failed for %s"), lpstrAlias);
  540. return NO_DEVICEID;
  541. }
  542. //
  543. // mdev_GetAlias
  544. //
  545. // Get the registry alias for the requested device ID
  546. //
  547. BOOL FNGLOBAL mdev_GetAlias(
  548. UINT uDeviceID,
  549. LPTSTR lpstrBuffer,
  550. UINT cbBuffer)
  551. {
  552. PMDEV_NODE pmd;
  553. for (pmd = gpMDevList; pmd; pmd = pmd->pNext)
  554. if (uDeviceID == (pmd->uDeviceID + pmd->uPort))
  555. {
  556. lstrcpyn(lpstrBuffer, pmd->szAlias, cbBuffer);
  557. return TRUE;
  558. }
  559. DPF(1, TEXT ("mdev_GetAlias: Failed for device ID %u"), uDeviceID);
  560. return FALSE;
  561. }
  562. //
  563. // mdev_NewDrivers
  564. //
  565. // Returns TRUE if there were new drivers in the registry that we've never
  566. // encountered before
  567. //
  568. BOOL FNGLOBAL mdev_NewDrivers(
  569. void)
  570. {
  571. if (gdwNewDrivers == (DWORD)-1L)
  572. {
  573. DPF(0, TEXT ("mdevNewDrivers() called before mdev_Init()!"));
  574. return FALSE;
  575. }
  576. return (BOOL)(gdwNewDrivers != 0);
  577. }
  578. #ifdef WINNT
  579. typedef BOOL (*MIGRATEFUNC)(void);
  580. //
  581. // mdev_Migrate
  582. //
  583. // Makes sure registry settings are correct
  584. // before rest of configuration occurs
  585. //
  586. PRIVATE VOID FNLOCAL mdev_Migrate(void)
  587. {
  588. HMODULE hModule;
  589. MIGRATEFUNC fnMigrate;
  590. // Is Winmm.dll already loaded ?!?
  591. hModule = GetModuleHandle (TEXT ("winmm.dll"));
  592. if (hModule)
  593. {
  594. fnMigrate = (MIGRATEFUNC)GetProcAddress (hModule, "MigrateAllDrivers");
  595. if (fnMigrate)
  596. {
  597. (*fnMigrate)();
  598. }
  599. }
  600. else
  601. {
  602. // Try to load Winmm.dll
  603. hModule = (HMODULE)LoadLibrary (TEXT ("winmm.dll"));
  604. if (hModule)
  605. {
  606. fnMigrate = (MIGRATEFUNC)GetProcAddress (hModule, "MigrateAllDrivers");
  607. if (fnMigrate)
  608. {
  609. (*fnMigrate)();
  610. }
  611. FreeLibrary (hModule);
  612. }
  613. }
  614. } // End mdev_Migrate
  615. #endif // End WINNT