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.

764 lines
19 KiB

  1. //
  2. // This module provides the following functions:
  3. //
  4. // CvtDlgToDlgEx - Converts a DLGTEMPLATE to a DLGTEMPLATEEX
  5. //
  6. //
  7. #include "ctlspriv.h"
  8. #include "dlgcvt.h"
  9. //
  10. // Define the amount (bytes) the stream buffer grows when required.
  11. // It will grow enough to satisfy the required write PLUS this
  12. // amount.
  13. //
  14. #ifdef DEBUG
  15. # define STREAM_GROW_BYTES 32 // Exercise stream growth.
  16. #else
  17. # define STREAM_GROW_BYTES 512
  18. #endif
  19. //
  20. // Simple MIN/MAX inline helpers.
  21. //
  22. template <class T>
  23. inline const T& MIN(const T& a, const T& b)
  24. {
  25. return a < b ? a : b;
  26. }
  27. template <class T>
  28. inline const T& MAX(const T& a, const T& b)
  29. {
  30. return a > b ? a : b;
  31. }
  32. //
  33. // This class implements a simple dynamic stream that grows as you
  34. // add data to it. It's modeled after the strstream class provided
  35. // by the C++ std lib. Unlike the std lib implementation, this one
  36. // doesn't require C++ EH to be enabled. If comctl32 compiled with
  37. // C++ EH enabled, I would have used strstream instead.
  38. // [brianau - 10/5/98]
  39. //
  40. class CByteStream
  41. {
  42. public:
  43. explicit CByteStream(int cbDefGrow = 512);
  44. ~CByteStream(void);
  45. //
  46. // Used as argument to AlignXXXX member functions.
  47. //
  48. enum AlignType { eAlignWrite, eAlignRead };
  49. //
  50. // Basic read/write functions.
  51. //
  52. int Read(LPVOID pb, int cb);
  53. int Write(const VOID *pb, int cb);
  54. //
  55. // Determine if there was an error when reading or
  56. // writing to the stream.
  57. //
  58. bool ReadError(void) const
  59. { return m_bReadErr; }
  60. bool WriteError(void) const
  61. { return m_bWriteErr; }
  62. //
  63. // Reset the stream read or write pointer.
  64. //
  65. void ResetRead(void)
  66. { m_pbRead = m_pbBuf; m_bReadErr = false; }
  67. void ResetWrite(void)
  68. { m_pbWrite = m_pbBuf; m_bWriteErr = false; }
  69. //
  70. // Reset the stream.
  71. //
  72. void Reset(void);
  73. //
  74. // These functions align the read and write stream pointers.
  75. //
  76. void AlignReadWord(void)
  77. { Align(eAlignRead, sizeof(WORD)); }
  78. void AlignReadDword(void)
  79. { Align(eAlignRead, sizeof(DWORD)); }
  80. void AlignReadQword(void)
  81. { Align(eAlignRead, sizeof(ULONGLONG)); }
  82. void AlignWriteWord(void)
  83. { Align(eAlignWrite, sizeof(WORD)); }
  84. void AlignWriteDword(void)
  85. { Align(eAlignWrite, sizeof(DWORD)); }
  86. void AlignWriteQword(void)
  87. { Align(eAlignWrite, sizeof(ULONGLONG)); }
  88. //
  89. // GetBuffer returns the address of the stream buffer in memory.
  90. // The buffer is "frozen" so it will not be released if the stream
  91. // object is destroyed. At this point, you own the buffer.
  92. // If bPermanent is false, you can call ReleaseBuffer to return
  93. // control of the buffer to the stream object.
  94. //
  95. LPBYTE GetBuffer(bool bPermanent = false);
  96. //
  97. // ReleaseBuffer returns control of the buffer obtained with GetBuffer
  98. // to the stream object.
  99. //
  100. bool ReleaseBuffer(LPBYTE pbBuf);
  101. //
  102. // Overload the insertion and extraction operators so we can
  103. // work like a normal std lib stream class.
  104. //
  105. template <class T>
  106. CByteStream& operator >> (T& x)
  107. { Read(&x, sizeof(x)); return *this; }
  108. template <class T>
  109. CByteStream& operator << (const T& x)
  110. { Write(&x, sizeof(x)); return *this; }
  111. private:
  112. int m_cbDefGrow; // Default amount (bytes) to grow when expanding buffer.
  113. LPBYTE m_pbBuf; // Addr of allocated buffer.
  114. LPBYTE m_pbRead; // Addr for next read.
  115. LPBYTE m_pbWrite; // Addr for next write.
  116. LPBYTE m_pbEnd; // Addr of byte following last byte in buffer.
  117. bool m_bWriteErr; // Any read errors?
  118. bool m_bReadErr; // Any write errors?
  119. bool m_bOwnsBuf; // true == delete buffer in dtor.
  120. //
  121. // Expand the buffer as needed.
  122. //
  123. bool GrowBuffer(int cb = 0);
  124. //
  125. // Align the read or write buffer pointer.
  126. // Used internally by the AlignXXXXX member functions.
  127. //
  128. void Align(AlignType a, size_t n);
  129. //
  130. // Internal consistency checks for debug builds.
  131. //
  132. void Validate(void) const;
  133. //
  134. // Prevent copy.
  135. //
  136. CByteStream(const CByteStream& rhs);
  137. CByteStream& operator = (const CByteStream& rhs);
  138. };
  139. //
  140. // Class for converting in-memory dialog templates between the two
  141. // structures DLGTEMPLATE <-> DLGTEMPLATEEX.
  142. //
  143. // Currently, the object only converts from DLGTEMPLATE -> DLGTEMPLATEEX.
  144. // It would be simple to create the code for the inverse conversion. However,
  145. // it's currently not needed so I didn't create it.
  146. //
  147. class CDlgTemplateConverter
  148. {
  149. public:
  150. explicit CDlgTemplateConverter(int iCharSet = DEFAULT_CHARSET)
  151. : m_iCharset(iCharSet),
  152. m_stm(STREAM_GROW_BYTES) { }
  153. ~CDlgTemplateConverter(void) { }
  154. HRESULT DlgToDlgEx(LPDLGTEMPLATE pTemplateIn, LPDLGTEMPLATEEX *ppTemplateOut);
  155. HRESULT DlgExToDlg(LPDLGTEMPLATEEX pTemplateIn, LPDLGTEMPLATE *ppTemplateOut)
  156. { return E_NOTIMPL; }
  157. private:
  158. int m_iCharset;
  159. CByteStream m_stm; // For converted template.
  160. HRESULT DlgHdrToDlgEx(CByteStream& s, LPWORD *ppw);
  161. HRESULT DlgItemToDlgEx(CByteStream& s, LPWORD *ppw);
  162. HRESULT DlgExHdrToDlg(CByteStream& s, LPWORD *ppw)
  163. { return E_NOTIMPL; }
  164. HRESULT DlgExItemToDlg(CByteStream& s, LPWORD *ppw)
  165. { return E_NOTIMPL; }
  166. //
  167. // Copy a string from pszW into a CByteStream object.
  168. // Copies at most cch chars. If cch is -1, assumes the string is
  169. // nul-terminated and will copy all chars in string including
  170. // terminating NULL.
  171. //
  172. int CopyStringW(CByteStream& stm, LPWSTR pszW, int cch = -1);
  173. //
  174. // Prevent copy.
  175. //
  176. CDlgTemplateConverter(const CDlgTemplateConverter& rhs);
  177. CDlgTemplateConverter& operator = (const CDlgTemplateConverter& rhs);
  178. };
  179. //
  180. // Generic alignment function.
  181. // Give it an address and an alignment size and it returns
  182. // the address adjusted for the requested alignment.
  183. //
  184. // n : 2 = 16-bit
  185. // 4 = 32-bit
  186. // 8 = 64-bit
  187. //
  188. LPVOID Align(LPVOID pv, size_t n)
  189. {
  190. const ULONG_PTR x = static_cast<ULONG_PTR>(n) - 1;
  191. return reinterpret_cast<LPVOID>((reinterpret_cast<ULONG_PTR>(pv) + x) & ~x);
  192. }
  193. inline LPVOID AlignWord(LPVOID pv)
  194. {
  195. return ::Align(pv, sizeof(WORD));
  196. }
  197. inline LPVOID AlignDWord(LPVOID pv)
  198. {
  199. return ::Align(pv, sizeof(DWORD));
  200. }
  201. inline LPVOID AlignQWord(LPVOID pv)
  202. {
  203. return ::Align(pv, sizeof(ULONGLONG));
  204. }
  205. CByteStream::CByteStream(
  206. int cbDefGrow
  207. ) : m_cbDefGrow(MAX(cbDefGrow, 1)),
  208. m_pbBuf(NULL),
  209. m_pbRead(NULL),
  210. m_pbWrite(NULL),
  211. m_pbEnd(NULL),
  212. m_bWriteErr(false),
  213. m_bReadErr(false),
  214. m_bOwnsBuf(true)
  215. {
  216. }
  217. CByteStream::~CByteStream(
  218. void
  219. )
  220. {
  221. if (m_bOwnsBuf && NULL != m_pbBuf)
  222. {
  223. LocalFree(m_pbBuf);
  224. }
  225. }
  226. //
  227. // Simple checks to validate stream state.
  228. // In non-debug builds, this will be a no-op.
  229. // Use ASSERT_VALIDSTREAM macro.
  230. //
  231. void
  232. CByteStream::Validate(
  233. void
  234. ) const
  235. {
  236. ASSERT(m_pbEnd >= m_pbBuf);
  237. ASSERT(m_pbWrite >= m_pbBuf);
  238. ASSERT(m_pbRead >= m_pbBuf);
  239. ASSERT(m_pbWrite <= m_pbEnd);
  240. ASSERT(m_pbRead <= m_pbEnd);
  241. }
  242. #ifdef DEBUG
  243. # define ASSERT_VALIDSTREAM(ps) ps->Validate()
  244. #else
  245. # define ASSERT_VALIDSTREAM(ps)
  246. #endif
  247. //
  248. // Read "cb" bytes from the stream and write them to
  249. // the location specified in "pb". Return number
  250. // of bytes read. Note that if we don't "own" the
  251. // buffer (i.e. the client has called GetBuffer but
  252. // not ReleaseBuffer), no read will occur.
  253. //
  254. int
  255. CByteStream::Read(
  256. LPVOID pb,
  257. int cb
  258. )
  259. {
  260. ASSERT_VALIDSTREAM(this);
  261. int cbRead = 0;
  262. if (m_bOwnsBuf)
  263. {
  264. cbRead = MIN(static_cast<int>(m_pbEnd - m_pbRead), cb);
  265. CopyMemory(pb, m_pbRead, cbRead);
  266. m_pbRead += cbRead;
  267. if (cb != cbRead)
  268. m_bReadErr = true;
  269. }
  270. ASSERT_VALIDSTREAM(this);
  271. return cbRead;
  272. }
  273. //
  274. // Write "cb" bytes from location "pb" into the stream.
  275. // Return number of bytes written. Note that if we don't "own" the
  276. // buffer (i.e. the client has called GetBuffer but
  277. // not ReleaseBuffer), no write will occur.
  278. //
  279. int
  280. CByteStream::Write(
  281. const VOID *pb,
  282. int cb
  283. )
  284. {
  285. ASSERT_VALIDSTREAM(this);
  286. int cbWritten = 0;
  287. if (m_bOwnsBuf)
  288. {
  289. if (m_pbWrite + cb < m_pbEnd ||
  290. GrowBuffer(static_cast<int>(m_pbEnd - m_pbBuf) + cb + m_cbDefGrow))
  291. {
  292. CopyMemory(m_pbWrite, pb, cb);
  293. m_pbWrite += cb;
  294. cbWritten = cb;
  295. }
  296. else
  297. m_bWriteErr = true;
  298. }
  299. ASSERT_VALIDSTREAM(this);
  300. return cbWritten;
  301. }
  302. //
  303. // Reallocate the buffer by cb or m_cbDefGrow.
  304. // Copy existing contents to new buffer. All internal
  305. // pointers are updated.
  306. //
  307. bool
  308. CByteStream::GrowBuffer(
  309. int cb // optional. Default is 0 causing us to use m_cbDefGrow.
  310. )
  311. {
  312. bool bResult = false;
  313. int cbGrow = 0 < cb ? cb : m_cbDefGrow;
  314. ULONG_PTR ulReadOfs = m_pbRead - m_pbBuf;
  315. ULONG_PTR ulWriteOfs = m_pbWrite - m_pbBuf;
  316. ULONG_PTR cbAlloc = m_pbEnd - m_pbBuf;
  317. LPBYTE pNew = static_cast<LPBYTE>(LocalAlloc(LPTR, cbAlloc + cbGrow));
  318. if (NULL != pNew)
  319. {
  320. if (NULL != m_pbBuf)
  321. {
  322. CopyMemory(pNew, m_pbBuf, cbAlloc);
  323. LocalFree(m_pbBuf);
  324. }
  325. m_pbBuf = pNew;
  326. m_pbRead = m_pbBuf + ulReadOfs;
  327. m_pbWrite = m_pbBuf + ulWriteOfs;
  328. m_pbEnd = m_pbBuf + cbAlloc + cbGrow;
  329. bResult = true;
  330. }
  331. ASSERT_VALIDSTREAM(this);
  332. return bResult;
  333. }
  334. //
  335. // Align the read or write pointer on the stream.
  336. // The write pointer is aligned by padding skipped bytes with 0.
  337. //
  338. void
  339. CByteStream::Align(
  340. CByteStream::AlignType a,
  341. size_t n
  342. )
  343. {
  344. static const BYTE fill[8] = {0};
  345. if (m_bOwnsBuf)
  346. {
  347. switch(a)
  348. {
  349. case eAlignWrite:
  350. Write(fill, static_cast<int>(reinterpret_cast<LPBYTE>(::Align(m_pbWrite, n)) - m_pbWrite));
  351. break;
  352. case eAlignRead:
  353. m_pbRead = reinterpret_cast<LPBYTE>(::Align(m_pbRead, n));
  354. if (m_pbRead >= m_pbEnd)
  355. m_bReadErr = true;
  356. break;
  357. default:
  358. break;
  359. }
  360. }
  361. ASSERT_VALIDSTREAM(this);
  362. }
  363. //
  364. // Caller takes ownership of the buffer.
  365. //
  366. LPBYTE
  367. CByteStream::GetBuffer(
  368. bool bPermanent // optional. Default is false.
  369. )
  370. {
  371. LPBYTE pbRet = m_pbBuf;
  372. if (bPermanent)
  373. {
  374. //
  375. // Caller now permanently owns the buffer.
  376. // Can't return it through ReleaseBuffer().
  377. // Reset the internal stream control values.
  378. //
  379. m_pbBuf = m_pbWrite = m_pbRead = m_pbEnd = NULL;
  380. m_bWriteErr = m_bReadErr = false;
  381. m_bOwnsBuf = true;
  382. }
  383. else
  384. {
  385. //
  386. // Caller now owns the buffer but it can be returned
  387. // through ReleaseBuffer().
  388. //
  389. m_bOwnsBuf = false;
  390. }
  391. return pbRet;
  392. }
  393. //
  394. // Take back ownership of the buffer.
  395. // Returns:
  396. //
  397. // true = CByteStream object took back ownership.
  398. // false = CByteStream object couldn't take ownership.
  399. //
  400. bool
  401. CByteStream::ReleaseBuffer(
  402. LPBYTE pbBuf
  403. )
  404. {
  405. if (pbBuf == m_pbBuf)
  406. {
  407. m_bOwnsBuf = true;
  408. return true;
  409. }
  410. return false;
  411. }
  412. //
  413. // Reset the stream.
  414. //
  415. void
  416. CByteStream::Reset(
  417. void
  418. )
  419. {
  420. if (NULL != m_pbBuf)
  421. {
  422. LocalFree(m_pbBuf);
  423. }
  424. m_pbBuf = m_pbWrite = m_pbRead = m_pbEnd = NULL;
  425. m_bWriteErr = m_bReadErr = false;
  426. m_bOwnsBuf = true;
  427. }
  428. //
  429. // Copy one or more WORDs from the location provided in "pszW" into
  430. // the stream. If cch is -1, it's assumed that the string is nul-terminated.
  431. // Returns the number of WCHARs written.
  432. //
  433. int
  434. CDlgTemplateConverter::CopyStringW(
  435. CByteStream& stm,
  436. LPWSTR pszW,
  437. int cch
  438. )
  439. {
  440. if (-1 == cch)
  441. cch = lstrlenW(pszW) + 1;
  442. return stm.Write(pszW, cch * sizeof(WCHAR)) / sizeof(WCHAR);
  443. }
  444. //
  445. // Convert a DLGTEMPLATE structure to a DLGTEMPLATEEX structure.
  446. // pti is the address of the DLGTEMPLATE to be converted.
  447. // ppto points to a LPDLGTEMPLATEEX ptr to receive the address of the
  448. // converted template structure. Caller is responsible for freeing
  449. // this buffer with LocalFree.
  450. //
  451. // Returns: E_OUTOFMEMORY, NOERROR
  452. //
  453. HRESULT
  454. CDlgTemplateConverter::DlgToDlgEx(
  455. LPDLGTEMPLATE pti,
  456. LPDLGTEMPLATEEX *ppto
  457. )
  458. {
  459. HRESULT hr = NOERROR;
  460. LPWORD pw = reinterpret_cast<LPWORD>(pti);
  461. *ppto = NULL;
  462. //
  463. // Reset the stream.
  464. //
  465. m_stm.Reset();
  466. //
  467. // Convert DLGTEMPLATE -> DLGTEMPLATEEX
  468. //
  469. hr = DlgHdrToDlgEx(m_stm, &pw);
  470. //
  471. // Convert each DLGITEMTEMPLATE -> DLGITEMTEMPLATEEX
  472. //
  473. for (int i = 0; i < pti->cdit && SUCCEEDED(hr); i++)
  474. {
  475. pw = reinterpret_cast<LPWORD>(::AlignDWord(pw));
  476. m_stm.AlignWriteDword();
  477. hr = DlgItemToDlgEx(m_stm, &pw);
  478. }
  479. if (SUCCEEDED(hr))
  480. {
  481. //
  482. // Return the buffer to the caller. Buffer is permanently
  483. // detached from the stream object so the stream's dtor
  484. // won't free it.
  485. //
  486. *ppto = reinterpret_cast<LPDLGTEMPLATEEX>(m_stm.GetBuffer(true));
  487. }
  488. return hr;
  489. };
  490. //
  491. // Convert DLGTEMPLATE -> DLGTEMPLATEEX
  492. //
  493. // s = Stream to hold converted template.
  494. // ppw = Address of current read pointer into the template being converted.
  495. // On exit, the referenced pointer is updated with the current read location.
  496. //
  497. // Returns: E_OUTOFMEMORY, NOERROR
  498. //
  499. HRESULT
  500. CDlgTemplateConverter::DlgHdrToDlgEx(
  501. CByteStream& s,
  502. LPWORD *ppw
  503. )
  504. {
  505. LPWORD pw = *ppw;
  506. LPDLGTEMPLATE pt = reinterpret_cast<LPDLGTEMPLATE>(pw);
  507. //
  508. // Convert the fixed-length stuff.
  509. //
  510. s << static_cast<WORD>(1) // wDlgVer
  511. << static_cast<WORD>(0xFFFF) // wSignature
  512. << static_cast<DWORD>(0) // dwHelpID
  513. << static_cast<DWORD>(pt->dwExtendedStyle)
  514. << static_cast<DWORD>(pt->style)
  515. << static_cast<WORD>(pt->cdit)
  516. << static_cast<short>(pt->x)
  517. << static_cast<short>(pt->y)
  518. << static_cast<short>(pt->cx)
  519. << static_cast<short>(pt->cy);
  520. //
  521. // Arrays are always WORD aligned.
  522. //
  523. pw = reinterpret_cast<LPWORD>(::AlignWord(reinterpret_cast<LPBYTE>(pw) + sizeof(DLGTEMPLATE)));
  524. s.AlignWriteWord();
  525. //
  526. // Copy the menu array.
  527. //
  528. switch(*pw)
  529. {
  530. case 0xFFFF:
  531. s << *pw++;
  532. //
  533. // Fall through...
  534. //
  535. case 0x0000:
  536. s << *pw++;
  537. break;
  538. default:
  539. pw += CopyStringW(s, (LPWSTR)pw);
  540. break;
  541. };
  542. //
  543. // Copy the class array.
  544. //
  545. switch(*pw)
  546. {
  547. case 0xFFFF:
  548. s << *pw++;
  549. //
  550. // Fall through...
  551. //
  552. case 0x0000:
  553. s << *pw++;
  554. break;
  555. default:
  556. pw += CopyStringW(s, (LPWSTR)pw);
  557. break;
  558. };
  559. //
  560. // Copy the title array.
  561. //
  562. switch(*pw)
  563. {
  564. case 0x0000:
  565. s << *pw++;
  566. break;
  567. default:
  568. pw += CopyStringW(s, (LPWSTR)pw);
  569. break;
  570. };
  571. //
  572. // Copy font information if it's present.
  573. //
  574. if (DS_SETFONT & pt->style)
  575. {
  576. s << *pw++; // pt size
  577. s << static_cast<WORD>(FW_NORMAL); // weight (default, not in DLGTEMPLATE)
  578. s << static_cast<BYTE>(FALSE); // italic (default, not in DLGTEMPLATE)
  579. s << static_cast<BYTE>(m_iCharset); // charset (default if not given,
  580. // not in DLGTEMPLATE)
  581. pw += CopyStringW(s, (LPWSTR)pw);
  582. }
  583. *ppw = pw;
  584. return s.WriteError() ? E_OUTOFMEMORY : NOERROR;
  585. }
  586. //
  587. // Convert DLGITEMTEMPLATE -> DLGITEMTEMPLATEEX
  588. //
  589. // s = Stream to hold converted template.
  590. // ppw = Address of current read pointer into the template being converted.
  591. // On exit, the referenced pointer is updated with the current read location.
  592. //
  593. // Returns: E_OUTOFMEMORY, NOERROR
  594. //
  595. HRESULT
  596. CDlgTemplateConverter::DlgItemToDlgEx(
  597. CByteStream& s,
  598. LPWORD *ppw
  599. )
  600. {
  601. LPWORD pw = *ppw;
  602. LPDLGITEMTEMPLATE pit = reinterpret_cast<LPDLGITEMTEMPLATE>(pw);
  603. //
  604. // Convert the fixed-length stuff.
  605. //
  606. s << static_cast<DWORD>(0) // dwHelpID
  607. << static_cast<DWORD>(pit->dwExtendedStyle)
  608. << static_cast<DWORD>(pit->style)
  609. << static_cast<short>(pit->x)
  610. << static_cast<short>(pit->y)
  611. << static_cast<short>(pit->cx)
  612. << static_cast<short>(pit->cy)
  613. << static_cast<DWORD>(pit->id);
  614. //
  615. // Arrays are always word aligned.
  616. //
  617. pw = reinterpret_cast<LPWORD>(::AlignWord(reinterpret_cast<LPBYTE>(pw) + sizeof(DLGITEMTEMPLATE)));
  618. s.AlignWriteWord();
  619. //
  620. // Copy the class array.
  621. //
  622. switch(*pw)
  623. {
  624. case 0xFFFF:
  625. s << *pw++;
  626. s << *pw++; // Class code.
  627. break;
  628. default:
  629. pw += CopyStringW(s, (LPWSTR)pw);
  630. break;
  631. };
  632. //
  633. // Copy the title array.
  634. //
  635. switch(*pw)
  636. {
  637. case 0xFFFF:
  638. s << *pw++;
  639. s << *pw++; // Resource ordinal value.
  640. break;
  641. default:
  642. pw += CopyStringW(s, (LPWSTR)pw);
  643. break;
  644. };
  645. //
  646. // Copy the creation data.
  647. // *pw is either 0 or the number of bytes of creation data,
  648. // including *pw.
  649. //
  650. switch(*pw)
  651. {
  652. case 0x0000:
  653. s << *pw++;
  654. break;
  655. default:
  656. pw += s.Write(pw, *pw) / sizeof(WORD);
  657. break;
  658. };
  659. *ppw = pw;
  660. return s.WriteError() ? E_OUTOFMEMORY : NOERROR;
  661. }
  662. //
  663. // This is the public function for converting a DLGTEMPLATE to
  664. // a DLGTEMPLATEEX.
  665. //
  666. // Returns: E_OUTOFMEMORY, NOERROR
  667. //
  668. HRESULT
  669. CvtDlgToDlgEx(
  670. LPDLGTEMPLATE pTemplate,
  671. LPDLGTEMPLATEEX *ppTemplateExOut,
  672. int iCharset
  673. )
  674. {
  675. CDlgTemplateConverter dtc(iCharset);
  676. return dtc.DlgToDlgEx(pTemplate, ppTemplateExOut);
  677. }