Merge pull request #1656 from janvorli/exception-handling-perf
[platform/upstream/coreclr.git] / src / utilcode / sortversioning.cpp
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
4 //
5 ////////////////////////////////////////////////////////////////////////////
6 //
7 //  File:    SortVersioning.cpp
8 //
9
10
11 //  Purpose:  Provides access of the sort versioning functionality on
12 //            downlevel (pre-Win7) machines.
13 //
14 //       
15 //            This is not used on CoreCLR, where we always go to the OS
16 //            for sorting.
17 //            
18 ////////////////////////////////////////////////////////////////////////////
19
20 #include "stdafx.h"
21 #include "sortversioning.h"
22 #include "newapis.h"
23
24 #include "mscoree.h"
25 #include "clrconfig.h"
26
27 #define SORT_VERSION_V4         0x00060101
28 #define SORT_VERSION_WHIDBEY    0x00001000
29 #define SORT_VERSION_DEFAULT    SORT_VERSION_V4
30 #define SORT_DEFAULT_DLL_NAME   MAKEDLLNAME(W("nlssorting"))
31
32 namespace SortVersioning
33 {
34 #define SORT_HASH_TBL_SIZE  128
35
36     //
37     // Forward Declarations
38     //
39     PSORTHANDLE MakeSortHashNode(
40         __in LPCWSTR    pSortName,
41         __in DWORD      dwVersion);
42
43
44     PSORTHANDLE InsertSortHashNode(
45         __in PSORTHANDLE pHashN);
46
47     static PSORTHANDLE     g_pSortHash[SORT_HASH_TBL_SIZE];    // Sort node hash table
48
49     static HMODULE g_hSortDefault = (HMODULE)-1;
50
51     __encoded_pointer static SORTGETHANDLE   g_pDefaultGetHandle;
52     __encoded_pointer static SORTCLOSEHANDLE g_pDefaultCloseHandle;
53
54     static HMODULE g_hSortCompatV2 = (HMODULE)-1;
55
56     __encoded_pointer static SORTGETHANDLE   g_pV2GetHandle;
57     __encoded_pointer static SORTCLOSEHANDLE g_pV2CloseHandle;
58
59     static HMODULE g_hSortCompatV4 = (HMODULE)-1;
60
61     __encoded_pointer static SORTGETHANDLE   g_pV4GetHandle;
62     __encoded_pointer static SORTCLOSEHANDLE g_pV4CloseHandle;
63
64
65     ////////////////////////////////////////////////////////////////////////////
66     //
67     //  NlsCompareInvariantNoCase
68     //
69     //  This routine does fast caseless comparison without needing the tables.
70     //  This helps us do the comparisons we need to load the tables :-)
71     //
72     //  Returns 0 if identical, <0 if pFirst if first string sorts first.
73     //
74     //  This is only intended to help with our locale name comparisons,
75     //  which are effectively limited to A-Z, 0-9, a-z and - where A-Z and a-z
76     //  compare as equal.
77     //
78     //  WARNING: [\]^_` will be less than A-Z because we make everything lower
79     //           case before comparing them.
80     //
81     //  When bNullEnd is TRUE, both of the strings should be null-terminator to be considered equal.
82     //  When bNullEnd is FALSE, the strings are considered equal when we reach the number of characters specifed by size
83     //  or when null terminators are reached, whichever happens first (strncmp-like behavior)
84     //
85     ////////////////////////////////////////////////////////////////////////////
86     int NlsCompareInvariantNoCase(
87         LPCWSTR pFirst,
88         LPCWSTR pSecond,
89         int     size,
90         BOOL bNullEnd)
91     {
92         int i=0;
93         WCHAR first;
94         WCHAR second;
95
96         for (;
97              size > 0 && (first = *pFirst) != 0 && (second = *pSecond) != 0;
98              size--, pFirst++, pSecond++)
99         {
100             // Make them lower case
101             if ((first >= 'A') && (first <= 'Z')) first |= 0x20;
102             if ((second >= 'A') && (second <= 'Z')) second |= 0x20;
103
104             // Get the diff
105             i = (first - second);
106
107             // Are they the same?
108             if (i == 0)
109                 continue;
110
111             // Otherwise the difference.  Remember we made A-Z into lower case, so
112             // the characters [\]^_` will sort < A-Z and also < a-z.  (Those are the
113             // characters between A-Z and a-Z in ascii)
114             return i;
115         }
116
117         // When we are here, one of these holds:
118         //    size == 0
119         //    or one of the strings has a null terminator
120         //    or both of the string reaches null terminator
121
122         if (bNullEnd || size != 0)
123         {
124             // If bNullEnd is TRUE, always check for null terminator.
125             // If bNullEnd is FALSE, we still have to check if one of the strings is terminated eariler
126             // than another (hense the size != 0 check).
127
128             // See if one string ended first
129             if (*pFirst != 0 || *pSecond != 0)
130             {
131                 // Which one?
132                 return *pFirst == 0 ? -1 : 1;
133             }
134         }
135
136         // Return our difference (0)
137         return i;
138     }
139
140 #if !defined(FEATURE_CORECLR) && !defined(CROSSGEN_COMPILE)
141     ////////////////////////////////////////////////////////////////////////////
142     //
143     //  LoadSortModuleAndInvariant()
144     //
145     //  Attempts to load the given dll. If that is successful, attempts to
146     //  load the invariant data. If that is successful, returns the module handle
147     //  and the addresses of the SortGetHandle function and SortCloseHandle function
148     //
149     //  failure is indicated by returning NULL for the module handle and the
150     //  function addresses
151     //
152     ////////////////////////////////////////////////////////////////////////////
153
154     HMODULE LoadSortModuleAndInvariant(
155         __in LPCWSTR sDllName,
156         __in DWORD dwVersion,
157         __out SORTGETHANDLE* ppGetHandle,
158         __out SORTCLOSEHANDLE* ppCloseHandle
159         )
160     {
161         *ppGetHandle = NULL;
162         *ppCloseHandle = NULL;
163         HMODULE hSort;
164
165         if(FAILED(UtilCode::LoadLibraryShim(sDllName, NULL, NULL, &hSort))) 
166         {            
167             return NULL;
168         }
169
170         SORTGETHANDLE pGetHandle = (SORTGETHANDLE)GetProcAddress(hSort, "SortGetHandle");
171         SORTCLOSEHANDLE pCloseHandle = (SORTCLOSEHANDLE)GetProcAddress(hSort, "SortCloseHandle");
172
173         // If we didn't load the procs, then remember that
174         if (pCloseHandle == NULL || pGetHandle == NULL)
175         {
176             ::FreeLibrary(hSort);
177             return NULL;
178         }
179
180         // Verify that the data file's available
181         NLSVERSIONINFO  sortVersion;
182         sortVersion.dwNLSVersionInfoSize = sizeof(NLSVERSIONINFO);
183         sortVersion.dwNLSVersion = dwVersion;
184         sortVersion.dwDefinedVersion = dwVersion;
185
186         // Invariant must be there and is kinda common, so we'll just get it
187         PSORTHANDLE pSort = pGetHandle(W(""), &sortVersion, NULL);
188         if (!pSort)
189         {
190             // Yikes, invariant failed, forget about it.
191             ::FreeLibrary(hSort);
192             return NULL;
193         }
194
195         // Since we found it, may as well remember it.
196         const SORTHANDLE * const pSortInHash = InsertSortHashNode(pSort);
197
198         // If we got a different one back then free the one we added
199         if (pSortInHash != pSort && pSortInHash)
200         {
201             // We got a different one from the hash (someone beat us to the cache)
202             // so use that and discard the new one.
203             pCloseHandle(pSort);
204         }
205
206         *ppGetHandle = pGetHandle;
207         *ppCloseHandle = pCloseHandle;
208         return hSort;
209     }
210
211     // Attempts to load a Sort DLL.  If this fails, the values of the __out parameters are unchanged.
212     __success(return)
213     BOOL LoadSortDllAndPublish(
214         __in  LPCWSTR sDllName, 
215         __in  DWORD dwVersion, 
216         __out __encoded_pointer SORTGETHANDLE* ppGetHandle,
217         __out __encoded_pointer SORTCLOSEHANDLE* ppCloseHandle,
218         __out HMODULE* phSortDll)
219     {
220         HMODULE hSortDll;
221         SORTGETHANDLE   pGetHandle;
222         SORTCLOSEHANDLE pCloseHandle;
223         
224         hSortDll = LoadSortModuleAndInvariant(sDllName, dwVersion, &pGetHandle, &pCloseHandle);        
225
226         if(hSortDll != NULL) {
227             *phSortDll = hSortDll;
228             *ppGetHandle = (SORTGETHANDLE)EncodePointer(pGetHandle);
229             *ppCloseHandle = (SORTCLOSEHANDLE)EncodePointer(pCloseHandle);
230             return TRUE;
231         }
232
233         return FALSE;
234     }
235
236     ////////////////////////////////////////////////////////////////////////////
237     //
238     //  GetSortGetHandle()
239     //
240     //  Get the SortGetHandle() function for the proper dll version.
241     //
242     ////////////////////////////////////////////////////////////////////////////
243     SORTGETHANDLE GetSortGetHandle(__in DWORD dwVersion)
244     {
245         if(dwVersion == SORT_VERSION_DEFAULT)
246         {
247             // If we haven't tried to load the module/proc before do so now
248             if (g_hSortDefault == (HMODULE)-1)
249             {
250                 LoadSortDllAndPublish(SORT_DEFAULT_DLL_NAME, SORT_VERSION_DEFAULT, &g_pDefaultGetHandle, &g_pDefaultCloseHandle, &g_hSortDefault);
251             }
252
253             // This check is necessary because the LoadSortDllAndPublish call may have failed since some platforms
254             // won't have nlssorting.dll (e.g. Windows 8 and above).
255             if (g_hSortDefault != (HMODULE)-1) 
256             {
257                 return (SORTGETHANDLE)DecodePointer(g_pDefaultGetHandle);
258             }
259         }
260
261         HMODULE* pHSortModule; 
262         SORTGETHANDLE*   ppGetHandle;
263         SORTCLOSEHANDLE* ppCloseHandle;
264
265         if(dwVersion == SORT_VERSION_V4)
266         {
267             ppGetHandle = &g_pV4GetHandle;
268             ppCloseHandle = &g_pV4CloseHandle;
269             pHSortModule = &g_hSortCompatV4;
270         } 
271         else if(dwVersion == SORT_VERSION_WHIDBEY) 
272         {
273             ppGetHandle = &g_pV2GetHandle;
274             ppCloseHandle = &g_pV2CloseHandle;
275             pHSortModule = &g_hSortCompatV2;
276         }
277         else
278         {
279             // Unsupported sorting version.
280             return NULL;
281         }
282
283         if(*pHSortModule == (HMODULE) -1)
284         {
285             // get module name - the module name should be "Sort"+dwVersion.ToString("x8")+".dll"
286             WCHAR moduleName[] = W("Sort00000000.dll");
287             // replace the "00000000" with the hexadecimal of dwVersion
288             LPCWSTR hex = W("0123456789abcdef");
289             WCHAR* p = &moduleName[4+8]; // position at end of number part of dll name
290
291             unsigned int value = dwVersion;
292
293             while (value != 0 && p != moduleName) {
294                 int digit = value & 0xF;
295                 *--p = hex[digit];
296                 value >>= 4;
297             }
298
299             if(!LoadSortDllAndPublish(&moduleName[0], dwVersion, ppGetHandle, ppCloseHandle, pHSortModule)) 
300             {
301                 // We failed to load a versioned sort dll, try to fall back to the current version.
302                 // If we haven't tried to load the module/proc before do so now
303                 if (g_hSortDefault == (HMODULE)-1)
304                 {
305                     LoadSortDllAndPublish(SORT_DEFAULT_DLL_NAME, SORT_VERSION_DEFAULT, &g_pDefaultGetHandle, &g_pDefaultCloseHandle, &g_hSortDefault);
306                 }
307
308                 *pHSortModule = g_hSortDefault;
309                 *ppCloseHandle = g_pDefaultCloseHandle;
310                 *ppGetHandle = g_pDefaultGetHandle;
311             }           
312         }
313
314         // At this point, we've either loaded a sorting dll or we've exausted all options.
315         if(*pHSortModule == (HMODULE) -1)
316         {
317             // Couldn't find anything, give up.
318             return NULL;
319         }
320         else
321         {
322             return (SORTGETHANDLE)DecodePointer(*ppGetHandle);
323         }
324
325         // Unknown version requested
326         return NULL;
327     }
328
329     void DoSortCloseHandle(__in DWORD dwVersion, __in PSORTHANDLE pSort)
330     {
331         if (dwVersion == SORT_VERSION_DEFAULT)
332         {
333             SORTCLOSEHANDLE pDefaultCloseHandle = (SORTCLOSEHANDLE)DecodePointer(g_pDefaultCloseHandle);
334             if(pDefaultCloseHandle != NULL)
335             {
336                 (pDefaultCloseHandle)(pSort);
337                 return;
338             }
339         }
340
341         if (dwVersion == SORT_VERSION_V4)
342         {
343             SORTCLOSEHANDLE pV4CloseHandle = (SORTCLOSEHANDLE)DecodePointer(g_pV4CloseHandle);
344             if(pV4CloseHandle != NULL)
345             {
346                 (pV4CloseHandle)(pSort);
347                 return;
348             }
349         }
350
351
352         if (dwVersion == SORT_VERSION_WHIDBEY) 
353         {
354             SORTCLOSEHANDLE pV2CloseHandle = (SORTCLOSEHANDLE)DecodePointer(g_pV2CloseHandle);
355             if(pV2CloseHandle != NULL)
356             {
357                 (pV2CloseHandle)(pSort);
358                 return;
359             }
360         }
361     }
362
363 #else // !defined(FEATURE_CORECLR) && !defined(CROSSGEN_COMPILE)
364
365     SORTGETHANDLE GetSortGetHandle(__in DWORD dwVersion)
366     {
367         return NULL;
368     }
369
370     void DoSortCloseHandle(__in DWORD dwVersion, __in PSORTHANDLE pSort)
371     {
372     }
373
374 #endif // !defined(FEATURE_CORECLR) && !defined(CROSSGEN_COMPILE)
375
376     ////////////////////////////////////////////////////////////////////////////
377     //
378     //  GetSortHashValue
379     //
380     //  Returns the hash value for given sort name & version.
381     //
382     //  WARNING: This must be case insensitive.  Currently we're expecting only
383     //           a-z, A-Z, 0-9 & -.
384     //
385     ////////////////////////////////////////////////////////////////////////////
386     __inline __range(0, SORT_HASH_TBL_SIZE-1) int GetSortHashValue(
387         __in LPCWSTR    pSortName,
388         __in DWORD      dwVersion)
389     {
390         int iHash = 12; // Seed hash value
391         int iMax;       // Number of characters to count (prevent problems with too-bad strings)
392
393         // Hash the string
394         if (pSortName)
395         {
396             for (iMax = 10; *pSortName != 0 && iMax != 0; pSortName++, iMax--)
397             {
398                 iHash <<= 1;
399                 iHash ^= ((*pSortName) & 0xdf);     // 0x20 will make cases be the same (and other wierd stuff too, but we don't care about that)
400             }
401         }
402
403         // Add the version hash
404         // (the middle 2 bytes are most interesting)
405         iHash ^= dwVersion >> 8;
406
407         // Mix up our bits and hash it with 128
408         _ASSERT(SORT_HASH_TBL_SIZE == 128);
409         return (iHash + (iHash >> 8)) & 0x7f;
410     }
411
412
413     ////////////////////////////////////////////////////////////////////////////
414     //
415     //  InsertSortHashNode
416     //
417     //  Inserts a sort hash node into the global sort hash tables.  It assumes
418     //  that all unused hash values in the table are pointing to NULL.  If
419     //  there is a collision, the new node will be added LAST in the list.
420     //  (Presuming that the most often used are also the first used)
421     //
422     //  We do an interlocked exchange and free the pointer if we can't add it.
423     //
424     //  Warning: We stick stuff in this list, but we never remove it, so it
425     //           get kind of big.  Removing entries would be difficult however
426     //           because it would require some sort of synchronization with the
427     //           reader functions (like GetLocaleInfo), or maybe an in-use flag
428     //           or spin count.
429     //
430     ////////////////////////////////////////////////////////////////////////////
431     PSORTHANDLE InsertSortHashNode(PSORTHANDLE pHashN)
432     {
433         __range(0, SORT_HASH_TBL_SIZE-1) UINT         index;
434         PSORTHANDLE  pSearch;
435         PSORTHANDLE* pNextToUpdate;
436
437         //
438         // Insert the hash node into the list (by name/version)
439         //
440 #ifdef _PREFAST_
441 #pragma warning(push)
442 #pragma warning(disable: 26037) // Prefast warning - Possible precondition violation due to failure to null terminate string -  GetSortHashValue only uses first 10 characters and sortName is null terminated
443 #endif // _PREFAST_
444         index = GetSortHashValue(pHashN->sortName, pHashN->dwNLSVersion);
445 #ifdef _PREFAST_
446 #pragma warning(pop)
447 #endif
448
449         // Get hash node
450         pSearch = g_pSortHash[index];
451
452         // Remember last pointer in case we need to add it
453         pNextToUpdate = &g_pSortHash[index];
454
455         // We'll be the last node when added
456         pHashN->pNext = NULL;
457
458         while(TRUE)
459         {
460             while (pSearch != NULL)
461             {
462                 // See if we already found a node.
463                 if ((pSearch->dwNLSVersion == pHashN->dwNLSVersion) &&
464                     NlsCompareInvariantNoCase( pSearch->sortName, pHashN->sortName,
465                                                LOCALE_NAME_MAX_LENGTH, TRUE) == 0)
466                 {
467                     // Its the same, which is unexpected, return the old one
468                     return pSearch;
469                 }
470
471                 pNextToUpdate = &pSearch->pNext;
472                 pSearch = pSearch->pNext;
473             }
474
475             // At end, try to add our node
476             pSearch = InterlockedCompareExchangeT(pNextToUpdate, pHashN, NULL);
477
478             // If pNextToUpdate isn't NULL then another process snuck in and updated the list
479             // while we were getting ready.
480             if (pSearch == NULL)
481             {
482                 // It was added, stop
483                 break;
484             }
485
486             // It wasn't added, pSearch now points to a new node that snuck in, so
487             // continue and try that one.  This should be really rare, even in a busy
488             // loop, so we don't try a real lock.  Either
489             // a) the snuck in node is the same as pHashN, and we'll return pSearch
490             //    in the first loop, or
491             // b) the snuck in node is new, in which case we'll try to readd.  Very worst
492             //    case we'd collide while someone added ALL of the other locales with our
493             //    hash, but eventually we'd hit case a.  (And there's only a couple hundred
494             //    tries, so this can't lock for long.)
495         }
496
497         // Return the same one we added
498         return pHashN;
499     }
500
501
502     ////////////////////////////////////////////////////////////////////////////
503     //
504     //  FindSortHashNode
505     //
506     //  Searches for the sort hash node for the given sort name & version.
507     //  The result is returned.  If none are found NULL is returned.
508     //
509     //  NOTE: Call GetSortNode() which calls this.
510     //
511     //  Defined as inline.
512     //
513     ////////////////////////////////////////////////////////////////////////////
514
515     __inline PSORTHANDLE FindSortHashNode(
516         __in LPCWSTR    pSortName,
517         __in DWORD      dwVersion)
518     {
519         PSORTHANDLE pHashN;
520         __range(0,SORT_HASH_TBL_SIZE-1) int         index;
521
522         // Get Index
523         index = GetSortHashValue(pSortName, dwVersion);
524
525         // Get hash node
526         pHashN = g_pSortHash[index];
527
528         // Look through the list to see if one matches name and user info
529         // We're sneaky here because we know our length of our hash name string is stored
530         // just before that string.
531         while ((pHashN != NULL) &&
532                ((dwVersion != pHashN->dwNLSVersion) ||
533                 (NlsCompareInvariantNoCase(pSortName, pHashN->sortName, LOCALE_NAME_MAX_LENGTH, TRUE) != 0)))
534         {
535             pHashN = pHashN->pNext;
536         }
537
538         return pHashN;
539     }
540
541
542     ////////////////////////////////////////////////////////////////////////////
543     //
544     //  MakeSortHashNode
545     //
546     //  Builds a sort hash node and sticks it in the hash table.
547     //
548     //  NOTE: Call GetSortNode() which calls this.
549     //
550     //  Defined as inline.
551     //
552     ////////////////////////////////////////////////////////////////////////////
553     PSORTHANDLE MakeSortHashNode(
554         __in LPCWSTR    pSortName,
555         __in DWORD      dwVersion)
556     {
557         NLSVERSIONINFO  sortVersion;
558
559         PSORTHANDLE     pSort = NULL;
560         PSORTHANDLE     pSortInHash;
561
562         // Valid locale, now we need to find out where to point this version at
563         SORTGETHANDLE pGetHandle = GetSortGetHandle(dwVersion);
564         if (pGetHandle == NULL) return NULL;
565
566         sortVersion.dwNLSVersionInfoSize = sizeof(NLSVERSIONINFO);
567         sortVersion.dwNLSVersion = dwVersion;
568         sortVersion.dwDefinedVersion = dwVersion;
569
570         pSort = pGetHandle(pSortName, &sortVersion, NULL);
571
572         // If still missing, fail
573         if (pSort == NULL)
574         {
575             // Invalid sort, fail
576             return NULL;
577         }
578
579         // Now we need to add it
580         pSortInHash = InsertSortHashNode(pSort);
581
582         // If we got a different one back then free the one we added
583         if (pSortInHash != pSort && pSortInHash)
584         {
585             // We got a different one from the hash (someone beat us to the cache)
586             // so use that and discard the new one.
587             DoSortCloseHandle(dwVersion, pSort);
588         }
589
590         return pSortInHash;
591     }
592
593
594     ////////////////////////////////////////////////////////////////////////////
595     //
596     //  GetSortNode
597     //
598     //  Get a sort hash node for the specified sort name & version
599     //
600     ////////////////////////////////////////////////////////////////////////////
601     PSORTHANDLE GetSortNode(
602         __in LPCWSTR    pSortName,
603         __in DWORD      dwVersion)
604     {
605         PSORTHANDLE pSortHashN = NULL;
606
607         // WARNING: We don't bother doing the null/default/system checks
608
609         // Didn't have an obvious one, look in the hash table
610         pSortHashN = FindSortHashNode(pSortName, dwVersion);
611
612         //
613         //  If the hash node does not exist, we may need to get make one
614         //
615         if (pSortHashN == NULL)
616         {
617             //
618             //  Hash node does NOT exist, try to make it
619
620        //
621             pSortHashN = MakeSortHashNode(pSortName, dwVersion);
622         }
623
624         //
625         //  If the hash node still does not exist, we may need to fallback to default
626         //  version
627         //
628         if (pSortHashN == NULL && dwVersion != SORT_VERSION_DEFAULT)
629         {
630             return GetSortNode(pSortName, SORT_VERSION_DEFAULT);
631         }
632
633         //
634         //  Return pointer to hash node
635         //  (null if we still don't have one)
636         //
637         return pSortHashN;
638     }
639
640     ////////////////////////////////////////////////////////////////////////////
641     //
642     //  SortNLSVersion
643     //  Check for the DWORD "CompatSortNLSVersion" CLR config option.
644     //
645     // .Net 4.0 introduces sorting changes that can affect the behavior of any of the methods
646     // in CompareInfo. To mitigate against compatibility problems Applications can enable the
647     // legacy CompareInfo behavior by using the 'SortNLSVersion' configuration option
648     //
649     // There are three ways to use the configuration option:
650     //
651     // 1) Config file (MyApp.exe.config)
652     //        <?xml version ="1.0"?>
653     //        <configuration>
654     //         <runtime>
655     //          <CompatSortNLSVersion enabled="4096"/><!--0x00001000 -->
656     //         </runtime>
657     //        </configuration>
658     // 2) Environment variable
659     //        set COMPLUS_CompatSortNLSVersion=4096
660     // 3) RegistryKey
661     //        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework]
662     //        "CompatSortNLSVersion"=dword:00001000
663     //
664     ////////////////////////////////////////////////////////////////////////////
665     DWORD SortNLSVersion()
666     {
667 #ifdef FEATURE_CORECLR
668         return SORT_VERSION_DEFAULT;
669 #else
670         static bool sortNLSVersionConfigChecked = false;
671         static DWORD sortNLSVersion = SORT_VERSION_DEFAULT;
672
673         if(!sortNLSVersionConfigChecked)
674         {
675             BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(return false);
676             sortNLSVersion = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CompatSortNLSVersion);
677             if(sortNLSVersion == 0)
678             {
679                 sortNLSVersion = SORT_VERSION_DEFAULT;
680             }
681             END_SO_INTOLERANT_CODE;
682
683             sortNLSVersionConfigChecked = true;
684         }
685         return sortNLSVersion;
686 #endif // !FEATURE_CORECLR
687     }
688
689     ////////////////////////////////////////////////////////////////////////////
690     //
691     //  VersionValue
692     //
693     //  Get the version from a version blob, resolving to the default version
694     //  if NULL
695     //
696     ////////////////////////////////////////////////////////////////////////////
697     __inline DWORD VersionValue(__in_opt const NLSVERSIONINFO * const lpVersionInformation)
698     {
699
700         //
701         //  If the caller passed null or zero we use the default version
702         //
703         if ((lpVersionInformation == NULL) ||
704             ((lpVersionInformation->dwNLSVersion == 0) &&
705              (lpVersionInformation->dwDefinedVersion ==0))
706              )
707         {
708             return SortNLSVersion();
709         }
710
711         // TODO: Will need to review this
712         if(((lpVersionInformation->dwNLSVersion == 0) &&
713             (lpVersionInformation->dwDefinedVersion != 0 )))
714         {
715             return lpVersionInformation->dwDefinedVersion;
716         }
717
718         return lpVersionInformation->dwNLSVersion;
719     }
720
721     ////////////////////////////////////////////////////////////////////////////
722     //
723     //  SortGetSortKey
724     //
725     //  Supposed to call the dll for the appropriate version.  If the default
726     //  version isn't available call the ordinal behavior (for minwin)
727     //
728     //  Just get the sort hash node and call the worker function
729     //
730     ////////////////////////////////////////////////////////////////////////////
731     __success(return != 0) int WINAPI SortGetSortKey(
732         __in LPCWSTR pLocaleName,
733         __in DWORD dwFlags,
734         __in_ecount(cchSrc) LPCWSTR pSrc,
735         __in int cchSrc,
736         __out_bcount_opt(cbDest) LPBYTE pDest,
737         __in int cbDest,
738         __in_opt CONST NLSVERSIONINFO *lpVersionInformation,
739         __in_opt LPVOID lpReserved,
740         __in_opt LPARAM lParam
741     )
742     {
743         PSORTHANDLE pSort = GetSortNode(pLocaleName, VersionValue(lpVersionInformation));
744         return SortDllGetSortKey(pSort, dwFlags, pSrc, cchSrc, pDest, cbDest, lpReserved, lParam);
745     }
746
747     // SortDllGetSortKey handles any modification to flags
748     // necessary before the actual call to the dll
749     __success(return != 0) int WINAPI SortDllGetSortKey(
750         __in PSORTHANDLE pSort,
751         __in DWORD dwFlags,
752         __in_ecount(cchSrc) LPCWSTR pSrc,
753         __in int cchSrc,
754         __out_bcount_opt(cbDest) LPBYTE pDest,
755         __in int cbDest,
756         __in_opt LPVOID lpReserved,
757         __in_opt LPARAM lParam )
758     {
759         if (pSort == NULL)
760         {
761             SetLastError(ERROR_INVALID_PARAMETER);
762             return 0;
763         }
764
765         //
766         // Note that GetSortKey'll have the opposite behavior for the
767         // linguistic casing flag (eg: use flag for bad behavior, linguistic
768         // by default)
769         dwFlags ^= NORM_LINGUISTIC_CASING;
770
771         return pSort->pSortGetSortKey(pSort, dwFlags, pSrc, cchSrc, pDest, cbDest, lpReserved, lParam);
772     }
773
774     __success(return != 0) int SortDllGetHashCode(
775         __in PSORTHANDLE pSort,
776         __in DWORD dwFlags,
777         __in_ecount(cchSrc) LPCWSTR pSrc,
778         __in int cchSrc,
779         __in_opt LPVOID lpReserved,
780         __in_opt LPARAM lParam )
781     {
782         if (pSort == NULL)
783         {
784             SetLastError(ERROR_INVALID_PARAMETER);
785             return 0;
786         }
787         const int SortDllGetHashCodeApiIntroducedVersion = 2;
788         if(pSort->dwSHVersion < SortDllGetHashCodeApiIntroducedVersion)
789         {
790             SetLastError(ERROR_NOT_SUPPORTED);
791             return 0;
792         }
793
794         //
795         // Note that GetSortKey'll have the opposite behavior for the
796         // linguistic casing flag (eg: use flag for bad behavior, linguistic
797         // by default)
798         dwFlags ^= NORM_LINGUISTIC_CASING;
799
800         return pSort->pSortGetHashCode(pSort, dwFlags, pSrc, cchSrc, lpReserved, lParam);
801     }
802
803
804     ////////////////////////////////////////////////////////////////////////////
805     //
806     //  SortChangeCase
807     //
808     //  Supposed to call the dll for the appropriate version.  If the default
809     //  version isn't available call the ordinal behavior (for minwin)
810     //
811     //  NOTE: The linguistic casing flags are backwards (ie: set the flag to
812     //        get the non-linguistic behavior.)  If we expose this then we'll
813     //        need to publish the no-linguistic flag.
814     //
815     //  Just get the sort hash node and call the worker function
816     //
817     ////////////////////////////////////////////////////////////////////////////
818     __success(return != 0) int WINAPI SortChangeCase(
819         __in LPCWSTR pLocaleName,
820         __in DWORD dwFlags,
821         __in_ecount(cchSrc) LPCWSTR pSrc,
822         __in int cchSrc,
823         __out_ecount_opt(cchDest) LPWSTR pDest,
824         __in int cchDest,
825         __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
826         __in_opt LPVOID lpReserved,
827         __in_opt LPARAM lParam)
828     {
829         PSORTHANDLE pSort = GetSortNode(pLocaleName, VersionValue(lpVersionInformation));
830         return SortDllChangeCase(pSort, dwFlags, pSrc, cchSrc, pDest, cchDest, lpReserved, lParam);
831     }
832
833     // SortDllChangeCase handles any modification to flags
834     // necessary before the actual call to the dll
835     __success(return != 0) int WINAPI SortDllChangeCase(
836         __in PSORTHANDLE pSort,
837         __in DWORD dwFlags,
838         __in_ecount(cchSrc) LPCWSTR pSrc,
839         __in int cchSrc,
840         __out_ecount_opt(cchDest) LPWSTR pDest,
841         __in int cchDest,
842         __in_opt LPVOID lpReserved,
843         __in_opt LPARAM lParam)
844     {
845         if (pSort == NULL)
846         {
847             SetLastError(ERROR_INVALID_PARAMETER);
848             return 0;
849         }
850
851         // Note that Change Case'll have the opposite behavior for the
852         // linguistic casing flag (eg: use flag for bad behavior, linguistic
853         // by default)
854         dwFlags ^= LCMAP_LINGUISTIC_CASING;
855 #ifdef _PREFAST_
856 #pragma warning(push)
857 #pragma warning(disable: 26036) // prefast - Possible postcondition violation due to failure to null terminate string
858 #endif // _PREFAST_
859         return pSort->pSortChangeCase(pSort, dwFlags, pSrc, cchSrc, pDest, cchDest, lpReserved, lParam);
860 #ifdef _PREFAST_
861 #pragma warning(pop)
862 #endif
863     }
864
865     ////////////////////////////////////////////////////////////////////////////
866     //
867     //  SortCompareString
868     //
869     //  Supposed to call the dll for the appropriate version.  If the default
870     //  version isn't available call the ordinal behavior (for minwin)
871     //
872     //  Just get the sort hash node and call the worker function
873     //
874     ////////////////////////////////////////////////////////////////////////////
875     __success(return != 0) int WINAPI SortCompareString(
876         __in LPCWSTR lpLocaleName,
877         __in DWORD dwCmpFlags,
878         __in_ecount(cchCount1) LPCWSTR lpString1,
879         __in int cchCount1,
880         __in_ecount(cchCount2) LPCWSTR lpString2,
881         __in int cchCount2,
882         __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
883         __in_opt LPVOID lpReserved,
884         __in_opt LPARAM lParam)
885     {
886         PSORTHANDLE pSort = GetSortNode(lpLocaleName, VersionValue(lpVersionInformation));
887         return SortDllCompareString(pSort, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2, lpReserved, lParam);
888
889     }
890
891     // SortDllCompareString handles any modification to flags
892     // necessary before the actual call to the dll
893     __success(return != 0) int WINAPI SortDllCompareString(
894         __in PSORTHANDLE pSort,
895         __in DWORD dwCmpFlags,
896         __in_ecount(cchCount1) LPCWSTR lpString1,
897         __in int cchCount1,
898         __in_ecount(cchCount2) LPCWSTR lpString2,
899         __in int cchCount2,
900         __in_opt LPVOID lpReserved,
901         __in_opt LPARAM lParam)
902     {
903         if (pSort == NULL)
904         {
905             SetLastError(ERROR_INVALID_PARAMETER);
906             return 0;
907         }
908
909         // Note that the dll will have the opposite behavior of CompareStringEx for the
910         // linguistic casing flag (eg: use flag for bad behavior, linguistic
911         // by default) because we want new public APIs to have the "right"
912         // behavior by default
913         dwCmpFlags ^= NORM_LINGUISTIC_CASING;
914
915         return pSort->pSortCompareString(pSort, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2, lpReserved, lParam);
916     }
917
918     ////////////////////////////////////////////////////////////////////////////
919     //
920     //  SortFindString
921     //
922     //  Finds lpStringValue within lpStringSource based on the rules given
923     //  in dwFindNLSStringFlags.
924     //
925     //  Supposed to call the dll for the appropriate version.  If the default
926     //  version isn't available call the ordinal behavior (for minwin)
927     //
928     //  Just get the sort hash node and call the worker function
929     //
930     ////////////////////////////////////////////////////////////////////////////
931     __success(return != 0) int WINAPI SortFindString(
932         __in LPCWSTR lpLocaleName,
933         __in DWORD dwFindNLSStringFlags,
934         __in_ecount(cchSource) LPCWSTR lpStringSource,
935         __in int cchSource,
936         __in_ecount(cchValue) LPCWSTR lpStringValue,
937         __in int cchValue,
938         __out_opt LPINT pcchFound,
939         __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
940         __in_opt LPVOID lpReserved,
941         __in_opt LPARAM lParam)
942     {
943         PSORTHANDLE pSort = GetSortNode(lpLocaleName, VersionValue(lpVersionInformation));
944         return SortDllFindString(pSort, dwFindNLSStringFlags, lpStringSource, cchSource, lpStringValue, cchValue, pcchFound, lpReserved, lParam);
945     }
946
947     // SortDllFindString handles any modification to flags
948     // necessary before the actual call to the dll
949     __success(return != 0) int WINAPI SortDllFindString(
950         __in PSORTHANDLE pSort,
951         __in DWORD dwFindNLSStringFlags,
952         __in_ecount(cchSource) LPCWSTR lpStringSource,
953         __in int cchSource,
954         __in_ecount(cchValue) LPCWSTR lpStringValue,
955         __in int cchValue,
956         __out_opt LPINT pcchFound,
957         __in_opt LPVOID lpReserved,
958         __in_opt LPARAM lParam)
959     {
960         if (pSort == NULL)
961         {
962             SetLastError(ERROR_INVALID_PARAMETER);
963             return 0;
964         }
965
966         // Note that the dll will  have the opposite behavior of FindNlsString for the
967         // linguistic casing flag (eg: use flag for bad behavior, linguistic
968         // by default) because we want new public APIs to have the "right"
969         // behavior by default
970         dwFindNLSStringFlags ^= NORM_LINGUISTIC_CASING;
971
972         int cchFound; // we need to get the length even if the caller doesn't care about it (see below)
973         int result = pSort->pSortFindString(pSort, dwFindNLSStringFlags, lpStringSource, cchSource, lpStringValue, cchValue, &cchFound, lpReserved, lParam);
974         // When searching from end with an empty pattern (either empty string or all ignored characters)
975         // a match is found (result != -1)
976         //      Currently we get a result == 0 but we are hoping this will change
977         //      with Win7 to be the length of the source string (thus pointing past-the-end)
978         // and the length of the match (cchFound) will be 0
979         // For compatibility, we need to return the index of the last character (or 0 if the source is empty)
980         if((dwFindNLSStringFlags & FIND_FROMEND) &&
981             result != -1 &&
982             cchFound == 0 &&
983             cchSource != 0)
984         {
985             result = cchSource - 1;
986         }
987
988         // if the caller cares about the length, give it to them
989         if(pcchFound != NULL)
990         {
991             *pcchFound = cchFound;
992         }
993
994         return result;
995
996     }
997
998     ////////////////////////////////////////////////////////////////////////////
999     //
1000     //  SortIsDefinedString
1001     //
1002     //  This routine looks for code points inside a string to see if they are
1003     //  defined within the NSL context. If lpVersionInformation is NULL, the
1004     //  version is the current version. Same thing the dwDefinedVersion is equal
1005     //  to zero.
1006     //
1007     //  Supposed to call the dll for the appropriate version.  If the default
1008     //  version isn't available call the ordinal behavior (for minwin)
1009     //
1010     //  Just get the sort hash node and call the worker function
1011     //
1012     ////////////////////////////////////////////////////////////////////////////
1013     BOOL WINAPI SortIsDefinedString(
1014         __in NLS_FUNCTION     Function,
1015         __in DWORD            dwFlags,
1016         __in CONST NLSVERSIONINFOEX * lpVersionInformation,
1017         __in_ecount(cchStr) LPCWSTR          lpString,
1018         __in INT              cchStr)
1019     {
1020         // Get an invariant sort node
1021         PSORTHANDLE pSort = GetSortNode(W(""), VersionValue((CONST NLSVERSIONINFO *)lpVersionInformation));
1022         return SortDllIsDefinedString(pSort, Function, dwFlags, lpString, cchStr);
1023     }
1024
1025     // SortDllIsDefinedString handles any modification to flags
1026     // necessary before the actual call to the dll
1027     BOOL WINAPI SortDllIsDefinedString(
1028         __in PSORTHANDLE      pSort,
1029         __in NLS_FUNCTION     Function,
1030         __in DWORD            dwFlags,
1031         __in_ecount(cchStr) LPCWSTR          lpString,
1032         __in INT              cchStr)
1033     {
1034         // Fail if we couldn't find one
1035         if (pSort == NULL)
1036         {
1037             SetLastError(ERROR_INVALID_PARAMETER);
1038             return 0;
1039         }
1040
1041         return pSort->pSortIsDefinedString(pSort, Function, dwFlags, lpString, cchStr);
1042     }
1043
1044     BOOL SortGetNLSVersion(__in PSORTHANDLE pSort,
1045                            __in NLS_FUNCTION Function,
1046                            __inout NLSVERSIONINFO * lpVersionInformation )
1047     {
1048         lpVersionInformation->dwNLSVersion = pSort->dwNLSVersion;
1049         lpVersionInformation->dwDefinedVersion = pSort->dwDefinedVersion;
1050
1051         return TRUE;
1052     }
1053
1054     // Wrapper for SortGetSortKey and SortChangeCase, which are both
1055     // smushed into LCMapStringEx
1056     __success(return != 0) int
1057         LCMapStringEx (__in LPCWSTR lpLocaleName,
1058                            __in DWORD dwMapFlags,
1059                            __in_ecount(cchSrc) LPCWSTR lpSrcStr,
1060                            __in int cchSrc,
1061                            __out_ecount_opt(cchDest) LPWSTR lpDestStr, // really this should be __out_awcount_opt(dwMapFlags & LCMAP_SORTKEY, cchDest)
1062                            __in int cchDest,
1063                            __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
1064                            __in_opt LPVOID lpReserved,
1065                            __in_opt LPARAM lParam )
1066     {
1067         // Should be either sort key...
1068         if (dwMapFlags & LCMAP_SORTKEY)
1069         {
1070 #ifdef _PREFAST_
1071 #pragma warning(push)
1072 #pragma warning(disable: 26036) // Prefast - Possible precondition violation due to failure to null terminate string lpDestStr-interpreted differently depending on flag
1073 #endif // _PREFAST_
1074             return SortGetSortKey(lpLocaleName,
1075                                   dwMapFlags & ~(LCMAP_SORTKEY),    // Don't need sort key flag
1076                                   lpSrcStr,
1077                                   cchSrc,
1078                                   (LPBYTE)lpDestStr,        // Sort keys are bytes not WCHARs
1079                                   cchDest,                  // Sort keys are bytes not WCHARs
1080                                   lpVersionInformation,
1081                                   lpReserved,
1082                                   lParam);
1083 #ifdef _PREFAST_
1084 #pragma warning(pop)
1085 #endif
1086         }
1087
1088         //
1089         // Check for changing case conditions.  This may be combined with Chinese or Japanese
1090         // transliteration, but not with sort key nor ignore space/symbols
1091         //
1092         _ASSERT(dwMapFlags & (LCMAP_TITLECASE | LCMAP_UPPERCASE | LCMAP_LOWERCASE));
1093
1094         //
1095         // Call casing wrapper, which'll either call the correct version dll
1096         // or call ordinal behavior in the minwin case
1097         //
1098         return SortChangeCase(lpLocaleName,
1099                               dwMapFlags & ~(LCMAP_BYTEREV),
1100                               lpSrcStr,
1101                               cchSrc,
1102                               lpDestStr,
1103                               cchDest,
1104                               lpVersionInformation,
1105                               lpReserved,
1106                               lParam);
1107     }
1108
1109
1110     ////////////////////////////////////////////////////////////////////////////
1111     //
1112     //  IsAvailableVersion()
1113     //
1114     //  Get the SortGetHandle() function for the proper dll version.
1115     //
1116     ////////////////////////////////////////////////////////////////////////////
1117     BOOL IsAvailableVersion(__in_opt CONST NLSVERSIONINFO * pVersion)
1118     {
1119         return GetSortGetHandle(VersionValue(pVersion)) != NULL;
1120     }
1121
1122
1123     ////////////////////////////////////////////////////////////////////////////
1124     //
1125     //  GetSortHandle()
1126     //
1127     //  Get the SortHandle for the given locale and version
1128     //
1129     ////////////////////////////////////////////////////////////////////////////
1130     PSORTHANDLE GetSortHandle(__in LPCWSTR lpLocaleName, __in_opt CONST NLSVERSIONINFO * pVersion)
1131     {
1132         DWORD version = VersionValue(pVersion);
1133         if (GetSortGetHandle(version) == NULL)
1134         {
1135             return NULL;
1136         }
1137         return GetSortNode(lpLocaleName, version);
1138     }
1139
1140 }