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.

613 lines
22 KiB

  1. /*--------------------------------------------------------------------------*
  2. *
  3. * Microsoft Windows
  4. * Copyright (C) Microsoft Corporation, 1999 - 1999
  5. *
  6. * File: xmlfile.cpp
  7. *
  8. * Contents: Implements extracting console icon from XML file
  9. *
  10. * History: 17-Dec-99 audriusz Created
  11. *
  12. *--------------------------------------------------------------------------*/
  13. #include "stdafx.h"
  14. #include "shlobj.h"
  15. #include "Extract.h"
  16. #include "base64.h"
  17. #include "xmlfile.h"
  18. #include "strings.h"
  19. //---------------------------------------------------------------------------
  20. // static (private) implementation helpers used thru this file
  21. //---------------------------------------------------------------------------
  22. static bool FindStringInData( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR pstrKey);
  23. static HRESULT DecodeBase64Fragment( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, HGLOBAL& hgResult);
  24. static HRESULT FindAndReadIconData(LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR strIconName, HGLOBAL& hglobIcon);
  25. static HRESULT LoadIconFromHGlobal(HGLOBAL hData, HICON& hIcon);
  26. static HRESULT ValidateXMLDocument(LPCSTR &pFileData, DWORD &dwLen, int *piBytesPerEnglishChar = NULL);
  27. // following function is a friend of class CXMLPersistableIcon. If renaming, take this into accnt
  28. static HRESULT LoadIconFromXMLData(LPCSTR pFileData, DWORD dwLen, CPersistableIcon &persistableIcon);
  29. /***************************************************************************\
  30. *
  31. * FUNCTION: FindStringInData
  32. *
  33. * PURPOSE: This function locates the string in provided data
  34. * NOTE - it matches first byte only (codepage of UNICODE string is ignored)
  35. *
  36. * PARAMETERS:
  37. * LPCSTR &pstrSource - [in/out] - data to search thru / possition
  38. * of the first char following the found match
  39. * int nBytesPerChar - [in] - width of the character
  40. * ( only the first byte of each character will be examined )
  41. * DWORD &dwCharsLeft - [in/out] - init. data len / data left after matching string
  42. * LPCSTR pstrKey - [in] - substring to search
  43. *
  44. * RETURNS:
  45. * bool - true if succeeded
  46. *
  47. \***************************************************************************/
  48. // Following sample illustrates algorithm used for the search.
  49. // we will try to locate "Console" in the string "Microsoft Management Console"
  50. //------------------------------------------------
  51. // Standard search (a la strstr)
  52. //------------------------------------------------
  53. // 1.
  54. // Microsoft Management Console
  55. // Console <- comparing (fails - move to the next char)
  56. // 2.
  57. // Microsoft Management Console
  58. // Console <- comparing (fails - move to the next char)
  59. // ------------------------ (19 steps skipped)
  60. // 22.
  61. // Console <- comparing (succeeds)
  62. //------------------------------------------------
  63. // More inteligent search
  64. //------------------------------------------------
  65. // 1. ! <- last char in searched seq
  66. // Microsoft Management Console
  67. // Console <- comparing (fails - last char in searched seq is 'o';
  68. // and 'o' is 3rd character from the end in the key;
  69. // so we can advance by 2 chars to match it)
  70. // 2. ! <- matching 'o' to last 'o' in the key
  71. // Microsoft Management Console
  72. // Console <- comparing (fails - last char in searched seq is 't';
  73. // 't' is not in the key
  74. // so we can advance by key length (7 chars) to skip it)
  75. // 3. ! <- pos following the last char in searched seq
  76. // Microsoft Management Console
  77. // Console <- comparing (fails - last char in searched seq is 'e';
  78. // 'e' is last character in the key;
  79. // we still can advance by key length (7 chars) to skip it)
  80. // 4. ! <- pos following the last char in searched seq
  81. // Microsoft Management Console
  82. // Console <- comparing (fails)
  83. // 5. ! <- match point
  84. // Microsoft Management Console
  85. // Console <- comparing (fails)
  86. // 6. ! <- match point
  87. // Microsoft Management Console
  88. // Console <- comparing (succeeds)
  89. //------------------------------------------------
  90. static bool
  91. FindStringInData( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR pstrKey)
  92. {
  93. typedef unsigned short KeyLen_t;
  94. static KeyLen_t nKeyDist[256]; // static - to keep stack small
  95. // calculate the key length
  96. DWORD dwKeyLen = strlen(pstrKey);
  97. // test for empty search key
  98. if (!dwKeyLen)
  99. return true; // we always match empty strings
  100. // test for longer search key than data provided
  101. if (dwKeyLen > dwCharsLeft)
  102. return false; // we'll never find longer substrings than the source
  103. // key length var size is not too big to minimize tho lookup table size
  104. KeyLen_t nKeyLen = (KeyLen_t)dwKeyLen;
  105. // recheck here if the key isn't too long
  106. if ((DWORD)nKeyLen != dwKeyLen) // key len does not fit to our variable -
  107. return false; // we do not deel with such a long keys
  108. // form the table holding minimal character distance from the end of pstrKey
  109. // It is used for increasing search speed:
  110. // When key does not match at current location, [instead of trying one location ahead,]
  111. // algorythm checks the last character in sequence tested with a key (data[keylen-1]).
  112. // now we check how far this character may be from the end of the key - we will have
  113. // distance of all key length in case character is not a part of the key.
  114. // we can safelly advance by that much. Sometimes we'll be positioning the key at whole
  115. // key_len offsets from previous test position, sometimes less - depending on data.
  116. // initialize the table. The distance is keylen value for all characters not existing in the key
  117. for (unsigned i = 0; i < sizeof(nKeyDist)/sizeof(nKeyDist[0]); ++i)
  118. nKeyDist[i] = nKeyLen;
  119. // now set minimal distance for characters in the key.
  120. // Note, that the last character is not included intensionally - to make
  121. // distance to it equal to whole key length
  122. for (i = 0; i < nKeyLen - 1; ++i)
  123. nKeyDist[pstrKey[i]] = nKeyLen - (KeyLen_t)i - 1;
  124. // we are done with initialization. Time for real work.
  125. LPCSTR p = pstrSource; // to speed it up: we use local variables
  126. DWORD dwLeft = dwCharsLeft;
  127. while ( 1 )
  128. {
  129. // set the pointers to start of inspected sequence
  130. LPCSTR ke = pstrKey; // pointer to evaluating key char
  131. LPCSTR pe = p; // pointer to evaluating source char
  132. // try to match all characters in the key
  133. KeyLen_t nToMatch = nKeyLen;
  134. while ( *pe == *ke )
  135. {
  136. --nToMatch;
  137. pe += nBytesPerChar;
  138. ++ke;
  139. // inspect if there still are some chars to match
  140. if (!nToMatch)
  141. {
  142. // we return the possitive answer here
  143. // change the reference parameters accordingly
  144. // (pointing right after the string found)
  145. pstrSource = pe;
  146. dwCharsLeft = dwLeft - nKeyLen;
  147. return true;
  148. }
  149. }
  150. // chLastChar is used as an index
  151. // need to cast the char to unsigned char - else it will
  152. // not work correctly for values over 127
  153. // NTRAID#NTBUG9-185761-2000/09/18 AUDRIUSZ
  154. BYTE chLastChar = p[(nKeyLen - 1) * nBytesPerChar]; // the last char from evaluated source range
  155. // the key couldn't be found at the position we inspected.
  156. // we can advance source pointer as far as we can match
  157. // the position of the last character to any entry in the key
  158. // or whole key length else.
  159. // We have a table built for that
  160. const KeyLen_t nToSkip = nKeyDist[chLastChar];
  161. if ((DWORD)nToSkip + (DWORD)nKeyLen >= dwLeft)
  162. return false; // gone too far... ( couldn't find the match )
  163. p += (nToSkip * nBytesPerChar);
  164. dwLeft -= nToSkip;
  165. }
  166. // we will not get here anyway...
  167. return false;
  168. }
  169. /***************************************************************************\
  170. *
  171. * METHOD: DecodeBase64Fragment
  172. *
  173. * PURPOSE: decodes base64 data fragment pointed by arguments
  174. *
  175. * PARAMETERS:
  176. * LPCSTR &pstrSource - [in/out] - data to decode / possition
  177. * of the first char following the decoded data
  178. * int nBytesPerChar - [in] - width of the character
  179. * ( only the first byte of each character will be examined )
  180. * DWORD &dwCharsLeft - [in/out] - init. data len / data left after skipping converted
  181. * HGLOBAL& hgResult - decoded data
  182. *
  183. * RETURNS:
  184. * HRESULT - result code
  185. *
  186. \***************************************************************************/
  187. static HRESULT
  188. DecodeBase64Fragment( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, HGLOBAL& hgResult)
  189. {
  190. HRESULT hrStatus = S_OK;
  191. LPCSTR p = pstrSource;
  192. DWORD dwLeft = dwCharsLeft;
  193. const size_t ICON_ALLOCATION_LEN = 8*1024; // big enough to have 1 allocation in most cases
  194. LPBYTE pDynamicBuffer = NULL;
  195. LPBYTE pConversionBuffer = NULL;
  196. size_t nCharsInDynamicBuffer = 0;
  197. size_t nDynamicBufferCapacity = 0;
  198. HGLOBAL hGlobAlloc = NULL;
  199. ASSERT(hResult == NULL);
  200. static base64_table conv;
  201. // convert until done or end is found
  202. while (1)
  203. {
  204. // standard conversion. converts 4 chars (6bit each) to 3 bytes
  205. BYTE inp[4];
  206. memset(&inp, 0 ,sizeof(inp));
  207. // collect 4 characters for conversion, if possible.
  208. for (int nChars = 0; nChars < 4 && dwLeft && *p != '<' && *p != '='; --dwLeft)
  209. {
  210. BYTE bt = conv.map2six(*p);
  211. p += nBytesPerChar;
  212. if (bt != 0xff)
  213. inp[nChars++] = bt;
  214. }
  215. // if nothing to convert - we are done
  216. if (!nChars)
  217. break;
  218. // make sure we have enough storage for result
  219. if (nChars + nCharsInDynamicBuffer > nDynamicBufferCapacity)
  220. {
  221. // need to extend the dynamic buffer
  222. LPBYTE pnewBuffer = (LPBYTE)realloc(pDynamicBuffer, nDynamicBufferCapacity + ICON_ALLOCATION_LEN);
  223. if (!pnewBuffer)
  224. {
  225. hrStatus = E_OUTOFMEMORY;
  226. goto ON_ERROR;
  227. }
  228. // assign new pointer
  229. pDynamicBuffer = pnewBuffer;
  230. nDynamicBufferCapacity += ICON_ALLOCATION_LEN;
  231. pConversionBuffer = &pDynamicBuffer[nCharsInDynamicBuffer];
  232. }
  233. // decode and put the staff to the memory;
  234. int nCharsPut = conv.decode4(inp, nChars, pConversionBuffer);
  235. // update count & current pointer
  236. nCharsInDynamicBuffer += nCharsPut;
  237. pConversionBuffer += nCharsPut;
  238. }
  239. // allocate the buffer and store the result data
  240. // The same buffer is not reused for conversion, because
  241. // it's assumed to be saffer to load icon from stream, containing only
  242. // as much data as required ( we would have larger buffer, containing some
  243. // uninitialized data at the end if returning buffer used for conversion)
  244. hGlobAlloc = GlobalAlloc(GMEM_MOVEABLE, nCharsInDynamicBuffer);
  245. if (hGlobAlloc == NULL)
  246. {
  247. hrStatus = E_OUTOFMEMORY;
  248. goto ON_ERROR;
  249. }
  250. // if we have characters, copy them to result
  251. if (nCharsInDynamicBuffer)
  252. {
  253. LPVOID pResultStorage = GlobalLock(hGlobAlloc);
  254. if (pResultStorage == NULL)
  255. {
  256. hrStatus = E_OUTOFMEMORY;
  257. goto ON_ERROR;
  258. }
  259. memcpy(pResultStorage, pDynamicBuffer, nCharsInDynamicBuffer);
  260. GlobalUnlock(hGlobAlloc);
  261. }
  262. // assign the memory handle to caller
  263. hgResult = hGlobAlloc;
  264. hGlobAlloc = NULL; // assign null to avoid releasing it
  265. // adjust poiters to start from where we finished the next time
  266. pstrSource = p;
  267. dwCharsLeft = dwLeft;
  268. hrStatus = S_OK;
  269. ON_ERROR: // note: ok result falls thru as well
  270. if (hGlobAlloc)
  271. GlobalFree(hGlobAlloc);
  272. if (pDynamicBuffer)
  273. free(pDynamicBuffer);
  274. return hrStatus;
  275. }
  276. /***************************************************************************\
  277. *
  278. * METHOD: FindAndReadIconData
  279. *
  280. * PURPOSE: Function locates Icon data in the xml file data and loads it into HGLOBAL
  281. *
  282. * PARAMETERS:
  283. * LPCSTR &pstrSource - [in/out] - data to look thru / possition
  284. * of the first char following the decoded icon data
  285. * int nBytesPerChar - [in] - width of the character
  286. * ( only the first byte of each character will be examined )
  287. * DWORD &dwCharsLeft - [in/out] - init. data len / data left after skipping decoded
  288. * LPCSTR strIconName - [in] name of Icon to locate
  289. * - NOTE: it assumes icon data to be a base64-encoded stream, saved
  290. * as contents of XML element, having IconName as its attribute
  291. * HGLOBAL& hglobIcon - [out] - memory block containing icon data
  292. *
  293. * RETURNS:
  294. * HRESULT - result code
  295. *
  296. \***************************************************************************/
  297. static HRESULT FindAndReadIconData(LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR strIconName, HGLOBAL& hglobIcon)
  298. {
  299. ASSERT(hglobIcon == NULL); // we do not free data here, pass null handler!
  300. // make local vars for efficiency
  301. DWORD dwLen = dwCharsLeft;
  302. LPCSTR pstrData = pstrSource;
  303. // locate the string with the name of icon (assume it's unique enough)
  304. const bool bIconFound = FindStringInData( pstrData, nBytesPerChar, dwLen, strIconName);
  305. if (!bIconFound)
  306. return E_FAIL;
  307. // now locate the end of tag '>' ( start of the contents )
  308. const bool bStartFound = FindStringInData( pstrData, nBytesPerChar, dwLen, ">" );
  309. if (!bStartFound)
  310. return E_FAIL;
  311. HRESULT hr = DecodeBase64Fragment( pstrData, nBytesPerChar, dwLen, hglobIcon);
  312. if (FAILED(hr))
  313. return hr;
  314. // update pointers to start from where we finished the next time
  315. dwCharsLeft = dwLen;
  316. pstrSource = pstrData;
  317. return S_OK;
  318. }
  319. /***************************************************************************\
  320. *
  321. * METHOD: LoadIconFromHGlobal
  322. *
  323. * PURPOSE: Function extracts HICON from stream contained in HGLOBAL
  324. *
  325. * PARAMETERS:
  326. * HGLOBAL hData [in] - data to load from
  327. * HICON& hIcon [out] - read icon
  328. *
  329. * RETURNS:
  330. * HRESULT - result code
  331. *
  332. \***************************************************************************/
  333. static HRESULT LoadIconFromHGlobal(HGLOBAL hData, HICON& hIcon)
  334. {
  335. HRESULT hr = S_OK;
  336. // create the stream
  337. IStreamPtr spStream;
  338. hr = CreateStreamOnHGlobal(hData, FALSE/*do not release*/, &spStream);
  339. if (FAILED(hr))
  340. return hr;
  341. // read the icon as image list
  342. HIMAGELIST himl = ImageList_Read (spStream);
  343. if (!himl)
  344. return E_FAIL;
  345. // retrieve icon from image list
  346. hIcon = ImageList_GetIcon (himl, 0, ILD_NORMAL);
  347. // destroy image list (no longer need it)
  348. ImageList_Destroy (himl);
  349. return S_OK;
  350. }
  351. /***************************************************************************\
  352. *
  353. * METHOD: LoadIconFromXMLData
  354. *
  355. * PURPOSE: Loads icon from memory containing file data of XML document
  356. *
  357. * PARAMETERS:
  358. * LPCSTR pFileData - file data suspected to contain XML document
  359. * DWORD dwLen - the len of input data
  360. * CPersistableIcon &persistableIcon - Icon to initialize upon successful loading
  361. *
  362. * RETURNS:
  363. * HRESULT - result code (S_OK - icon loaded, error code else)
  364. *
  365. \***************************************************************************/
  366. static HRESULT LoadIconFromXMLData(LPCSTR pFileData, DWORD dwLen, CPersistableIcon &persistableIcon)
  367. {
  368. HRESULT hr = S_OK;
  369. int nBytesPerChar = 0;
  370. // check if we recognize the document contents
  371. hr = ValidateXMLDocument(pFileData,dwLen, &nBytesPerChar);
  372. if (hr != S_OK) // hr == S_FALSE means format is not recognized
  373. return E_FAIL;
  374. // Get required keywords.
  375. USES_CONVERSION;
  376. LPCSTR lpcstrLarge = T2CA(XML_ATTR_CONSOLE_ICON_LARGE);
  377. LPCSTR lpcstrSmall = T2CA(XML_ATTR_CONSOLE_ICON_SMALL);
  378. HICON hLargeIcon = NULL;
  379. HICON hSmallIcon = NULL;
  380. // try to read large icon first
  381. HGLOBAL hgLargeIcon = NULL;
  382. hr = FindAndReadIconData(pFileData, nBytesPerChar, dwLen, lpcstrLarge, hgLargeIcon );
  383. if (FAILED(hr))
  384. return hr;
  385. // try to read small icon ( look behind the large one - it should be there!)
  386. HGLOBAL hgSmallIcon = NULL;
  387. hr = FindAndReadIconData( pFileData, nBytesPerChar, dwLen, lpcstrSmall, hgSmallIcon );
  388. if (FAILED(hr))
  389. goto ON_ERROR;
  390. // do get the handles of the icons!
  391. hr = LoadIconFromHGlobal(hgLargeIcon, hLargeIcon);
  392. if (FAILED(hr))
  393. goto ON_ERROR;
  394. hr = LoadIconFromHGlobal(hgSmallIcon, hSmallIcon);
  395. if (FAILED(hr))
  396. goto ON_ERROR;
  397. persistableIcon.m_icon32.Attach (hLargeIcon);
  398. persistableIcon.m_icon16.Attach (hSmallIcon);
  399. // done!
  400. hr = S_OK;
  401. ON_ERROR:
  402. if (hLargeIcon && FAILED(hr))
  403. DestroyIcon(hLargeIcon);
  404. if (hSmallIcon && FAILED(hr))
  405. DestroyIcon(hSmallIcon);
  406. if (hgLargeIcon)
  407. GlobalFree(hgLargeIcon);
  408. return hr;
  409. }
  410. /***************************************************************************\
  411. *
  412. * METHOD: ExtractIconFromXMLFile
  413. *
  414. * PURPOSE: Loads icon from file containing XML document
  415. *
  416. * PARAMETERS:
  417. * LPCTSTR lpstrFileName - name of file to inspect
  418. * CPersistableIcon &persistableIcon - Icon to initialize upon successful loading
  419. *
  420. * RETURNS:
  421. * HRESULT - result code
  422. *
  423. \***************************************************************************/
  424. HRESULT ExtractIconFromXMLFile(LPCTSTR lpstrFileName, CPersistableIcon &persistableIcon)
  425. {
  426. HRESULT hrResult = S_OK;
  427. // open the file
  428. HANDLE hFile = CreateFile(lpstrFileName, GENERIC_READ, FILE_SHARE_READ,
  429. NULL, OPEN_EXISTING, 0, NULL);
  430. if (hFile == INVALID_HANDLE_VALUE)
  431. return hrResult = HRESULT_FROM_WIN32(GetLastError());
  432. // map data into virtual memory
  433. HANDLE hMapping = CreateFileMapping(hFile, NULL/*sec*/, PAGE_READONLY,
  434. 0/*sizeHi*/, 0/*sizeLo*/, NULL/*szname*/);
  435. if (hMapping == NULL)
  436. {
  437. hrResult = HRESULT_FROM_WIN32(GetLastError());
  438. CloseHandle(hFile);
  439. return hrResult;
  440. }
  441. // get pointer to physical memory
  442. LPVOID pData = MapViewOfFile(hMapping, FILE_MAP_READ, 0/*offHi*/, 0/*offLo*/, 0/*len*/);
  443. if (pData)
  444. {
  445. // we are sure here the sizeHi is zero. mapping should fail else
  446. DWORD dwLen = GetFileSize(hFile, NULL/*pSizeHi*/);
  447. // try to load icon from mapped data
  448. hrResult = LoadIconFromXMLData((LPCSTR)pData, dwLen, persistableIcon);
  449. // we do not need a view any more
  450. UnmapViewOfFile(pData);
  451. pData = NULL;
  452. // fall thru to release handles before return
  453. }
  454. else // getting the view failed
  455. {
  456. hrResult = HRESULT_FROM_WIN32(GetLastError());
  457. // fall thru to release handles before return
  458. }
  459. CloseHandle(hMapping);
  460. CloseHandle(hFile);
  461. return hrResult;
  462. }
  463. /***************************************************************************\
  464. *
  465. * METHOD: ValidateXMLDocument
  466. *
  467. * PURPOSE: Validates XML document loaded into memory
  468. * NOTE: it's rather __VERY__ weak inspection. it only checks if doc starts with '<'
  469. *
  470. * PARAMETERS:
  471. * LPCSTR &pFileData - [in/out] - data to look thru / start of xml documet contents
  472. * DWORD &dwLen - [in/out] - init. data len / data left after skipping header
  473. * int *piBytesPerEnglishChar - [out, optional] - bytes occupied by english character
  474. *
  475. * RETURNS:
  476. * HRESULT - (S_FALSE - data does not qualify for XML document)
  477. *
  478. \***************************************************************************/
  479. static HRESULT
  480. ValidateXMLDocument(LPCSTR &pFileData, DWORD &dwLen, int *piBytesPerEnglishChar /*= NULL*/)
  481. {
  482. // default to ansi when not sure
  483. int nBytesPerChar = 1;
  484. if (dwLen >= 2)
  485. {
  486. // raw UNICODE big endian ?
  487. if ((unsigned char)pFileData[1] == 0xff && (unsigned char)pFileData[0] == 0xfe)
  488. {
  489. // to maintain simplicity of the code, we will treat this like little endian.
  490. // we just position file pointer incorrectly.
  491. // since everything we are intersted:
  492. // - is in page 0 (xml tags and base 64)
  493. // - never is at the end of file ( closing tags expected )
  494. // - we do not care about the page of any data
  495. // :we can mix the page codes of the elements and pretend dealing w/ little endian
  496. pFileData += 3; // skip UNICODE signature and first page number
  497. dwLen -= 3;
  498. dwLen /= 2; // we count characters - seems like we have less of them
  499. nBytesPerChar = 2;
  500. }
  501. // raw UNICODE little endian ?
  502. else if ((unsigned char)pFileData[0] == 0xff && (unsigned char)pFileData[1] == 0xfe)
  503. {
  504. pFileData += 2; // skip UNICODE signature
  505. dwLen -= 2;
  506. dwLen /= 2; // we count characters - seems like we have less of them
  507. nBytesPerChar = 2;
  508. }
  509. // compressed UNICODE (UTF 8) ?
  510. else if (dwLen >= 2 && (unsigned char)pFileData[0] == 0xef
  511. && (unsigned char)pFileData[1] == 0xbb && (unsigned char)pFileData[2] == 0xbf)
  512. {
  513. //just skip signature and treat it as ANSI
  514. pFileData += 3; // skip UNICODE signature
  515. dwLen -= 3;
  516. nBytesPerChar = 1;
  517. }
  518. }
  519. // skip whitespaces
  520. char ch;
  521. while (dwLen && (((ch = *pFileData)==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r')))
  522. {
  523. pFileData += nBytesPerChar;
  524. --dwLen;
  525. }
  526. // check if we have a valid xml file (it should open with '<')
  527. if (!dwLen || *pFileData != '<')
  528. return S_FALSE;
  529. if (piBytesPerEnglishChar)
  530. *piBytesPerEnglishChar = nBytesPerChar;
  531. return S_OK;
  532. }