CompareInfo.Windows.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Buffers;
  5. using System.Diagnostics;
  6. using System.Runtime.InteropServices;
  7. using Internal.Runtime.CompilerServices;
  8. namespace System.Globalization
  9. {
  10. public partial class CompareInfo
  11. {
  12. private unsafe void InitSort(CultureInfo culture)
  13. {
  14. _sortName = culture.SortName;
  15. if (GlobalizationMode.Invariant)
  16. {
  17. _sortHandle = IntPtr.Zero;
  18. }
  19. else
  20. {
  21. const uint LCMAP_SORTHANDLE = 0x20000000;
  22. IntPtr handle;
  23. int ret = Interop.Kernel32.LCMapStringEx(_sortName, LCMAP_SORTHANDLE, null, 0, &handle, IntPtr.Size, null, null, IntPtr.Zero);
  24. _sortHandle = ret > 0 ? handle : IntPtr.Zero;
  25. }
  26. }
  27. private static unsafe int FindStringOrdinal(
  28. uint dwFindStringOrdinalFlags,
  29. string stringSource,
  30. int offset,
  31. int cchSource,
  32. string value,
  33. int cchValue,
  34. bool bIgnoreCase)
  35. {
  36. Debug.Assert(!GlobalizationMode.Invariant);
  37. Debug.Assert(stringSource != null);
  38. Debug.Assert(value != null);
  39. fixed (char* pSource = stringSource)
  40. fixed (char* pValue = value)
  41. {
  42. int ret = Interop.Kernel32.FindStringOrdinal(
  43. dwFindStringOrdinalFlags,
  44. pSource + offset,
  45. cchSource,
  46. pValue,
  47. cchValue,
  48. bIgnoreCase ? 1 : 0);
  49. return ret < 0 ? ret : ret + offset;
  50. }
  51. }
  52. private static unsafe int FindStringOrdinal(
  53. uint dwFindStringOrdinalFlags,
  54. ReadOnlySpan<char> source,
  55. ReadOnlySpan<char> value,
  56. bool bIgnoreCase)
  57. {
  58. Debug.Assert(!GlobalizationMode.Invariant);
  59. Debug.Assert(!source.IsEmpty);
  60. Debug.Assert(!value.IsEmpty);
  61. fixed (char* pSource = &MemoryMarshal.GetReference(source))
  62. fixed (char* pValue = &MemoryMarshal.GetReference(value))
  63. {
  64. int ret = Interop.Kernel32.FindStringOrdinal(
  65. dwFindStringOrdinalFlags,
  66. pSource,
  67. source.Length,
  68. pValue,
  69. value.Length,
  70. bIgnoreCase ? 1 : 0);
  71. return ret;
  72. }
  73. }
  74. internal static int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
  75. {
  76. Debug.Assert(!GlobalizationMode.Invariant);
  77. Debug.Assert(source != null);
  78. Debug.Assert(value != null);
  79. return FindStringOrdinal(FIND_FROMSTART, source, startIndex, count, value, value.Length, ignoreCase);
  80. }
  81. internal static int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase, bool fromBeginning)
  82. {
  83. Debug.Assert(!GlobalizationMode.Invariant);
  84. Debug.Assert(source.Length != 0);
  85. Debug.Assert(value.Length != 0);
  86. uint positionFlag = fromBeginning ? (uint)FIND_FROMSTART : FIND_FROMEND;
  87. return FindStringOrdinal(positionFlag, source, value, ignoreCase);
  88. }
  89. internal static int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
  90. {
  91. Debug.Assert(!GlobalizationMode.Invariant);
  92. Debug.Assert(source != null);
  93. Debug.Assert(value != null);
  94. return FindStringOrdinal(FIND_FROMEND, source, startIndex - count + 1, count, value, value.Length, ignoreCase);
  95. }
  96. private unsafe int GetHashCodeOfStringCore(ReadOnlySpan<char> source, CompareOptions options)
  97. {
  98. Debug.Assert(!GlobalizationMode.Invariant);
  99. Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
  100. if (source.Length == 0)
  101. {
  102. return 0;
  103. }
  104. uint flags = LCMAP_SORTKEY | (uint)GetNativeCompareFlags(options);
  105. fixed (char* pSource = source)
  106. {
  107. int sortKeyLength = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
  108. flags,
  109. pSource, source.Length /* in chars */,
  110. null, 0,
  111. null, null, _sortHandle);
  112. if (sortKeyLength == 0)
  113. {
  114. throw new ArgumentException(SR.Arg_ExternalException);
  115. }
  116. // Note in calls to LCMapStringEx below, the input buffer is specified in wchars (and wchar count),
  117. // but the output buffer is specified in bytes (and byte count). This is because when generating
  118. // sort keys, LCMapStringEx treats the output buffer as containing opaque binary data.
  119. // See https://docs.microsoft.com/en-us/windows/desktop/api/winnls/nf-winnls-lcmapstringex.
  120. byte[] borrowedArr = null;
  121. Span<byte> span = sortKeyLength <= 512 ?
  122. stackalloc byte[512] :
  123. (borrowedArr = ArrayPool<byte>.Shared.Rent(sortKeyLength));
  124. fixed (byte* pSortKey = &MemoryMarshal.GetReference(span))
  125. {
  126. if (Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
  127. flags,
  128. pSource, source.Length /* in chars */,
  129. pSortKey, sortKeyLength,
  130. null, null, _sortHandle) != sortKeyLength)
  131. {
  132. throw new ArgumentException(SR.Arg_ExternalException);
  133. }
  134. }
  135. int hash = Marvin.ComputeHash32(span.Slice(0, sortKeyLength), Marvin.DefaultSeed);
  136. // Return the borrowed array if necessary.
  137. if (borrowedArr != null)
  138. {
  139. ArrayPool<byte>.Shared.Return(borrowedArr);
  140. }
  141. return hash;
  142. }
  143. }
  144. private static unsafe int CompareStringOrdinalIgnoreCase(ref char string1, int count1, ref char string2, int count2)
  145. {
  146. Debug.Assert(!GlobalizationMode.Invariant);
  147. fixed (char* char1 = &string1)
  148. fixed (char* char2 = &string2)
  149. {
  150. // Use the OS to compare and then convert the result to expected value by subtracting 2
  151. return Interop.Kernel32.CompareStringOrdinal(char1, count1, char2, count2, true) - 2;
  152. }
  153. }
  154. // TODO https://github.com/dotnet/coreclr/issues/13827:
  155. // This method shouldn't be necessary, as we should be able to just use the overload
  156. // that takes two spans. But due to this issue, that's adding significant overhead.
  157. private unsafe int CompareString(ReadOnlySpan<char> string1, string string2, CompareOptions options)
  158. {
  159. Debug.Assert(string2 != null);
  160. Debug.Assert(!GlobalizationMode.Invariant);
  161. Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
  162. string localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
  163. fixed (char* pLocaleName = localeName)
  164. fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
  165. fixed (char* pString2 = &string2.GetRawStringData())
  166. {
  167. Debug.Assert(pString1 != null);
  168. int result = Interop.Kernel32.CompareStringEx(
  169. pLocaleName,
  170. (uint)GetNativeCompareFlags(options),
  171. pString1,
  172. string1.Length,
  173. pString2,
  174. string2.Length,
  175. null,
  176. null,
  177. _sortHandle);
  178. if (result == 0)
  179. {
  180. throw new ArgumentException(SR.Arg_ExternalException);
  181. }
  182. // Map CompareStringEx return value to -1, 0, 1.
  183. return result - 2;
  184. }
  185. }
  186. private unsafe int CompareString(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
  187. {
  188. Debug.Assert(!GlobalizationMode.Invariant);
  189. Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
  190. string localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
  191. fixed (char* pLocaleName = localeName)
  192. fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
  193. fixed (char* pString2 = &MemoryMarshal.GetReference(string2))
  194. {
  195. Debug.Assert(pString1 != null);
  196. Debug.Assert(pString2 != null);
  197. int result = Interop.Kernel32.CompareStringEx(
  198. pLocaleName,
  199. (uint)GetNativeCompareFlags(options),
  200. pString1,
  201. string1.Length,
  202. pString2,
  203. string2.Length,
  204. null,
  205. null,
  206. _sortHandle);
  207. if (result == 0)
  208. {
  209. throw new ArgumentException(SR.Arg_ExternalException);
  210. }
  211. // Map CompareStringEx return value to -1, 0, 1.
  212. return result - 2;
  213. }
  214. }
  215. private unsafe int FindString(
  216. uint dwFindNLSStringFlags,
  217. ReadOnlySpan<char> lpStringSource,
  218. ReadOnlySpan<char> lpStringValue,
  219. int* pcchFound)
  220. {
  221. Debug.Assert(!GlobalizationMode.Invariant);
  222. Debug.Assert(!lpStringSource.IsEmpty);
  223. Debug.Assert(!lpStringValue.IsEmpty);
  224. string localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
  225. fixed (char* pLocaleName = localeName)
  226. fixed (char* pSource = &MemoryMarshal.GetReference(lpStringSource))
  227. fixed (char* pValue = &MemoryMarshal.GetReference(lpStringValue))
  228. {
  229. return Interop.Kernel32.FindNLSStringEx(
  230. pLocaleName,
  231. dwFindNLSStringFlags,
  232. pSource,
  233. lpStringSource.Length,
  234. pValue,
  235. lpStringValue.Length,
  236. pcchFound,
  237. null,
  238. null,
  239. _sortHandle);
  240. }
  241. }
  242. private unsafe int FindString(
  243. uint dwFindNLSStringFlags,
  244. string lpStringSource,
  245. int startSource,
  246. int cchSource,
  247. string lpStringValue,
  248. int startValue,
  249. int cchValue,
  250. int* pcchFound)
  251. {
  252. Debug.Assert(!GlobalizationMode.Invariant);
  253. Debug.Assert(lpStringSource != null);
  254. Debug.Assert(lpStringValue != null);
  255. string localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
  256. fixed (char* pLocaleName = localeName)
  257. fixed (char* pSource = lpStringSource)
  258. fixed (char* pValue = lpStringValue)
  259. {
  260. char* pS = pSource + startSource;
  261. char* pV = pValue + startValue;
  262. return Interop.Kernel32.FindNLSStringEx(
  263. pLocaleName,
  264. dwFindNLSStringFlags,
  265. pS,
  266. cchSource,
  267. pV,
  268. cchValue,
  269. pcchFound,
  270. null,
  271. null,
  272. _sortHandle);
  273. }
  274. }
  275. internal unsafe int IndexOfCore(string source, string target, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
  276. {
  277. Debug.Assert(!GlobalizationMode.Invariant);
  278. Debug.Assert(source != null);
  279. Debug.Assert(target != null);
  280. Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
  281. Debug.Assert((options & CompareOptions.Ordinal) == 0);
  282. int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, startIndex, count,
  283. target, 0, target.Length, matchLengthPtr);
  284. if (retValue >= 0)
  285. {
  286. return retValue + startIndex;
  287. }
  288. return -1;
  289. }
  290. internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr, bool fromBeginning)
  291. {
  292. Debug.Assert(!GlobalizationMode.Invariant);
  293. Debug.Assert(source.Length != 0);
  294. Debug.Assert(target.Length != 0);
  295. Debug.Assert((options == CompareOptions.None || options == CompareOptions.IgnoreCase));
  296. uint positionFlag = fromBeginning ? (uint)FIND_FROMSTART : FIND_FROMEND;
  297. return FindString(positionFlag | (uint)GetNativeCompareFlags(options), source, target, matchLengthPtr);
  298. }
  299. private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
  300. {
  301. Debug.Assert(!GlobalizationMode.Invariant);
  302. Debug.Assert(!string.IsNullOrEmpty(source));
  303. Debug.Assert(target != null);
  304. Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
  305. if (target.Length == 0)
  306. return startIndex;
  307. if ((options & CompareOptions.Ordinal) != 0)
  308. {
  309. return FastLastIndexOfString(source, target, startIndex, count, target.Length);
  310. }
  311. else
  312. {
  313. int retValue = FindString(FIND_FROMEND | (uint)GetNativeCompareFlags(options), source, startIndex - count + 1,
  314. count, target, 0, target.Length, null);
  315. if (retValue >= 0)
  316. {
  317. return retValue + startIndex - (count - 1);
  318. }
  319. }
  320. return -1;
  321. }
  322. private unsafe bool StartsWith(string source, string prefix, CompareOptions options)
  323. {
  324. Debug.Assert(!GlobalizationMode.Invariant);
  325. Debug.Assert(!string.IsNullOrEmpty(source));
  326. Debug.Assert(!string.IsNullOrEmpty(prefix));
  327. Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
  328. return FindString(FIND_STARTSWITH | (uint)GetNativeCompareFlags(options), source, 0, source.Length,
  329. prefix, 0, prefix.Length, null) >= 0;
  330. }
  331. private unsafe bool StartsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
  332. {
  333. Debug.Assert(!GlobalizationMode.Invariant);
  334. Debug.Assert(!source.IsEmpty);
  335. Debug.Assert(!prefix.IsEmpty);
  336. Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
  337. return FindString(FIND_STARTSWITH | (uint)GetNativeCompareFlags(options), source, prefix, null) >= 0;
  338. }
  339. private unsafe bool EndsWith(string source, string suffix, CompareOptions options)
  340. {
  341. Debug.Assert(!GlobalizationMode.Invariant);
  342. Debug.Assert(!string.IsNullOrEmpty(source));
  343. Debug.Assert(!string.IsNullOrEmpty(suffix));
  344. Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
  345. return FindString(FIND_ENDSWITH | (uint)GetNativeCompareFlags(options), source, 0, source.Length,
  346. suffix, 0, suffix.Length, null) >= 0;
  347. }
  348. private unsafe bool EndsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
  349. {
  350. Debug.Assert(!GlobalizationMode.Invariant);
  351. Debug.Assert(!source.IsEmpty);
  352. Debug.Assert(!suffix.IsEmpty);
  353. Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
  354. return FindString(FIND_ENDSWITH | (uint)GetNativeCompareFlags(options), source, suffix, null) >= 0;
  355. }
  356. // PAL ends here
  357. [NonSerialized]
  358. private IntPtr _sortHandle;
  359. private const uint LCMAP_SORTKEY = 0x00000400;
  360. private const uint LCMAP_HASH = 0x00040000;
  361. private const int FIND_STARTSWITH = 0x00100000;
  362. private const int FIND_ENDSWITH = 0x00200000;
  363. private const int FIND_FROMSTART = 0x00400000;
  364. private const int FIND_FROMEND = 0x00800000;
  365. // TODO: Instead of this method could we just have upstack code call LastIndexOfOrdinal with ignoreCase = false?
  366. private static unsafe int FastLastIndexOfString(string source, string target, int startIndex, int sourceCount, int targetCount)
  367. {
  368. int retValue = -1;
  369. int sourceStartIndex = startIndex - sourceCount + 1;
  370. fixed (char* pSource = source, spTarget = target)
  371. {
  372. char* spSubSource = pSource + sourceStartIndex;
  373. int endPattern = sourceCount - targetCount;
  374. if (endPattern < 0)
  375. return -1;
  376. Debug.Assert(target.Length >= 1);
  377. char patternChar0 = spTarget[0];
  378. for (int ctrSrc = endPattern; ctrSrc >= 0; ctrSrc--)
  379. {
  380. if (spSubSource[ctrSrc] != patternChar0)
  381. continue;
  382. int ctrPat;
  383. for (ctrPat = 1; ctrPat < targetCount; ctrPat++)
  384. {
  385. if (spSubSource[ctrSrc + ctrPat] != spTarget[ctrPat])
  386. break;
  387. }
  388. if (ctrPat == targetCount)
  389. {
  390. retValue = ctrSrc;
  391. break;
  392. }
  393. }
  394. if (retValue >= 0)
  395. {
  396. retValue += startIndex - sourceCount + 1;
  397. }
  398. }
  399. return retValue;
  400. }
  401. private unsafe SortKey CreateSortKey(string source, CompareOptions options)
  402. {
  403. Debug.Assert(!GlobalizationMode.Invariant);
  404. if (source == null) { throw new ArgumentNullException(nameof(source)); }
  405. if ((options & ValidSortkeyCtorMaskOffFlags) != 0)
  406. {
  407. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  408. }
  409. byte [] keyData = null;
  410. if (source.Length == 0)
  411. {
  412. keyData = Array.Empty<byte>();
  413. }
  414. else
  415. {
  416. uint flags = LCMAP_SORTKEY | (uint)GetNativeCompareFlags(options);
  417. fixed (char *pSource = source)
  418. {
  419. int sortKeyLength = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
  420. flags,
  421. pSource, source.Length,
  422. null, 0,
  423. null, null, _sortHandle);
  424. if (sortKeyLength == 0)
  425. {
  426. throw new ArgumentException(SR.Arg_ExternalException);
  427. }
  428. keyData = new byte[sortKeyLength];
  429. fixed (byte* pBytes = keyData)
  430. {
  431. if (Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
  432. flags,
  433. pSource, source.Length,
  434. pBytes, keyData.Length,
  435. null, null, _sortHandle) != sortKeyLength)
  436. {
  437. throw new ArgumentException(SR.Arg_ExternalException);
  438. }
  439. }
  440. }
  441. }
  442. return new SortKey(Name, source, options, keyData);
  443. }
  444. private static unsafe bool IsSortable(char* text, int length)
  445. {
  446. Debug.Assert(!GlobalizationMode.Invariant);
  447. Debug.Assert(text != null);
  448. return Interop.Kernel32.IsNLSDefinedString(Interop.Kernel32.COMPARE_STRING, 0, IntPtr.Zero, text, length);
  449. }
  450. private const int COMPARE_OPTIONS_ORDINAL = 0x40000000; // Ordinal
  451. private const int NORM_IGNORECASE = 0x00000001; // Ignores case. (use LINGUISTIC_IGNORECASE instead)
  452. private const int NORM_IGNOREKANATYPE = 0x00010000; // Does not differentiate between Hiragana and Katakana characters. Corresponding Hiragana and Katakana will compare as equal.
  453. private const int NORM_IGNORENONSPACE = 0x00000002; // Ignores nonspacing. This flag also removes Japanese accent characters. (use LINGUISTIC_IGNOREDIACRITIC instead)
  454. private const int NORM_IGNORESYMBOLS = 0x00000004; // Ignores symbols.
  455. private const int NORM_IGNOREWIDTH = 0x00020000; // Does not differentiate between a single-byte character and the same character as a double-byte character.
  456. private const int NORM_LINGUISTIC_CASING = 0x08000000; // use linguistic rules for casing
  457. private const int SORT_STRINGSORT = 0x00001000; // Treats punctuation the same as symbols.
  458. private static int GetNativeCompareFlags(CompareOptions options)
  459. {
  460. // Use "linguistic casing" by default (load the culture's casing exception tables)
  461. int nativeCompareFlags = NORM_LINGUISTIC_CASING;
  462. if ((options & CompareOptions.IgnoreCase) != 0) { nativeCompareFlags |= NORM_IGNORECASE; }
  463. if ((options & CompareOptions.IgnoreKanaType) != 0) { nativeCompareFlags |= NORM_IGNOREKANATYPE; }
  464. if ((options & CompareOptions.IgnoreNonSpace) != 0) { nativeCompareFlags |= NORM_IGNORENONSPACE; }
  465. if ((options & CompareOptions.IgnoreSymbols) != 0) { nativeCompareFlags |= NORM_IGNORESYMBOLS; }
  466. if ((options & CompareOptions.IgnoreWidth) != 0) { nativeCompareFlags |= NORM_IGNOREWIDTH; }
  467. if ((options & CompareOptions.StringSort) != 0) { nativeCompareFlags |= SORT_STRINGSORT; }
  468. // TODO: Can we try for GetNativeCompareFlags to never
  469. // take Ordinal or OrdinalIgnoreCase. This value is not part of Win32, we just handle it special
  470. // in some places.
  471. // Suffix & Prefix shouldn't use this, make sure to turn off the NORM_LINGUISTIC_CASING flag
  472. if (options == CompareOptions.Ordinal) { nativeCompareFlags = COMPARE_OPTIONS_ORDINAL; }
  473. Debug.Assert(((options & ~(CompareOptions.IgnoreCase |
  474. CompareOptions.IgnoreKanaType |
  475. CompareOptions.IgnoreNonSpace |
  476. CompareOptions.IgnoreSymbols |
  477. CompareOptions.IgnoreWidth |
  478. CompareOptions.StringSort)) == 0) ||
  479. (options == CompareOptions.Ordinal), "[CompareInfo.GetNativeCompareFlags]Expected all flags to be handled");
  480. return nativeCompareFlags;
  481. }
  482. private unsafe SortVersion GetSortVersion()
  483. {
  484. Debug.Assert(!GlobalizationMode.Invariant);
  485. Interop.Kernel32.NlsVersionInfoEx nlsVersion = new Interop.Kernel32.NlsVersionInfoEx();
  486. nlsVersion.dwNLSVersionInfoSize = sizeof(Interop.Kernel32.NlsVersionInfoEx);
  487. Interop.Kernel32.GetNLSVersionEx(Interop.Kernel32.COMPARE_STRING, _sortName, &nlsVersion);
  488. return new SortVersion(
  489. nlsVersion.dwNLSVersion,
  490. nlsVersion.dwEffectiveId == 0 ? LCID : nlsVersion.dwEffectiveId,
  491. nlsVersion.guidCustomVersion);
  492. }
  493. }
  494. }