CompareInfo.Windows.cs 26 KB

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