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.
5 #include "stdafx.h" // Standard header.
6 #include <utilcode.h> // Utility helpers.
8 #include "ndpversion.h"
10 #include "../dlls/mscorrc/resource.h"
12 #include "resourcestring.h"
13 #define NATIVE_STRING_RESOURCE_NAME mscorrc_debug
14 __attribute__((visibility("default"))) DECLARE_NATIVE_STRING_RESOURCE_TABLE(NATIVE_STRING_RESOURCE_NAME);
17 #include "stringarraylist.h"
21 #ifdef USE_FORMATMESSAGE_WRAPPER
22 // we implement the wrapper for FormatMessageW.
23 // Need access to the original
24 #undef WszFormatMessage
25 #define WszFormatMessage ::FormatMessageW
28 #define MAX_VERSION_STRING 30
30 // External prototypes.
31 extern HINSTANCE GetModuleInst();
35 //*****************************************************************************
36 // Get the MUI ID, on downlevel platforms where MUI is not supported it
37 // returns the default system ID.
39 typedef LANGID (WINAPI *PFNGETUSERDEFAULTUILANGUAGE)(void); // kernel32!GetUserDefaultUILanguage
41 int GetMUILanguageID(LocaleIDValue* pResult)
47 #ifdef MODE_PREEMPTIVE
53 _ASSERTE(sizeof(LocaleID)/sizeof(WCHAR) >=LOCALE_NAME_MAX_LENGTH);
54 return ::GetSystemDefaultLocaleName(*pResult, LOCALE_NAME_MAX_LENGTH);
57 static void BuildMUIDirectory(int langid, __out SString* pResult)
63 PRECONDITION(CheckPointer(pResult));
67 pResult->Printf(W("MUI\\%04x\\"), langid);
70 void GetMUILanguageName(__out SString* pResult)
76 PRECONDITION(CheckPointer(pResult));
81 GetMUILanguageID(&langid);
83 int lcid = ::LocaleNameToLCID(langid,0);
84 return BuildMUIDirectory(lcid, pResult);
87 void GetMUIParentLanguageName(SString* pResult)
92 BuildMUIDirectory(langid, pResult);
94 #ifndef DACCESS_COMPILE
95 HRESULT GetMUILanguageNames(__inout StringArrayList* pCultureNames)
101 PRECONDITION(CheckPointer(pCultureNames));
109 GetMUILanguageName(&result);
111 if(!result.IsEmpty())
113 pCultureNames->Append(result);
116 GetMUIParentLanguageName(&result);
118 _ASSERTE(!result.IsEmpty());
119 pCultureNames->Append(result);
120 pCultureNames->Append(SString::Empty());
126 #endif // DACCESS_COMPILE
128 #endif // !FEATURE_PAL
130 BOOL CCompRC::s_bIsMscoree = FALSE;
132 //*****************************************************************************
133 // Do the mapping from an langId to an hinstance node
134 //*****************************************************************************
135 HRESOURCEDLL CCompRC::LookupNode(LocaleID langId, BOOL &fMissing)
137 LIMITED_METHOD_CONTRACT;
139 if (m_pHash == NULL) return NULL;
143 for(i = 0; i < m_nHashSize; i ++) {
144 if (m_pHash[i].IsSet() && m_pHash[i].HasID(langId)) {
145 return m_pHash[i].GetLibraryHandle();
147 if (m_pHash[i].IsMissing() && m_pHash[i].HasID(langId))
157 //*****************************************************************************
158 // Add a new node to the map and return it.
159 //*****************************************************************************
160 const int MAP_STARTSIZE = 7;
161 const int MAP_GROWSIZE = 5;
163 HRESULT CCompRC::AddMapNode(LocaleID langId, HRESOURCEDLL hInst, BOOL fMissing)
169 INJECT_FAULT(return E_OUTOFMEMORY;);
174 if (m_pHash == NULL) {
175 m_pHash = new (nothrow)CCulturedHInstance[MAP_STARTSIZE];
177 return E_OUTOFMEMORY;
178 m_nHashSize = MAP_STARTSIZE;
181 // For now, place in first open slot
183 for(i = 0; i < m_nHashSize; i ++) {
184 if (!m_pHash[i].IsSet() && !m_pHash[i].IsMissing()) {
187 m_pHash[i].SetMissing(langId);
191 m_pHash[i].Set(langId,hInst);
198 // Out of space, regrow
199 CCulturedHInstance * pNewHash = new (nothrow)CCulturedHInstance[m_nHashSize + MAP_GROWSIZE];
202 memcpy(pNewHash, m_pHash, sizeof(CCulturedHInstance) * m_nHashSize);
207 m_pHash[m_nHashSize].SetMissing(langId);
211 m_pHash[m_nHashSize].Set(langId,hInst);
213 m_nHashSize += MAP_GROWSIZE;
216 return E_OUTOFMEMORY;
220 //*****************************************************************************
222 //*****************************************************************************
223 LPCWSTR CCompRC::m_pDefaultResource = W("mscorrc.debug.dll");
224 LPCWSTR CCompRC::m_pFallbackResource= W("mscorrc.dll");
227 LPCSTR CCompRC::m_pDefaultResourceDomain = "mscorrc.debug";
228 LPCSTR CCompRC::m_pFallbackResourceDomain = "mscorrc";
229 #endif // FEATURE_PAL
231 HRESULT CCompRC::Init(LPCWSTR pResourceFile, BOOL bUseFallback)
237 INJECT_FAULT(return E_OUTOFMEMORY;);
241 // This function is called during Watson process. We need to make sure
242 // that this function is restartable.
244 // Make sure to NEVER null out the function callbacks in the Init
245 // function. They get set for the "Default CCompRC" during EEStartup
246 // and we want to make sure we don't wipe them out.
248 m_bUseFallback = bUseFallback;
250 if (m_pResourceFile == NULL)
254 NewArrayHolder<WCHAR> pwszResourceFile(NULL);
256 DWORD lgth = (DWORD) wcslen(pResourceFile) + 1;
257 pwszResourceFile = new(nothrow) WCHAR[lgth];
258 if (pwszResourceFile)
260 wcscpy_s(pwszResourceFile, lgth, pResourceFile);
261 LPCWSTR pFile = pwszResourceFile.Extract();
262 if (InterlockedCompareExchangeT(&m_pResourceFile, pFile, NULL) != NULL)
269 InterlockedCompareExchangeT(&m_pResourceFile, m_pDefaultResource, NULL);
272 if (m_pResourceFile == NULL)
274 return E_OUTOFMEMORY;
279 if (m_pResourceFile == m_pDefaultResource)
281 m_pResourceDomain = m_pDefaultResourceDomain;
283 else if (m_pResourceFile == m_pFallbackResource)
285 m_pResourceDomain = m_pFallbackResourceDomain;
289 _ASSERTE(!"Unsupported resource file");
292 #ifndef CROSSGEN_COMPILE
293 // PAL_BindResources requires that libcoreclr.so has been loaded,
294 // and thus can'be be called by crossgen.
295 if (!PAL_BindResources(m_pResourceDomain))
297 // The function can fail only due to OOM
298 return E_OUTOFMEMORY;
302 #endif // FEATURE_PAL
306 // NOTE: there are times when the debugger's helper thread is asked to do a favor for another thread in the
307 // process. Typically, this favor involves putting up a dialog for the user. Putting up a dialog usually ends
308 // up involving the CCompRC code since (of course) the strings in the dialog are in a resource file. Thus, the
309 // debugger's helper thread will attempt to acquire this CRST. This is okay, since the helper thread only does
310 // these favors for other threads when there is no debugger attached. Thus, there are no deadlock hazards with
311 // this lock, and its safe for the helper thread to take, so this CRST is marked with CRST_DEBUGGER_THREAD.
312 CRITSEC_COOKIE csMap = ClrCreateCriticalSection(CrstCCompRC,
313 (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD | CRST_TAKEN_DURING_SHUTDOWN));
317 if (InterlockedCompareExchangeT(&m_csMap, csMap, NULL) != NULL)
319 ClrDeleteCriticalSection(csMap);
325 return E_OUTOFMEMORY;
330 void CCompRC::SetResourceCultureCallbacks(
331 FPGETTHREADUICULTURENAMES fpGetThreadUICultureNames,
332 FPGETTHREADUICULTUREID fpGetThreadUICultureId)
334 LIMITED_METHOD_CONTRACT;
336 m_fpGetThreadUICultureNames = fpGetThreadUICultureNames;
337 m_fpGetThreadUICultureId = fpGetThreadUICultureId;
340 void CCompRC::GetResourceCultureCallbacks(
341 FPGETTHREADUICULTURENAMES* fpGetThreadUICultureNames,
342 FPGETTHREADUICULTUREID* fpGetThreadUICultureId)
344 LIMITED_METHOD_CONTRACT;
346 if(fpGetThreadUICultureNames)
347 *fpGetThreadUICultureNames=m_fpGetThreadUICultureNames;
349 if(fpGetThreadUICultureId)
350 *fpGetThreadUICultureId=m_fpGetThreadUICultureId;
353 void CCompRC::Destroy()
359 #ifdef MODE_PREEMPTIVE
365 // Free all resource libraries
367 //*****************************************************************************
368 // Free the loaded library if we ever loaded it and only if we are not on
369 // Win 95 which has a known bug with DLL unloading (it randomly unloads a
370 // dll on shut down, not necessarily the one you asked for). This is done
371 // only in debug mode to make coverage runs accurate.
372 //*****************************************************************************
375 if (m_Primary.GetLibraryHandle()) {
376 ::FreeLibrary(m_Primary.GetLibraryHandle());
379 if (m_pHash != NULL) {
381 for(i = 0; i < m_nHashSize; i ++) {
382 if (m_pHash[i].GetLibraryHandle() != NULL) {
383 ::FreeLibrary(m_pHash[i].GetLibraryHandle());
390 // destroy map structure
391 if(m_pResourceFile != m_pDefaultResource)
392 delete [] m_pResourceFile;
393 m_pResourceFile = NULL;
396 ClrDeleteCriticalSection(m_csMap);
397 ZeroMemory(&(m_csMap), sizeof(CRITSEC_COOKIE));
400 if(m_pHash != NULL) {
407 //*****************************************************************************
408 // Initialization is done lazily, for backwards compatibility "mscorrc.dll"
409 // is consider the default location for all strings that use CCompRC.
410 // An instance value for CCompRC can be created to load resources from a different
412 //*****************************************************************************
413 LONG CCompRC::m_dwDefaultInitialized = 0;
414 CCompRC CCompRC::m_DefaultResourceDll;
416 CCompRC* CCompRC::GetDefaultResourceDll()
422 #ifdef MODE_PREEMPTIVE
428 if (m_dwDefaultInitialized)
429 return &m_DefaultResourceDll;
431 if(FAILED(m_DefaultResourceDll.Init(NULL, TRUE)))
435 m_dwDefaultInitialized = 1;
437 return &m_DefaultResourceDll;
440 LONG CCompRC::m_dwFallbackInitialized = 0;
441 CCompRC CCompRC::m_FallbackResourceDll;
443 CCompRC* CCompRC::GetFallbackResourceDll()
449 #ifdef MODE_PREEMPTIVE
455 if (m_dwFallbackInitialized)
456 return &m_FallbackResourceDll;
458 if(FAILED(m_FallbackResourceDll.Init(m_pFallbackResource, FALSE)))
462 m_dwFallbackInitialized = 1;
464 return &m_FallbackResourceDll;
469 //*****************************************************************************
470 //*****************************************************************************
472 HRESULT CCompRC::GetLibrary(LocaleID langId, HRESOURCEDLL* phInst)
478 #ifdef MODE_PREEMPTIVE
481 PRECONDITION(phInst != NULL);
486 HRESOURCEDLL hInst = 0;
487 #ifndef DACCESS_COMPILE
488 HRESOURCEDLL hLibInst = 0; //Holds early library instance
489 BOOL fLibAlreadyOpen = FALSE; //Determine if we can close the opened library.
492 // Try to match the primary entry, or else use the primary if we don't care.
493 if (m_Primary.IsSet())
495 if (langId == UICULTUREID_DONTCARE || m_Primary.HasID(langId))
497 hInst = m_Primary.GetLibraryHandle();
501 else if(m_Primary.IsMissing())
503 // If primary is missing then the hash will not have anything either
504 hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
506 #ifndef DACCESS_COMPILE
507 // If this is the first visit, we must set the primary entry
510 // Don't immediately return if LoadLibrary fails so we can indicate the file was missing
511 hr = LoadLibrary(&hLibInst);
512 // If it's a transient failure, don't cache the failure
513 if (FAILED(hr) && Exception::IsTransient(hr))
518 CRITSEC_Holder csh (m_csMap);
520 if (!m_Primary.IsSet() && !m_Primary.IsMissing())
525 m_Primary.Set(langId,hLibInst);
529 m_Primary.SetMissing(langId);
533 // Someone got into this critical section before us and set the primary already
534 else if (m_Primary.HasID(langId))
536 hInst = m_Primary.GetLibraryHandle();
537 fLibAlreadyOpen = TRUE;
540 // If neither case is true, someone got into this critical section before us and
541 // set the primary to other than the language we want...
544 fLibAlreadyOpen = TRUE;
551 FreeLibrary(hLibInst);
552 fLibAlreadyOpen = FALSE;
557 // If we enter here, we know that the primary is set to something other than the
558 // language we want - multiple languages use the hash table
559 if (hInst == NULL && !m_Primary.IsMissing())
561 // See if the resource exists in the hash table
563 CRITSEC_Holder csh(m_csMap);
564 BOOL fMissing = FALSE;
565 hInst = LookupNode(langId, fMissing);
566 if (fMissing == TRUE)
568 hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
573 #ifndef DACCESS_COMPILE
574 // If we didn't find it, we have to load the library and insert it into the hash
577 hr = LoadLibrary(&hLibInst);
578 // If it's a transient failure, don't cache the failure
579 if (FAILED(hr) && Exception::IsTransient(hr))
584 CRITSEC_Holder csh (m_csMap);
586 // Double check - someone may have entered this section before us
587 BOOL fMissing = FALSE;
588 hInst = LookupNode(langId, fMissing);
589 if (hInst == NULL && !fMissing)
594 hr = AddMapNode(langId, hInst);
597 HRESULT hrLoadLibrary = hr;
598 hr = AddMapNode(langId, hInst, TRUE /* fMissing */);
607 fLibAlreadyOpen = TRUE;
611 if (fLibAlreadyOpen || FAILED(hr))
613 FreeLibrary(hLibInst);
617 // We found the node, so set hr to be a success.
622 #endif // DACCESS_COMPILE
629 //*****************************************************************************
631 // We load the localized libraries and cache the handle for future use.
632 // Mutliple threads may call this, so the cache structure is thread safe.
633 //*****************************************************************************
634 HRESULT CCompRC::LoadString(ResourceCategory eCategory, UINT iResourceID, __out_ecount(iMax) LPWSTR szBuffer, int iMax, int *pcwchUsed)
637 LocaleIDValue langIdValue;
639 // Must resolve current thread's langId to a dll.
640 if(m_fpGetThreadUICultureId) {
641 int ret = (*m_fpGetThreadUICultureId)(&langIdValue);
643 // Callback can't return 0, since that indicates empty.
644 // To indicate empty, callback should return UICULTUREID_DONTCARE
653 langId = UICULTUREID_DONTCARE;
657 return LoadString(eCategory, langId, iResourceID, szBuffer, iMax, pcwchUsed);
660 HRESULT CCompRC::LoadString(ResourceCategory eCategory, LocaleID langId, UINT iResourceID, __out_ecount(iMax) LPWSTR szBuffer, int iMax, int *pcwchUsed)
666 #ifdef MODE_PREEMPTIVE
674 HRESOURCEDLL hInst = 0; //instance of cultured resource dll
677 hr = GetLibrary(langId, &hInst);
681 // Now that we have the proper dll handle, load the string
682 _ASSERTE(hInst != NULL);
684 length = ::WszLoadString(hInst, iResourceID, szBuffer, iMax);
693 if(GetLastError()==ERROR_SUCCESS)
694 hr=HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
696 hr=HRESULT_FROM_GetLastError();
700 // Failed to load string
701 if ( hr != E_OUTOFMEMORY && ShouldUseFallback())
703 CCompRC* pFallback=CCompRC::GetFallbackResourceDll();
706 //should not fall back to itself
707 _ASSERTE(pFallback != this);
709 // check existence in the fallback Dll
711 hr = pFallback->LoadString(Optional, langId, iResourceID,szBuffer, iMax, pcwchUsed);
732 StackSString ssErrorFormat;
733 if (eCategory == Error)
735 hr=ssErrorFormat.LoadResourceAndReturnHR(pFallback, CCompRC::Required, IDS_EE_LINK_FOR_ERROR_MESSAGES);
739 _ASSERTE(eCategory == Debugging);
740 hr=ssErrorFormat.LoadResourceAndReturnHR(pFallback, CCompRC::Required, IDS_EE_LINK_FOR_DEBUGGING_MESSAGES);
745 StackSString sFormattedMessage;
746 int iErrorCode = HR_FOR_URT_MSG(iResourceID);
750 DWORD_PTR args[] = {(DWORD_PTR)VER_FILEVERSION_STR_L, iResourceID, iErrorCode};
752 length = WszFormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY ,
753 (LPCWSTR)ssErrorFormat, 0, 0,
754 szBuffer,iMax,(va_list*)args);
756 if (length == 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
758 // The buffer wasn't big enough for the message. Tell the caller this.
760 // Clear the buffer, just in case.
761 if (szBuffer && iMax)
765 hr=HRESULT_FROM_GetLastError();
777 // Format mesage failed
778 hr=HRESULT_FROM_GetLastError();
782 else // if (pFallback)
784 _ASSERTE(FAILED(hr));
787 // if we got here then we couldn't get the fallback message
788 // the fallback message is required so just falling through into "Required"
792 if ( hr != E_OUTOFMEMORY)
794 // Shouldn't be any reason for this condition but the case where
795 // the resource dll is missing, code used the wrong ID or developer didn't
796 // update the resource DLL.
797 _ASSERTE(!"Missing mscorrc.dll or mscorrc.debug.dll?");
798 hr = HRESULT_FROM_GetLastError();
803 _ASSERTE(!"Invalid eCategory");
808 // Return an empty string to save the people with a bad error handling
809 if (szBuffer && iMax)
813 #else // !FEATURE_PAL
814 return LoadNativeStringResource(NATIVE_STRING_RESOURCE_TABLE(NATIVE_STRING_RESOURCE_NAME), iResourceID,
815 szBuffer, iMax, pcwchUsed);
816 #endif // !FEATURE_PAL
819 #ifndef DACCESS_COMPILE
820 HRESULT CCompRC::LoadMUILibrary(HRESOURCEDLL * pHInst)
823 _ASSERTE(pHInst != NULL);
825 LocaleIDValue langIdValue;
826 // Must resolve current thread's langId to a dll.
827 if(m_fpGetThreadUICultureId) {
828 int ret = (*m_fpGetThreadUICultureId)(&langIdValue);
830 // Callback can't return 0, since that indicates empty.
831 // To indicate empty, callback should return UICULTUREID_DONTCARE
836 langId = UICULTUREID_DONTCARE;
838 HRESULT hr = GetLibrary(langId, pHInst);
842 HRESULT CCompRC::LoadResourceFile(HRESOURCEDLL * pHInst, LPCWSTR lpFileName)
845 DWORD dwLoadLibraryFlags;
846 if(m_pResourceFile == m_pDefaultResource)
847 dwLoadLibraryFlags = LOAD_LIBRARY_AS_DATAFILE;
849 dwLoadLibraryFlags = 0;
851 if ((*pHInst = WszLoadLibraryEx(lpFileName, NULL, dwLoadLibraryFlags)) == NULL) {
852 return HRESULT_FROM_GetLastError();
854 #else // !FEATURE_PAL
855 PORTABILITY_ASSERT("UNIXTODO: Implement resource loading - use peimagedecoder?");
856 #endif // !FEATURE_PAL
860 //*****************************************************************************
861 // Load the library for this thread's current language
862 // Called once per language.
864 // 1. Dll in localized path (<dir passed>\<lang name (en-US format)>\mscorrc.dll)
865 // 2. Dll in localized (parent) path (<dir passed>\<lang name> (en format)\mscorrc.dll)
866 // 3. Dll in root path (<dir passed>\mscorrc.dll)
867 // 4. Dll in current path (<current dir>\mscorrc.dll)
868 //*****************************************************************************
869 HRESULT CCompRC::LoadLibraryHelper(HRESOURCEDLL *pHInst,
876 #ifdef MODE_PREEMPTIVE
885 _ASSERTE(m_pResourceFile != NULL);
887 // must initialize before calling SString::Empty()
890 // Try and get both the culture fallback sequence
892 StringArrayList cultureNames;
894 if (m_fpGetThreadUICultureNames)
896 hr = (*m_fpGetThreadUICultureNames)(&cultureNames);
902 cultureNames.Append(SString::Empty());
904 EX_CATCH_HRESULT(hr);
907 if (hr == E_OUTOFMEMORY)
911 for (DWORD i=0; i< cultureNames.GetCount();i++)
913 SString& sLang = cultureNames[i];
915 PathString rcPathName(rcPath);
917 if (!rcPathName.EndsWith(W("\\")))
919 rcPathName.Append(W("\\"));
922 if (!sLang.IsEmpty())
924 rcPathName.Append(sLang);
925 rcPathName.Append(W("\\"));
926 rcPathName.Append(m_pResourceFile);
930 rcPathName.Append(m_pResourceFile);
933 // Feedback for debugging to eliminate unecessary loads.
934 DEBUG_STMT(DbgWriteEx(W("Loading %s to load strings.\n"), rcPath.GetUnicode()));
936 // Load the resource library as a data file, so that the OS doesn't have
937 // to allocate it as code. This only works so long as the file contains
939 hr = LoadResourceFile(pHInst, rcPathName);
945 EX_CATCH_HRESULT(hr);
947 // Last ditch search effort in current directory
949 hr = LoadResourceFile(pHInst, m_pResourceFile);
955 // Two-stage approach:
956 // First try module directory, then try CORSystemDirectory for default resource
957 HRESULT CCompRC::LoadLibraryThrows(HRESOURCEDLL * pHInst)
963 #ifdef MODE_PREEMPTIVE
971 _ASSERTE(pHInst != NULL);
973 #ifdef CROSSGEN_COMPILE
974 // The resources are embeded into the .exe itself for crossgen
975 *pHInst = GetModuleInst();
977 PathString rcPath; // Path to resource DLL.
979 // Try first in the same directory as this dll.
981 VALIDATECORECLRCALLBACKS();
984 hr = g_CoreClrCallbacks.m_pfnGetCORSystemDirectory(rcPath);
988 hr = LoadLibraryHelper(pHInst, rcPath);
991 #endif // CROSSGEN_COMPILE
995 HRESULT CCompRC::LoadLibrary(HRESOURCEDLL * pHInst)
1001 #ifdef MODE_PREEMPTIVE
1010 hr = LoadLibraryThrows(pHInst);
1012 EX_CATCH_HRESULT(hr);
1016 #endif // DACCESS_COMPILE
1020 #ifdef USE_FORMATMESSAGE_WRAPPER
1023 CCompRC::FormatMessage(
1025 IN LPCVOID lpSource,
1026 IN DWORD dwMessageId,
1027 IN DWORD dwLanguageId,
1028 OUT LPWSTR lpBuffer,
1030 IN va_list *Arguments)
1032 STATIC_CONTRACT_NOTHROW;
1034 if (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
1036 dwFlags&=~FORMAT_MESSAGE_FROM_SYSTEM;
1037 dwFlags|=FORMAT_MESSAGE_FROM_STRING;
1038 str.LoadResourceAndReturnHR(NULL,CCompRC::Error,dwMessageId);
1039 lpSource=str.GetUnicode();
1041 return WszFormatMessage(dwFlags,
1049 #endif // USE_FORMATMESSAGE_WRAPPER