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.

1161 lines
38 KiB

  1. //
  2. // Copyright (c) 1999-2001 Microsoft Corporation. All rights reserved.
  3. //
  4. // Implementation of CDirectMusicScript.
  5. //
  6. #include "stdinc.h"
  7. #include "dll.h"
  8. #include "dmscript.h"
  9. #include "oleaut.h"
  10. #include "globaldisp.h"
  11. #include "activescript.h"
  12. #include "sourcetext.h"
  13. //////////////////////////////////////////////////////////////////////
  14. // Creation
  15. CDirectMusicScript::CDirectMusicScript()
  16. : m_cRef(0),
  17. m_fZombie(false),
  18. m_fCriticalSectionInitialized(false),
  19. m_pPerformance8(NULL),
  20. m_pLoader8P(NULL),
  21. m_pDispPerformance(NULL),
  22. m_pComposer8(NULL),
  23. m_fUseOleAut(true),
  24. m_pScriptManager(NULL),
  25. m_pContainerDispatch(NULL),
  26. m_pGlobalDispatch(NULL),
  27. m_fInitError(false)
  28. {
  29. LockModule(true);
  30. InitializeCriticalSection(&m_CriticalSection);
  31. // Note: on pre-Blackcomb OS's, this call can raise an exception; if it
  32. // ever pops in stress, we can add an exception handler and retry loop.
  33. m_fCriticalSectionInitialized = TRUE;
  34. m_info.fLoaded = false;
  35. m_vDirectMusicVersion.dwVersionMS = 0;
  36. m_vDirectMusicVersion.dwVersionLS = 0;
  37. Zero(&m_iohead);
  38. ZeroAndSize(&m_InitErrorInfo);
  39. }
  40. void CDirectMusicScript::ReleaseObjects()
  41. {
  42. if (m_pScriptManager)
  43. {
  44. m_pScriptManager->Close();
  45. SafeRelease(m_pScriptManager);
  46. }
  47. SafeRelease(m_pPerformance8);
  48. SafeRelease(m_pDispPerformance);
  49. if (m_pLoader8P)
  50. {
  51. m_pLoader8P->ReleaseP();
  52. m_pLoader8P = NULL;
  53. }
  54. SafeRelease(m_pComposer8);
  55. delete m_pContainerDispatch;
  56. m_pContainerDispatch = NULL;
  57. delete m_pGlobalDispatch;
  58. m_pGlobalDispatch = NULL;
  59. }
  60. HRESULT CDirectMusicScript::CreateInstance(
  61. IUnknown* pUnknownOuter,
  62. const IID& iid,
  63. void** ppv)
  64. {
  65. *ppv = NULL;
  66. if (pUnknownOuter)
  67. return CLASS_E_NOAGGREGATION;
  68. CDirectMusicScript *pInst = new CDirectMusicScript;
  69. if (pInst == NULL)
  70. return E_OUTOFMEMORY;
  71. return pInst->QueryInterface(iid, ppv);
  72. }
  73. //////////////////////////////////////////////////////////////////////
  74. // IUnknown
  75. STDMETHODIMP
  76. CDirectMusicScript::QueryInterface(const IID &iid, void **ppv)
  77. {
  78. V_INAME(CDirectMusicScript::QueryInterface);
  79. V_PTRPTR_WRITE(ppv);
  80. V_REFGUID(iid);
  81. if (iid == IID_IUnknown || iid == IID_IDirectMusicScript)
  82. {
  83. *ppv = static_cast<IDirectMusicScript*>(this);
  84. }
  85. else if (iid == IID_IDirectMusicScriptPrivate)
  86. {
  87. *ppv = static_cast<IDirectMusicScriptPrivate*>(this);
  88. }
  89. else if (iid == IID_IDirectMusicObject)
  90. {
  91. *ppv = static_cast<IDirectMusicObject*>(this);
  92. }
  93. else if (iid == IID_IDirectMusicObjectP)
  94. {
  95. *ppv = static_cast<IDirectMusicObjectP*>(this);
  96. }
  97. else if (iid == IID_IPersistStream)
  98. {
  99. *ppv = static_cast<IPersistStream*>(this);
  100. }
  101. else if (iid == IID_IPersist)
  102. {
  103. *ppv = static_cast<IPersist*>(this);
  104. }
  105. else if (iid == IID_IDispatch)
  106. {
  107. *ppv = static_cast<IDispatch*>(this);
  108. }
  109. else
  110. {
  111. *ppv = NULL;
  112. return E_NOINTERFACE;
  113. }
  114. reinterpret_cast<IUnknown*>(this)->AddRef();
  115. return S_OK;
  116. }
  117. STDMETHODIMP_(ULONG)
  118. CDirectMusicScript::AddRef()
  119. {
  120. return InterlockedIncrement(&m_cRef);
  121. }
  122. STDMETHODIMP_(ULONG)
  123. CDirectMusicScript::Release()
  124. {
  125. if (!InterlockedDecrement(&m_cRef))
  126. {
  127. this->Zombie();
  128. DeleteCriticalSection(&m_CriticalSection);
  129. delete this;
  130. LockModule(false);
  131. return 0;
  132. }
  133. return m_cRef;
  134. }
  135. //////////////////////////////////////////////////////////////////////
  136. // IPersistStream
  137. STDMETHODIMP
  138. CDirectMusicScript::Load(IStream* pStream)
  139. {
  140. V_INAME(CDirectMusicScript::Load);
  141. V_INTERFACE(pStream);
  142. if (m_fZombie)
  143. {
  144. Trace(1, "Error: Call of IDirectMusicScript::Load after the script has been garbage collected. "
  145. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  146. "and then calling CollectGarbage or Release on the loader.");
  147. return DMUS_S_GARBAGE_COLLECTED;
  148. }
  149. HRESULT hr = S_OK;
  150. SmartRef::CritSec CS(&m_CriticalSection);
  151. // Clear any old info
  152. this->ReleaseObjects();
  153. m_info.fLoaded = false;
  154. m_info.oinfo.Clear();
  155. m_vDirectMusicVersion.dwVersionMS = 0;
  156. m_vDirectMusicVersion.dwVersionLS = 0;
  157. m_wstrLanguage = NULL;
  158. m_fInitError = false;
  159. // Get the loader from stream
  160. IDirectMusicGetLoader *pIDMGetLoader = NULL;
  161. SmartRef::ComPtr<IDirectMusicLoader> scomLoader;
  162. hr = pStream->QueryInterface(IID_IDirectMusicGetLoader, reinterpret_cast<void **>(&pIDMGetLoader));
  163. if (FAILED(hr))
  164. {
  165. Trace(1, "Error: unable to load script from a stream because it doesn't support the IDirectMusicGetLoader interface.\n");
  166. return DMUS_E_UNSUPPORTED_STREAM;
  167. }
  168. hr = pIDMGetLoader->GetLoader(&scomLoader);
  169. pIDMGetLoader->Release();
  170. if (FAILED(hr))
  171. return hr;
  172. hr = scomLoader->QueryInterface(IID_IDirectMusicLoader8P, reinterpret_cast<void **>(&m_pLoader8P)); // OK if this fails -- just means the scripts won't be garbage collected
  173. if (SUCCEEDED(hr))
  174. {
  175. // Hold only a private ref on the loader. See IDirectMusicLoader8P::AddRefP for more info.
  176. m_pLoader8P->AddRefP();
  177. m_pLoader8P->Release(); // offset the QI
  178. }
  179. // Read the script's header information
  180. SmartRef::RiffIter riForm(pStream);
  181. if (!riForm)
  182. {
  183. #ifdef DBG
  184. if (SUCCEEDED(riForm.hr()))
  185. {
  186. Trace(1, "Error: Unable to load script: Unexpected end of file.\n");
  187. }
  188. #endif
  189. return SUCCEEDED(riForm.hr()) ? DMUS_E_SCRIPT_INVALID_FILE : riForm.hr();
  190. }
  191. hr = riForm.FindRequired(SmartRef::RiffIter::Riff, DMUS_FOURCC_SCRIPT_FORM, DMUS_E_SCRIPT_INVALID_FILE);
  192. if (FAILED(hr))
  193. {
  194. #ifdef DBG
  195. if (hr == DMUS_E_SCRIPT_INVALID_FILE)
  196. {
  197. Trace(1, "Error: Unable to load script: Form 'DMSC' not found.\n");
  198. }
  199. #endif
  200. return hr;
  201. }
  202. SmartRef::RiffIter ri = riForm.Descend();
  203. if (!ri)
  204. return ri.hr();
  205. hr = ri.FindRequired(SmartRef::RiffIter::Chunk, DMUS_FOURCC_SCRIPT_CHUNK, DMUS_E_SCRIPT_INVALID_FILE);
  206. if (FAILED(hr))
  207. {
  208. #ifdef DBG
  209. if (hr == DMUS_E_SCRIPT_INVALID_FILE)
  210. {
  211. Trace(1, "Error: Unable to load script: Chunk 'schd' not found.\n");
  212. }
  213. #endif
  214. return hr;
  215. }
  216. hr = SmartRef::RiffIterReadChunk(ri, &m_iohead);
  217. if (FAILED(hr))
  218. return hr;
  219. hr = ri.LoadObjectInfo(&m_info.oinfo, SmartRef::RiffIter::Chunk, DMUS_FOURCC_SCRIPTVERSION_CHUNK);
  220. if (FAILED(hr))
  221. return hr;
  222. hr = SmartRef::RiffIterReadChunk(ri, &m_vDirectMusicVersion);
  223. if (FAILED(hr))
  224. return hr;
  225. // Read the script's embedded container
  226. IDirectMusicContainer *pContainer = NULL;
  227. hr = ri.FindAndGetEmbeddedObject(
  228. SmartRef::RiffIter::Riff,
  229. DMUS_FOURCC_CONTAINER_FORM,
  230. DMUS_E_SCRIPT_INVALID_FILE,
  231. scomLoader,
  232. CLSID_DirectMusicContainer,
  233. IID_IDirectMusicContainer,
  234. reinterpret_cast<void**>(&pContainer));
  235. if (FAILED(hr))
  236. {
  237. #ifdef DBG
  238. if (hr == DMUS_E_SCRIPT_INVALID_FILE)
  239. {
  240. Trace(1, "Error: Unable to load script: Form 'DMCN' no found.\n");
  241. }
  242. #endif
  243. return hr;
  244. }
  245. // Build the container object that will represent the items in the container to the script
  246. m_pContainerDispatch = new CContainerDispatch(pContainer, scomLoader, m_iohead.dwFlags, &hr);
  247. pContainer->Release();
  248. if (!m_pContainerDispatch)
  249. return E_OUTOFMEMORY;
  250. if (FAILED(hr))
  251. return hr;
  252. // Create the global dispatch object
  253. m_pGlobalDispatch = new CGlobalDispatch(this);
  254. if (!m_pGlobalDispatch)
  255. return E_OUTOFMEMORY;
  256. // Get the script's language
  257. hr = ri.FindRequired(SmartRef::RiffIter::Chunk, DMUS_FOURCC_SCRIPTLANGUAGE_CHUNK, DMUS_E_SCRIPT_INVALID_FILE);
  258. if (FAILED(hr))
  259. {
  260. #ifdef DBG
  261. if (hr == DMUS_E_SCRIPT_INVALID_FILE)
  262. {
  263. Trace(1, "Error: Unable to load script: Chunk 'scla' no found.\n");
  264. }
  265. #endif
  266. return hr;
  267. }
  268. hr = ri.ReadText(&m_wstrLanguage);
  269. if (FAILED(hr))
  270. {
  271. #ifdef DBG
  272. if (hr == E_FAIL)
  273. {
  274. Trace(1, "Error: Unable to load script: Problem reading 'scla' chunk.\n");
  275. }
  276. #endif
  277. return hr == E_FAIL ? DMUS_E_SCRIPT_INVALID_FILE : hr;
  278. }
  279. // Get the script's source code
  280. SmartRef::WString wstrSource;
  281. for (++ri; ;++ri)
  282. {
  283. if (!ri)
  284. {
  285. Trace(1, "Error: Unable to load script: Expected chunk 'scsr' or list 'DMRF'.\n");
  286. return DMUS_E_SCRIPT_INVALID_FILE;
  287. }
  288. SmartRef::RiffIter::RiffType type = ri.type();
  289. FOURCC id = ri.id();
  290. if (type == SmartRef::RiffIter::Chunk)
  291. {
  292. if (id == DMUS_FOURCC_SCRIPTSOURCE_CHUNK)
  293. {
  294. hr = ri.ReadText(&wstrSource);
  295. if (FAILED(hr))
  296. {
  297. #ifdef DBG
  298. if (hr == E_FAIL)
  299. {
  300. Trace(1, "Error: Unable to load script: Problem reading 'scsr' chunk.\n");
  301. }
  302. #endif
  303. return hr == E_FAIL ? DMUS_E_SCRIPT_INVALID_FILE : hr;
  304. }
  305. }
  306. break;
  307. }
  308. else if (type == SmartRef::RiffIter::List)
  309. {
  310. if (id == DMUS_FOURCC_REF_LIST)
  311. {
  312. DMUS_OBJECTDESC desc;
  313. hr = ri.ReadReference(&desc);
  314. if (FAILED(hr))
  315. return hr;
  316. // The resulting desc shouldn't have a name or GUID (the plain text file can't hold name/GUID info)
  317. // and it should have a clsid should be GUID_NULL, which we'll replace with the clsid of our private
  318. // source helper object.
  319. if (desc.dwValidData & (DMUS_OBJ_NAME | DMUS_OBJ_OBJECT) ||
  320. !(desc.dwValidData & DMUS_OBJ_CLASS) || desc.guidClass != GUID_NULL)
  321. {
  322. #ifdef DBG
  323. if (desc.dwValidData & (DMUS_OBJ_NAME | DMUS_OBJ_OBJECT))
  324. {
  325. Trace(1, "Error: Unable to load script: 'DMRF' list must have dwValidData with DMUS_OBJ_CLASS and guidClassID of GUID_NULL.\n");
  326. }
  327. else
  328. {
  329. Trace(1, "Error: Unable to load script: 'DMRF' list cannot have dwValidData with DMUS_OBJ_NAME or DMUS_OBJ_OBJECT.\n");
  330. }
  331. #endif
  332. return DMUS_E_SCRIPT_INVALID_FILE;
  333. }
  334. desc.guidClass = CLSID_DirectMusicSourceText;
  335. IDirectMusicSourceText *pISource = NULL;
  336. hr = scomLoader->EnableCache(CLSID_DirectMusicSourceText, false); // This is a private object we just use temporarily. Don't want these guys hanging around in the cache.
  337. if (FAILED(hr))
  338. return hr;
  339. hr = scomLoader->GetObject(&desc, IID_IDirectMusicSourceText, reinterpret_cast<void**>(&pISource));
  340. if (FAILED(hr))
  341. return hr;
  342. DWORD cwchSourceBufferSize = 0;
  343. pISource->GetTextLength(&cwchSourceBufferSize);
  344. WCHAR *pwszSource = new WCHAR[cwchSourceBufferSize];
  345. if (!pwszSource)
  346. return E_OUTOFMEMORY;
  347. pISource->GetText(pwszSource);
  348. *&wstrSource = pwszSource;
  349. pISource->Release();
  350. }
  351. break;
  352. }
  353. }
  354. m_info.fLoaded = true;
  355. // Now that we are loaded and initialized, we can start active scripting
  356. // See if we're dealing with a custom DirectMusic scripting engine. Such engines are marked with the key DMScript. They can be
  357. // called on multiple threads and they don't use oleaut32. Ordinary active scripting engines are marked with the key OLEScript.
  358. SmartRef::HKey shkeyLanguage;
  359. SmartRef::HKey shkeyMark;
  360. SmartRef::AString astrLanguage = m_wstrLanguage;
  361. if (ERROR_SUCCESS != ::RegOpenKeyEx(HKEY_CLASSES_ROOT, astrLanguage, 0, KEY_QUERY_VALUE, &shkeyLanguage) || !shkeyLanguage)
  362. {
  363. Trace(1, "Error: Unable to load script: Scripting engine for language %s does not exist or is not registered.\n", astrLanguage);
  364. return DMUS_E_SCRIPT_LANGUAGE_INCOMPATIBLE;
  365. }
  366. bool fCustomScriptEngine = ERROR_SUCCESS == ::RegOpenKeyEx(shkeyLanguage, "DMScript", 0, KEY_QUERY_VALUE, &shkeyMark) && shkeyMark;
  367. if (!fCustomScriptEngine)
  368. {
  369. if (ERROR_SUCCESS != ::RegOpenKeyEx(shkeyLanguage, "OLEScript", 0, KEY_QUERY_VALUE, &shkeyMark) || !shkeyMark)
  370. {
  371. Trace(1, "Error: Unable to load script: Language %s refers to a COM object that is not registered as a scripting engine (OLEScript key).\n", astrLanguage);
  372. return DMUS_E_SCRIPT_LANGUAGE_INCOMPATIBLE;
  373. }
  374. }
  375. m_fUseOleAut = !fCustomScriptEngine;
  376. if (fCustomScriptEngine)
  377. {
  378. m_pScriptManager = new CActiveScriptManager(
  379. m_fUseOleAut,
  380. m_wstrLanguage,
  381. wstrSource,
  382. this,
  383. &hr,
  384. &m_InitErrorInfo);
  385. }
  386. else
  387. {
  388. m_pScriptManager = new CSingleThreadedScriptManager(
  389. m_fUseOleAut,
  390. m_wstrLanguage,
  391. wstrSource,
  392. this,
  393. &hr,
  394. &m_InitErrorInfo);
  395. }
  396. if (!m_pScriptManager)
  397. return E_OUTOFMEMORY;
  398. if (FAILED(hr))
  399. {
  400. SafeRelease(m_pScriptManager);
  401. }
  402. if (hr == DMUS_E_SCRIPT_ERROR_IN_SCRIPT)
  403. {
  404. // If we fail here, load would fail and client would never be able to get the
  405. // error information. Instead, return S_OK and save the error to return from Init.
  406. m_fInitError = true;
  407. hr = S_OK;
  408. }
  409. return hr;
  410. }
  411. //////////////////////////////////////////////////////////////////////
  412. // IDirectMusicObject
  413. STDMETHODIMP
  414. CDirectMusicScript::GetDescriptor(LPDMUS_OBJECTDESC pDesc)
  415. {
  416. V_INAME(CDirectMusicScript::GetDescriptor);
  417. V_PTR_WRITE(pDesc, DMUS_OBJECTDESC);
  418. ZeroMemory(pDesc, sizeof(DMUS_OBJECTDESC));
  419. pDesc->dwSize = sizeof(DMUS_OBJECTDESC);
  420. if (m_fZombie)
  421. {
  422. Trace(1, "Error: Call of IDirectMusicScript::GetDescriptor after the script has been garbage collected. "
  423. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  424. "and then calling CollectGarbage or Release on the loader.");
  425. return DMUS_S_GARBAGE_COLLECTED;
  426. }
  427. if (wcslen(m_info.oinfo.wszName) > 0)
  428. {
  429. pDesc->dwValidData |= DMUS_OBJ_NAME;
  430. wcsncpy(pDesc->wszName, m_info.oinfo.wszName, DMUS_MAX_NAME);
  431. pDesc->wszName[DMUS_MAX_NAME-1] = L'\0';
  432. }
  433. if (GUID_NULL != m_info.oinfo.guid)
  434. {
  435. pDesc->guidObject = m_info.oinfo.guid;
  436. pDesc->dwValidData |= DMUS_OBJ_OBJECT;
  437. }
  438. pDesc->vVersion = m_info.oinfo.vVersion;
  439. pDesc->dwValidData |= DMUS_OBJ_VERSION;
  440. pDesc->guidClass = CLSID_DirectMusicScript;
  441. pDesc->dwValidData |= DMUS_OBJ_CLASS;
  442. if (m_info.wstrFilename)
  443. {
  444. wcsncpy(pDesc->wszFileName, m_info.wstrFilename, DMUS_MAX_FILENAME);
  445. pDesc->wszFileName[DMUS_MAX_FILENAME-1] = L'\0';
  446. pDesc->dwValidData |= DMUS_OBJ_FILENAME;
  447. }
  448. if (m_info.fLoaded)
  449. {
  450. pDesc->dwValidData |= DMUS_OBJ_LOADED;
  451. }
  452. return S_OK;
  453. }
  454. STDMETHODIMP
  455. CDirectMusicScript::SetDescriptor(LPDMUS_OBJECTDESC pDesc)
  456. {
  457. V_INAME(CDirectMusicScript::SetDescriptor);
  458. V_STRUCTPTR_READ(pDesc, DMUS_OBJECTDESC);
  459. if (m_fZombie)
  460. {
  461. Trace(1, "Error: Call of IDirectMusicScript::SetDescriptor after the script has been garbage collected. "
  462. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  463. "and then calling CollectGarbage or Release on the loader.");
  464. return DMUS_S_GARBAGE_COLLECTED;
  465. }
  466. DWORD dwTemp = pDesc->dwValidData;
  467. if (pDesc->dwValidData & DMUS_OBJ_OBJECT)
  468. {
  469. m_info.oinfo.guid = pDesc->guidObject;
  470. }
  471. if (pDesc->dwValidData & DMUS_OBJ_CLASS)
  472. {
  473. pDesc->dwValidData &= ~DMUS_OBJ_CLASS;
  474. }
  475. if (pDesc->dwValidData & DMUS_OBJ_NAME)
  476. {
  477. wcsncpy(m_info.oinfo.wszName, pDesc->wszName, DMUS_MAX_NAME);
  478. m_info.oinfo.wszName[DMUS_MAX_NAME-1] = L'\0';
  479. }
  480. if (pDesc->dwValidData & DMUS_OBJ_CATEGORY)
  481. {
  482. pDesc->dwValidData &= ~DMUS_OBJ_CATEGORY;
  483. }
  484. if (pDesc->dwValidData & DMUS_OBJ_FILENAME)
  485. {
  486. m_info.wstrFilename = pDesc->wszFileName;
  487. }
  488. if (pDesc->dwValidData & DMUS_OBJ_FULLPATH)
  489. {
  490. pDesc->dwValidData &= ~DMUS_OBJ_FULLPATH;
  491. }
  492. if (pDesc->dwValidData & DMUS_OBJ_URL)
  493. {
  494. pDesc->dwValidData &= ~DMUS_OBJ_URL;
  495. }
  496. if (pDesc->dwValidData & DMUS_OBJ_VERSION)
  497. {
  498. m_info.oinfo.vVersion = pDesc->vVersion;
  499. }
  500. if (pDesc->dwValidData & DMUS_OBJ_DATE)
  501. {
  502. pDesc->dwValidData &= ~DMUS_OBJ_DATE;
  503. }
  504. if (pDesc->dwValidData & DMUS_OBJ_LOADED)
  505. {
  506. pDesc->dwValidData &= ~DMUS_OBJ_LOADED;
  507. }
  508. return dwTemp == pDesc->dwValidData ? S_OK : S_FALSE;
  509. }
  510. STDMETHODIMP
  511. CDirectMusicScript::ParseDescriptor(LPSTREAM pStream, LPDMUS_OBJECTDESC pDesc)
  512. {
  513. V_INAME(CDirectMusicScript::ParseDescriptor);
  514. V_INTERFACE(pStream);
  515. V_PTR_WRITE(pDesc, DMUS_OBJECTDESC);
  516. ZeroMemory(pDesc, sizeof(DMUS_OBJECTDESC));
  517. pDesc->dwSize = sizeof(DMUS_OBJECTDESC);
  518. if (m_fZombie)
  519. {
  520. Trace(1, "Error: Call of IDirectMusicScript::ParseDescriptor after the script has been garbage collected. "
  521. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  522. "and then calling CollectGarbage or Release on the loader.");
  523. return DMUS_S_GARBAGE_COLLECTED;
  524. }
  525. SmartRef::CritSec CS(&m_CriticalSection);
  526. // Read the script's header information
  527. SmartRef::RiffIter riForm(pStream);
  528. if (!riForm)
  529. {
  530. #ifdef DBG
  531. if (SUCCEEDED(riForm.hr()))
  532. {
  533. Trace(2, "Error: ParseDescriptor on a script failed: Unexpected end of file. "
  534. "(Note that this may be OK, such as when ScanDirectory is used to parse a set of unknown files, some of which are not scripts.)\n");
  535. }
  536. #endif
  537. return SUCCEEDED(riForm.hr()) ? DMUS_E_SCRIPT_INVALID_FILE : riForm.hr();
  538. }
  539. HRESULT hr = riForm.FindRequired(SmartRef::RiffIter::Riff, DMUS_FOURCC_SCRIPT_FORM, DMUS_E_SCRIPT_INVALID_FILE);
  540. if (FAILED(hr))
  541. {
  542. #ifdef DBG
  543. if (hr == DMUS_E_SCRIPT_INVALID_FILE)
  544. {
  545. Trace(1, "Error: ParseDescriptor on a script failed: Form 'DMSC' not found. "
  546. "(Note that this may be OK, such as when ScanDirectory is used to parse a set of unknown files, some of which are not scripts.)\n");
  547. }
  548. #endif
  549. return hr;
  550. }
  551. SmartRef::RiffIter ri = riForm.Descend();
  552. if (!ri)
  553. return ri.hr();
  554. hr = ri.LoadObjectInfo(&m_info.oinfo, SmartRef::RiffIter::Chunk, DMUS_FOURCC_SCRIPTVERSION_CHUNK);
  555. if (FAILED(hr))
  556. return hr;
  557. hr = this->GetDescriptor(pDesc);
  558. return hr;
  559. }
  560. STDMETHODIMP_(void)
  561. CDirectMusicScript::Zombie()
  562. {
  563. m_fZombie = true;
  564. this->ReleaseObjects();
  565. }
  566. //////////////////////////////////////////////////////////////////////
  567. // IDirectMusicScript
  568. STDMETHODIMP
  569. CDirectMusicScript::Init(IDirectMusicPerformance *pPerformance, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
  570. {
  571. V_INAME(CDirectMusicScript::Init);
  572. V_INTERFACE(pPerformance);
  573. V_PTR_WRITE_OPT(pErrorInfo, DMUS_SCRIPT_ERRORINFO);
  574. if (m_fZombie)
  575. {
  576. Trace(1, "Error: Call of IDirectMusicScript::Init after the script has been garbage collected. "
  577. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  578. "and then calling CollectGarbage or Release on the loader.");
  579. return DMUS_S_GARBAGE_COLLECTED;
  580. }
  581. SmartRef::ComPtr<IDirectMusicPerformance8> scomPerformance8;
  582. HRESULT hr = pPerformance->QueryInterface(IID_IDirectMusicPerformance8, reinterpret_cast<void **>(&scomPerformance8));
  583. if (FAILED(hr))
  584. return hr;
  585. // Don't take the critical section if the script is already initialized.
  586. // For example, this is necessary in the following situation:
  587. // - The critical section has already been taken by CallRoutine.
  588. // - The routine played a segment with a script track referencing this script.
  589. // - The script track calls Init (from a different thread) to make sure the script
  590. // is initialized.
  591. if (m_pPerformance8)
  592. {
  593. // Additional calls to Init are ignored.
  594. // First call wins. Return S_FALSE if performance doesn't match.
  595. if (m_pPerformance8 == scomPerformance8)
  596. return S_OK;
  597. else
  598. return S_FALSE;
  599. }
  600. SmartRef::CritSec CS(&m_CriticalSection);
  601. if (m_fInitError)
  602. {
  603. if (pErrorInfo)
  604. {
  605. // Syntax errors in a script occur as it is loaded, before SetDescriptor gives a script
  606. // its filename. We'll have it after the load (before init is called) so can add it
  607. // back in here.
  608. if (m_InitErrorInfo.wszSourceFile[0] == L'\0' && m_info.wstrFilename)
  609. wcsTruncatedCopy(m_InitErrorInfo.wszSourceFile, m_info.wstrFilename, DMUS_MAX_FILENAME);
  610. CopySizedStruct(pErrorInfo, &m_InitErrorInfo);
  611. }
  612. return DMUS_E_SCRIPT_ERROR_IN_SCRIPT;
  613. }
  614. if (!m_info.fLoaded)
  615. {
  616. Trace(1, "Error: IDirectMusicScript::Init called before the script has been loaded.\n");
  617. return DMUS_E_NOT_LOADED;
  618. }
  619. // Get the dispatch interface for the performance
  620. SmartRef::ComPtr<IDispatch> scomDispPerformance = NULL;
  621. hr = pPerformance->QueryInterface(IID_IDispatch, reinterpret_cast<void **>(&scomDispPerformance));
  622. if (FAILED(hr))
  623. return hr;
  624. // Get a composer object
  625. hr = CoCreateInstance(CLSID_DirectMusicComposer, NULL, CLSCTX_INPROC_SERVER, IID_IDirectMusicComposer8, reinterpret_cast<void **>(&m_pComposer8));
  626. if (FAILED(hr))
  627. return hr;
  628. m_pDispPerformance = scomDispPerformance.disown();
  629. m_pPerformance8 = scomPerformance8.disown();
  630. hr = m_pScriptManager->Start(pErrorInfo);
  631. if (FAILED(hr))
  632. return hr;
  633. hr = m_pContainerDispatch->OnScriptInit(m_pPerformance8);
  634. return hr;
  635. }
  636. // Returns DMUS_E_SCRIPT_ROUTINE_NOT_FOUND if routine doesn't exist in the script.
  637. STDMETHODIMP
  638. CDirectMusicScript::CallRoutine(WCHAR *pwszRoutineName, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
  639. {
  640. V_INAME(CDirectMusicScript::CallRoutine);
  641. V_BUFPTR_READ(pwszRoutineName, 2);
  642. V_PTR_WRITE_OPT(pErrorInfo, DMUS_SCRIPT_ERRORINFO);
  643. if (m_fZombie)
  644. {
  645. Trace(1, "Error: Call of IDirectMusicScript::CallRoutine after the script has been garbage collected. "
  646. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  647. "and then calling CollectGarbage or Release on the loader.");
  648. return DMUS_S_GARBAGE_COLLECTED;
  649. }
  650. SmartRef::CritSec CS(&m_CriticalSection);
  651. if (!m_pScriptManager || !m_pPerformance8)
  652. {
  653. Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::CallRoutine.\n");
  654. return DMUS_E_NOT_INIT;
  655. }
  656. return m_pScriptManager->CallRoutine(pwszRoutineName, pErrorInfo);
  657. }
  658. // Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND if variable doesn't exist in the script.
  659. STDMETHODIMP
  660. CDirectMusicScript::SetVariableVariant(
  661. WCHAR *pwszVariableName,
  662. VARIANT varValue,
  663. BOOL fSetRef,
  664. DMUS_SCRIPT_ERRORINFO *pErrorInfo)
  665. {
  666. V_INAME(CDirectMusicScript::SetVariableVariant);
  667. V_BUFPTR_READ(pwszVariableName, 2);
  668. V_PTR_WRITE_OPT(pErrorInfo, DMUS_SCRIPT_ERRORINFO);
  669. switch (varValue.vt)
  670. {
  671. case VT_BSTR:
  672. V_BUFPTR_READ_OPT(varValue.bstrVal, sizeof(OLECHAR));
  673. // We could be more thorough and verify each character until we hit the terminator but
  674. // that would be inefficient. We could also use the length preceding a BSTR pointer,
  675. // but that would be cheating COM's functions that encapsulate BSTRs and could lead to
  676. // problems in future versions of windows such as 64 bit if the BSTR format changes.
  677. break;
  678. case VT_UNKNOWN:
  679. V_INTERFACE_OPT(varValue.punkVal);
  680. break;
  681. case VT_DISPATCH:
  682. V_INTERFACE_OPT(varValue.pdispVal);
  683. break;
  684. }
  685. if (m_fZombie)
  686. {
  687. Trace(1, "Error: Call of IDirectMusicScript::SetVariableObject/SetVariableNumber/SetVariableVariant after the script has been garbage collected. "
  688. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  689. "and then calling CollectGarbage or Release on the loader.");
  690. return DMUS_S_GARBAGE_COLLECTED;
  691. }
  692. SmartRef::CritSec CS(&m_CriticalSection);
  693. if (!m_pScriptManager || !m_pPerformance8)
  694. {
  695. Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::SetVariableVariant.\n");
  696. return DMUS_E_NOT_INIT;
  697. }
  698. HRESULT hr = m_pScriptManager->SetVariable(pwszVariableName, varValue, !!fSetRef, pErrorInfo);
  699. if (hr == DMUS_E_SCRIPT_VARIABLE_NOT_FOUND)
  700. {
  701. // There are also items in the script's container that the m_pScriptManager object isn't available.
  702. // If that's the case, we should return a more specific error message.
  703. IUnknown *punk = NULL;
  704. hr = m_pContainerDispatch->GetVariableObject(pwszVariableName, &punk);
  705. if (SUCCEEDED(hr))
  706. {
  707. // We don't actually need the object--it can't be set. Just needed to find out if it's there
  708. // in order to return a more specific error message.
  709. punk->Release();
  710. return DMUS_E_SCRIPT_CONTENT_READONLY;
  711. }
  712. }
  713. return hr;
  714. }
  715. // Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND and empty value if variable doesn't exist in the script.
  716. // Certain varient types such as BSTRs and interface pointers must be freed/released according to the standards for VARIANTS.
  717. // If unsure, use VariantClear (requires oleaut32).
  718. STDMETHODIMP
  719. CDirectMusicScript::GetVariableVariant(WCHAR *pwszVariableName, VARIANT *pvarValue, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
  720. {
  721. V_INAME(CDirectMusicScript::GetVariableVariant);
  722. V_BUFPTR_READ(pwszVariableName, 2);
  723. V_PTR_WRITE(pvarValue, VARIANT);
  724. V_PTR_WRITE_OPT(pErrorInfo, DMUS_SCRIPT_ERRORINFO);
  725. DMS_VariantInit(m_fUseOleAut, pvarValue);
  726. if (m_fZombie)
  727. {
  728. Trace(1, "Error: Call of IDirectMusicScript::GetVariableObject/GetVariableNumber/GetVariableVariant after the script has been garbage collected. "
  729. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  730. "and then calling CollectGarbage or Release on the loader.");
  731. return DMUS_S_GARBAGE_COLLECTED;
  732. }
  733. SmartRef::CritSec CS(&m_CriticalSection);
  734. if (!m_pScriptManager || !m_pPerformance8)
  735. {
  736. Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::GetVariableVariant.\n");
  737. return DMUS_E_NOT_INIT;
  738. }
  739. HRESULT hr = m_pScriptManager->GetVariable(pwszVariableName, pvarValue, pErrorInfo);
  740. if (hr == DMUS_E_SCRIPT_VARIABLE_NOT_FOUND)
  741. {
  742. // There are also items in the script's container that we need to return.
  743. // This is implemented by the container, which returns the IUnknown pointer directly rather than through a variant.
  744. IUnknown *punk = NULL;
  745. hr = m_pContainerDispatch->GetVariableObject(pwszVariableName, &punk);
  746. if (SUCCEEDED(hr))
  747. {
  748. pvarValue->vt = VT_UNKNOWN;
  749. pvarValue->punkVal = punk;
  750. }
  751. }
  752. #ifdef DBG
  753. if (hr == DMUS_E_SCRIPT_VARIABLE_NOT_FOUND)
  754. {
  755. Trace(1, "Error: Attempt to get variable '%S' that is not defined in the script.\n", pwszVariableName);
  756. }
  757. #endif
  758. if (!m_fUseOleAut && pvarValue->vt == VT_BSTR)
  759. {
  760. // m_fUseOleAut is false when we're using our own custom scripting engine that avoids
  761. // depending on oleaut32.dll. But in this case we're returning a BSTR variant to the
  762. // caller. We have to allocate this string with SysAllocString (from oleaut32)
  763. // because the caller is going to free it with SysFreeString--the standard thing to
  764. // do with a variant BSTR.
  765. BSTR bstrOle = DMS_SysAllocString(true, pvarValue->bstrVal); // allocate a copy with oleaut
  766. DMS_SysFreeString(false, pvarValue->bstrVal); // free the previous value (allocated without oleaut)
  767. pvarValue->bstrVal = bstrOle; // return the oleaut string to the user
  768. if (!bstrOle)
  769. hr = E_OUTOFMEMORY;
  770. }
  771. return hr;
  772. }
  773. // Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND if variable doesn't exist in the script.
  774. STDMETHODIMP
  775. CDirectMusicScript::SetVariableNumber(WCHAR *pwszVariableName, LONG lValue, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
  776. {
  777. VARIANT var;
  778. var.vt = VT_I4;
  779. var.lVal = lValue;
  780. return this->SetVariableVariant(pwszVariableName, var, false, pErrorInfo);
  781. }
  782. // Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND and 0 if variable doesn't exist in the script.
  783. // Returns DISP_E_TYPEMISMATCH if variable's datatype cannot be converted to LONG.
  784. STDMETHODIMP
  785. CDirectMusicScript::GetVariableNumber(WCHAR *pwszVariableName, LONG *plValue, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
  786. {
  787. V_INAME(CDirectMusicScript::GetVariableNumber);
  788. V_PTR_WRITE(plValue, LONG);
  789. *plValue = 0;
  790. VARIANT var;
  791. HRESULT hr = this->GetVariableVariant(pwszVariableName, &var, pErrorInfo);
  792. if (FAILED(hr) || hr == S_FALSE || hr == DMUS_S_GARBAGE_COLLECTED)
  793. return hr;
  794. hr = DMS_VariantChangeType(m_fUseOleAut, &var, &var, 0, VT_I4);
  795. if (SUCCEEDED(hr))
  796. *plValue = var.lVal;
  797. // GetVariableVariant forces a BSTR to be allocated with SysAllocString;
  798. // so if we allocated a BSTR there, we need to free it with SysAllocString here.
  799. bool fUseOleAut = m_fUseOleAut;
  800. if (!m_fUseOleAut && var.vt == VT_BSTR)
  801. {
  802. fUseOleAut = true;
  803. }
  804. DMS_VariantClear(fUseOleAut, &var);
  805. return hr;
  806. }
  807. // Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND if variable doesn't exist in the script.
  808. STDMETHODIMP
  809. CDirectMusicScript::SetVariableObject(WCHAR *pwszVariableName, IUnknown *punkValue, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
  810. {
  811. VARIANT var;
  812. var.vt = VT_UNKNOWN;
  813. var.punkVal = punkValue;
  814. return this->SetVariableVariant(pwszVariableName, var, true, pErrorInfo);
  815. }
  816. // Returns DMUS_E_SCRIPT_VARIABLE_NOT_FOUND and NULL if variable doesn't exist in the script.
  817. // Returns DISP_E_TYPEMISMATCH if variable's datatype cannot be converted to IUnknown.
  818. STDMETHODIMP
  819. CDirectMusicScript::GetVariableObject(WCHAR *pwszVariableName, REFIID riid, LPVOID FAR *ppv, DMUS_SCRIPT_ERRORINFO *pErrorInfo)
  820. {
  821. V_INAME(CDirectMusicScript::GetVariableObject);
  822. V_PTR_WRITE(ppv, IUnknown *);
  823. *ppv = NULL;
  824. VARIANT var;
  825. HRESULT hr = this->GetVariableVariant(pwszVariableName, &var, pErrorInfo);
  826. if (FAILED(hr) || hr == DMUS_S_GARBAGE_COLLECTED)
  827. return hr;
  828. hr = DMS_VariantChangeType(m_fUseOleAut, &var, &var, 0, VT_UNKNOWN);
  829. if (SUCCEEDED(hr))
  830. hr = var.punkVal->QueryInterface(riid, ppv);
  831. DMS_VariantClear(m_fUseOleAut, &var);
  832. return hr;
  833. }
  834. STDMETHODIMP
  835. CDirectMusicScript::EnumRoutine(DWORD dwIndex, WCHAR *pwszName)
  836. {
  837. V_INAME(CDirectMusicScript::EnumRoutine);
  838. V_BUFPTR_WRITE(pwszName, MAX_PATH);
  839. *pwszName = L'\0';
  840. if (m_fZombie)
  841. {
  842. Trace(1, "Error: Call of IDirectMusicScript::EnumRoutine after the script has been garbage collected. "
  843. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  844. "and then calling CollectGarbage or Release on the loader.");
  845. return DMUS_S_GARBAGE_COLLECTED;
  846. }
  847. if (!m_pScriptManager || !m_pPerformance8)
  848. {
  849. Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::EnumRoutine.\n");
  850. return DMUS_E_NOT_INIT;
  851. }
  852. return m_pScriptManager->EnumItem(true, dwIndex, pwszName, NULL);
  853. }
  854. STDMETHODIMP
  855. CDirectMusicScript::EnumVariable(DWORD dwIndex, WCHAR *pwszName)
  856. {
  857. V_INAME(CDirectMusicScript::EnumRoutine);
  858. V_BUFPTR_WRITE(pwszName, MAX_PATH);
  859. *pwszName = L'\0';
  860. if (m_fZombie)
  861. {
  862. Trace(1, "Error: Call of IDirectMusicScript::EnumVariable after the script has been garbage collected. "
  863. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  864. "and then calling CollectGarbage or Release on the loader.");
  865. return DMUS_S_GARBAGE_COLLECTED;
  866. }
  867. if (!m_pScriptManager || !m_pPerformance8)
  868. {
  869. Trace(1, "Error: IDirectMusicScript::Init must be called before IDirectMusicScript::EnumVariable.\n");
  870. return DMUS_E_NOT_INIT;
  871. }
  872. int cScriptItems = 0;
  873. HRESULT hr = m_pScriptManager->EnumItem(false, dwIndex, pwszName, &cScriptItems);
  874. if (FAILED(hr))
  875. return hr;
  876. if (hr == S_FALSE)
  877. {
  878. // There are also items in the script's container that we need to report.
  879. assert(dwIndex >= cScriptItems);
  880. hr = m_pContainerDispatch->EnumItem(dwIndex - cScriptItems, pwszName);
  881. }
  882. return hr;
  883. }
  884. STDMETHODIMP
  885. CDirectMusicScript::ScriptTrackCallRoutine(
  886. WCHAR *pwszRoutineName,
  887. IDirectMusicSegmentState *pSegSt,
  888. DWORD dwVirtualTrackID,
  889. bool fErrorPMsgsEnabled,
  890. __int64 i64IntendedStartTime,
  891. DWORD dwIntendedStartTimeFlags)
  892. {
  893. V_INAME(CDirectMusicScript::CallRoutine);
  894. V_BUFPTR_READ(pwszRoutineName, 2);
  895. V_INTERFACE(pSegSt);
  896. if (m_fZombie)
  897. {
  898. Trace(1, "Error: Script track attempted to call a routine after the script has been garbage collected. "
  899. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  900. "and then calling CollectGarbage or Release on the loader.");
  901. return DMUS_S_GARBAGE_COLLECTED;
  902. }
  903. SmartRef::CritSec CS(&m_CriticalSection);
  904. if (!m_pScriptManager || !m_pPerformance8)
  905. {
  906. Trace(1, "Error: Unitialized Script elements in an attempt to call a Script Routine.\n");
  907. return DMUS_E_NOT_INIT;
  908. }
  909. return m_pScriptManager->ScriptTrackCallRoutine(
  910. pwszRoutineName,
  911. pSegSt,
  912. dwVirtualTrackID,
  913. fErrorPMsgsEnabled,
  914. i64IntendedStartTime,
  915. dwIntendedStartTimeFlags);
  916. }
  917. STDMETHODIMP
  918. CDirectMusicScript::GetTypeInfoCount(UINT *pctinfo)
  919. {
  920. V_INAME(CDirectMusicScript::GetTypeInfoCount);
  921. V_PTR_WRITE(pctinfo, UINT);
  922. *pctinfo = 0;
  923. if (m_fZombie)
  924. {
  925. Trace(1, "Error: Call of IDirectMusicScript::GetTypeInfoCount after the script has been garbage collected. "
  926. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  927. "and then calling CollectGarbage or Release on the loader.");
  928. return DMUS_S_GARBAGE_COLLECTED;
  929. }
  930. return S_OK;
  931. }
  932. STDMETHODIMP
  933. CDirectMusicScript::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo)
  934. {
  935. *ppTInfo = NULL;
  936. return E_NOTIMPL;
  937. }
  938. STDMETHODIMP
  939. CDirectMusicScript::GetIDsOfNames(
  940. REFIID riid,
  941. LPOLESTR __RPC_FAR *rgszNames,
  942. UINT cNames,
  943. LCID lcid,
  944. DISPID __RPC_FAR *rgDispId)
  945. {
  946. if (m_fZombie)
  947. {
  948. if (rgDispId)
  949. {
  950. for (int i = 0; i < cNames; ++i)
  951. {
  952. rgDispId[i] = DISPID_UNKNOWN;
  953. }
  954. }
  955. Trace(1, "Error: Call of GetIDsOfNames after a script has been garbage collected. "
  956. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  957. "and then calling CollectGarbage or Release on the loader.");
  958. return DMUS_S_GARBAGE_COLLECTED;
  959. }
  960. if (!m_pScriptManager || !m_pPerformance8)
  961. {
  962. Trace(1, "Error: IDirectMusicScript::Init must be called before GetIDsOfNames.\n");
  963. return DMUS_E_NOT_INIT;
  964. }
  965. return m_pScriptManager->DispGetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId);
  966. }
  967. STDMETHODIMP
  968. CDirectMusicScript::Invoke(
  969. DISPID dispIdMember,
  970. REFIID riid,
  971. LCID lcid,
  972. WORD wFlags,
  973. DISPPARAMS __RPC_FAR *pDispParams,
  974. VARIANT __RPC_FAR *pVarResult,
  975. EXCEPINFO __RPC_FAR *pExcepInfo,
  976. UINT __RPC_FAR *puArgErr)
  977. {
  978. if (m_fZombie)
  979. {
  980. if (pVarResult)
  981. DMS_VariantInit(m_fUseOleAut, pVarResult);
  982. Trace(1, "Error: Call of Invoke after the script has been garbage collected. "
  983. "It is invalid to continue using a script after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
  984. "and then calling CollectGarbage or Release on the loader.");
  985. return DMUS_S_GARBAGE_COLLECTED;
  986. }
  987. if (!m_pScriptManager || !m_pPerformance8)
  988. {
  989. Trace(1, "Error: IDirectMusicScript::Init must be called before Invoke.\n");
  990. return DMUS_E_NOT_INIT;
  991. }
  992. return m_pScriptManager->DispInvoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
  993. }
  994. //////////////////////////////////////////////////////////////////////
  995. // Methods that allow CActiveScriptManager access to private script interfaces
  996. IDispatch *CDirectMusicScript::GetGlobalDispatch()
  997. {
  998. assert(m_pGlobalDispatch);
  999. return m_pGlobalDispatch;
  1000. }