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 /*============================================================
11 ** Purpose: Default way to access String and Object resources
15 ===========================================================*/
17 namespace System.Resources
21 using System.Globalization;
22 using System.Collections;
24 using System.Reflection;
25 using System.Runtime.Serialization;
26 using System.Security;
27 using System.Threading;
28 using System.Runtime.InteropServices;
29 using System.Runtime.CompilerServices;
30 using Microsoft.Win32;
31 using System.Collections.Generic;
32 using System.Runtime.Versioning;
33 using System.Diagnostics;
37 // This is implemented in System.Runtime.WindowsRuntime as function System.Resources.WindowsRuntimeResourceManager,
38 // allowing us to ask for a WinRT-specific ResourceManager.
39 // It is important to have WindowsRuntimeResourceManagerBase as regular class with virtual methods and default implementations.
40 // Defining WindowsRuntimeResourceManagerBase as abstract class or interface will cause issues when adding more methods to it
41 // because it'll create dependency between mscorlib and System.Runtime.WindowsRuntime which will require always shipping both DLLs together.
43 // [FriendAccessAllowed]
44 internal abstract class WindowsRuntimeResourceManagerBase
46 public abstract bool Initialize(string libpath, string reswFilename, out PRIExceptionInfo exceptionInfo);
48 public abstract String GetString(String stringName, String startingCulture, String neutralResourcesCulture);
50 public abstract CultureInfo GlobalResourceContextBestFitCultureInfo
55 public abstract bool SetGlobalResourceContextDefaultCulture(CultureInfo ci);
58 // [FriendAccessAllowed]
59 internal class PRIExceptionInfo
61 public string _PackageSimpleName;
62 public string _ResWFile;
64 #endif // FEATURE_APPX
66 // Resource Manager exposes an assembly's resources to an application for
67 // the correct CultureInfo. An example would be localizing text for a
68 // user-visible message. Create a set of resource files listing a name
69 // for a message and its value, compile them using ResGen, put them in
70 // an appropriate place (your assembly manifest(?)), then create a Resource
71 // Manager and query for the name of the message you want. The Resource
72 // Manager will use CultureInfo.GetCurrentUICulture() to look
73 // up a resource for your user's locale settings.
75 // Users should ideally create a resource file for every culture, or
76 // at least a meaningful subset. The filenames will follow the naming
79 // basename.culture name.resources
81 // The base name can be the name of your application, or depending on
82 // the granularity desired, possibly the name of each class. The culture
83 // name is determined from CultureInfo's Name property.
84 // An example file name may be MyApp.en-US.resources for
85 // MyApp's US English resources.
90 // In Feb 08, began first step of refactoring ResourceManager to improve
91 // maintainability (sd changelist 3012100). This resulted in breaking
92 // apart the InternalGetResourceSet "big loop" so that the file-based
93 // and manifest-based lookup was located in separate methods.
94 // In Apr 08, continued refactoring so that file-based and manifest-based
95 // concerns are encapsulated by separate classes. At construction, the
96 // ResourceManager creates one of these classes based on whether the
97 // RM will need to use file-based or manifest-based resources, and
98 // afterwards refers to this through the interface IResourceGroveler.
100 // Serialization Compat: Ideally, we could have refactored further but
101 // this would have broken serialization compat. For example, the
102 // ResourceManager member UseManifest and UseSatelliteAssem are no
103 // longer relevant on ResourceManager. Similarly, other members could
104 // ideally be moved to the file-based or manifest-based classes
105 // because they are only relevant for those types of lookup.
107 // Solution now / in the future:
108 // For now, we simply use a mediator class so that we can keep these
109 // members on ResourceManager but allow the file-based and manifest-
110 // based classes to access/set these members in a uniform way. See
111 // ResourceManagerMediator.
112 // We encapsulate fallback logic in a fallback iterator class, so that
113 // this logic isn't duplicated in several methods.
115 // In the future, we can look into either breaking serialization if we
116 // decide this doesn't make sense for ResourceManager (i.e. how common
117 // is the scenario), manually make serialization work by providing
118 // appropriate OnSerialization, Deserialization methods. We can also
119 // look into further factoring and better design of IResourceGroveler
120 // interface to accommodate unused parameters that don't make sense
121 // for either file-based or manifest-based lookup paths.
123 // Benefits of this refactoring:
124 // - Makes it possible to understand what the ResourceManager does,
125 // which is key for maintainability.
126 // - Makes the ResourceManager more extensible by identifying and
127 // encapsulating what varies
128 // - Unearthed a bug that's been lurking a while in file-based
129 // lookup paths for InternalGetResourceSet if createIfNotExists is
131 // - Reuses logic, e.g. by breaking apart the culture fallback into
132 // the fallback iterator class, we don't have to repeat the
133 // sometimes confusing fallback logic across multiple methods
134 // - Fxcop violations reduced to 1/5th of original count. Most
135 // importantly, code complexity violations disappeared.
136 // - Finally, it got rid of dead code paths. Because the big loop was
137 // so confusing, it masked unused chunks of code. Also, dividing
138 // between file-based and manifest-based allowed functionaliy
139 // unused in silverlight to fall out.
141 // Note: this type is integral to the construction of exception objects,
142 // and sometimes this has to be done in low memory situtations (OOM) or
143 // to create TypeInitializationExceptions due to failure of a static class
144 // constructor. This type needs to be extremely careful and assume that
145 // any type it references may have previously failed to construct, so statics
146 // belonging to that type may not be initialized. FrameworkEventSource.Log
147 // is one such example.
150 public class ResourceManager
152 internal class CultureNameResourceSetPair
154 public String lastCultureName;
155 public ResourceSet lastResourceSet;
158 protected String BaseNameField;
159 // Sets is a many-to-one table of CultureInfos mapped to ResourceSets.
160 // Don't synchronize ResourceSets - too fine-grained a lock to be effective
161 [Obsolete("call InternalGetResourceSet instead")]
162 internal Hashtable ResourceSets;
165 // don't serialize the cache of ResourceSets
167 private Dictionary<String, ResourceSet> _resourceSets;
168 private String moduleDir; // For assembly-ignorant directory location
169 protected Assembly MainAssembly; // Need the assembly manifest sometimes.
170 private Type _locationInfo; // For Assembly or type-based directory layout
171 private Type _userResourceSet; // Which ResourceSet instance to create
172 private CultureInfo _neutralResourcesCulture; // For perf optimizations.
175 private CultureNameResourceSetPair _lastUsedResourceCache;
177 private bool _ignoreCase; // Whether case matters in GetString & GetObject
179 private bool UseManifest; // Use Assembly manifest, or grovel disk.
181 // unused! But need to keep for serialization
182 [OptionalField(VersionAdded = 1)]
183 private bool UseSatelliteAssem; // Are all the .resources files in the
184 // main assembly, or in satellite assemblies for each culture?
185 #if RESOURCE_SATELLITE_CONFIG
186 private static volatile Hashtable _installedSatelliteInfo; // Give the user the option
187 // to prevent certain satellite assembly probes via a config file.
188 // Note that config files are per-appdomain, not per-assembly nor process
189 private static volatile bool _checkedConfigFile; // Did we read the app's config file?
192 // Whether to fall back to the main assembly or a particular
193 // satellite for the neutral resources.
195 private UltimateResourceFallbackLocation _fallbackLoc;
196 // Version number of satellite assemblies to look for. May be null.
198 private Version _satelliteContractVersion;
200 private bool _lookedForSatelliteContractVersion;
202 // unused! But need to keep for serialization
203 [OptionalField(VersionAdded = 1)]
204 private Assembly _callingAssembly; // Assembly who created the ResMgr.
206 // replaces _callingAssembly
207 [OptionalField(VersionAdded = 4)]
208 private RuntimeAssembly m_callingAssembly; // Assembly who created the ResMgr.
210 // no need to serialize this; just create a new one on deserialization
212 private IResourceGroveler resourceGroveler;
214 public static readonly int MagicNumber = unchecked((int)0xBEEFCACE); // If only hex had a K...
216 // Version number so ResMgr can get the ideal set of classes for you.
218 // 1) MagicNumber (little endian Int32)
219 // 2) HeaderVersionNumber (little endian Int32)
220 // 3) Num Bytes to skip past ResMgr header (little endian Int32)
221 // 4) IResourceReader type name for this file (bytelength-prefixed UTF-8 String)
222 // 5) ResourceSet type name for this file (bytelength-prefixed UTF8 String)
223 public static readonly int HeaderVersionNumber = 1;
226 //It would be better if we could use _neutralCulture instead of calling
227 //CultureInfo.InvariantCulture everywhere, but we run into problems with the .cctor. CultureInfo
228 //initializes assembly, which initializes ResourceManager, which tries to get a CultureInfo which isn't
229 //there yet because CultureInfo's class initializer hasn't finished. If we move SystemResMgr off of
230 //Assembly (or at least make it an internal property) we should be able to circumvent this problem.
232 // private static CultureInfo _neutralCulture = null;
234 // This is our min required ResourceSet type.
235 private static readonly Type _minResourceSet = typeof(ResourceSet);
236 // These Strings are used to avoid using Reflection in CreateResourceSet.
237 // The first set are used by ResourceWriter. The second are used by
239 internal static readonly String ResReaderTypeName = typeof(ResourceReader).FullName;
240 internal static readonly String ResSetTypeName = typeof(RuntimeResourceSet).FullName;
241 internal static readonly String MscorlibName = typeof(ResourceReader).Assembly.FullName;
242 internal const String ResFileExtension = ".resources";
243 internal const int ResFileExtensionLength = 10;
245 private static volatile bool s_IsAppXModel;
247 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
250 m_callingAssembly = (RuntimeAssembly)Assembly.GetCallingAssembly();
253 protected ResourceManager()
257 _lastUsedResourceCache = new CultureNameResourceSetPair();
258 ResourceManagerMediator mediator = new ResourceManagerMediator(this);
259 resourceGroveler = new ManifestBasedResourceGroveler(mediator);
262 // Constructs a Resource Manager for files beginning with
263 // baseName in the directory specified by resourceDir
264 // or in the current directory. This Assembly-ignorant constructor is
265 // mostly useful for testing your own ResourceSet implementation.
267 // A good example of a baseName might be "Strings". BaseName
268 // should not end in ".resources".
270 // Note: System.Windows.Forms uses this method at design time.
272 private ResourceManager(String baseName, String resourceDir, Type usingResourceSet)
274 if (null == baseName)
275 throw new ArgumentNullException(nameof(baseName));
276 if (null == resourceDir)
277 throw new ArgumentNullException(nameof(resourceDir));
279 BaseNameField = baseName;
281 moduleDir = resourceDir;
282 _userResourceSet = usingResourceSet;
283 #pragma warning disable 618
284 ResourceSets = new Hashtable(); // for backward compatibility
285 #pragma warning restore 618
286 _resourceSets = new Dictionary<String, ResourceSet>();
287 _lastUsedResourceCache = new CultureNameResourceSetPair();
290 ResourceManagerMediator mediator = new ResourceManagerMediator(this);
291 resourceGroveler = new FileBasedResourceGroveler(mediator);
294 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
295 public ResourceManager(String baseName, Assembly assembly)
297 if (null == baseName)
298 throw new ArgumentNullException(nameof(baseName));
300 if (null == assembly)
301 throw new ArgumentNullException(nameof(assembly));
303 if (!(assembly is RuntimeAssembly))
304 throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
306 MainAssembly = assembly;
307 BaseNameField = baseName;
309 SetAppXConfiguration();
311 CommonAssemblyInit();
313 m_callingAssembly = (RuntimeAssembly)Assembly.GetCallingAssembly();
314 // Special case for mscorlib - protect mscorlib's private resources.
315 // This isn't for security reasons, but to ensure we can make
316 // breaking changes to mscorlib's internal resources without
317 // assuming users may have taken a dependency on them.
318 if (assembly == typeof(Object).Assembly && m_callingAssembly != assembly)
320 m_callingAssembly = null;
324 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
325 public ResourceManager(String baseName, Assembly assembly, Type usingResourceSet)
327 if (null == baseName)
328 throw new ArgumentNullException(nameof(baseName));
329 if (null == assembly)
330 throw new ArgumentNullException(nameof(assembly));
332 if (!(assembly is RuntimeAssembly))
333 throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
335 MainAssembly = assembly;
336 BaseNameField = baseName;
338 if (usingResourceSet != null && (usingResourceSet != _minResourceSet) && !(usingResourceSet.IsSubclassOf(_minResourceSet)))
339 throw new ArgumentException(SR.Arg_ResMgrNotResSet, nameof(usingResourceSet));
340 _userResourceSet = usingResourceSet;
342 CommonAssemblyInit();
343 m_callingAssembly = (RuntimeAssembly)Assembly.GetCallingAssembly();
344 // Special case for mscorlib - protect mscorlib's private resources.
345 // This isn't for security reasons, but to ensure we can make
346 // breaking changes to mscorlib's internal resources without
347 // assuming users may have taken a dependency on them.
348 if (assembly == typeof(Object).Assembly && m_callingAssembly != assembly)
349 m_callingAssembly = null;
352 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
353 public ResourceManager(Type resourceSource)
355 if (null == resourceSource)
356 throw new ArgumentNullException(nameof(resourceSource));
358 if (!(resourceSource is RuntimeType))
359 throw new ArgumentException(SR.Argument_MustBeRuntimeType);
361 _locationInfo = resourceSource;
362 MainAssembly = _locationInfo.Assembly;
363 BaseNameField = resourceSource.Name;
365 SetAppXConfiguration();
367 CommonAssemblyInit();
369 m_callingAssembly = (RuntimeAssembly)Assembly.GetCallingAssembly();
370 // Special case for mscorlib - protect mscorlib's private resources.
371 if (MainAssembly == typeof(Object).Assembly && m_callingAssembly != MainAssembly)
373 m_callingAssembly = null;
378 private void OnDeserializing(StreamingContext ctx)
380 _resourceSets = null;
381 resourceGroveler = null;
382 _lastUsedResourceCache = null;
386 private void OnDeserialized(StreamingContext ctx)
388 _resourceSets = new Dictionary<String, ResourceSet>();
389 _lastUsedResourceCache = new CultureNameResourceSetPair();
390 // set up resource groveler, depending on whether this ResourceManager
391 // is looking for files or assemblies
392 ResourceManagerMediator mediator = new ResourceManagerMediator(this);
395 resourceGroveler = new ManifestBasedResourceGroveler(mediator);
399 resourceGroveler = new FileBasedResourceGroveler(mediator);
402 // correct callingAssembly for v2
403 if (m_callingAssembly == null)
405 m_callingAssembly = (RuntimeAssembly)_callingAssembly;
408 // v2 does this lazily
409 if (UseManifest && _neutralResourcesCulture == null)
411 _neutralResourcesCulture = ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(MainAssembly, ref _fallbackLoc);
416 private void OnSerializing(StreamingContext ctx)
418 // Initialize the fields Whidbey expects
419 _callingAssembly = m_callingAssembly;
420 UseSatelliteAssem = UseManifest;
421 #pragma warning disable 618
422 ResourceSets = new Hashtable(); // for backward compatibility
423 #pragma warning restore 618
427 // Trying to unify code as much as possible, even though having to do a
428 // security check in each constructor prevents it.
429 private void CommonAssemblyInit()
431 // Now we can use the managed resources even when using PRI's to support the APIs GetObject, GetStream...etc.
434 _resourceSets = new Dictionary<String, ResourceSet>();
435 _lastUsedResourceCache = new CultureNameResourceSetPair();
437 _fallbackLoc = UltimateResourceFallbackLocation.MainAssembly;
439 ResourceManagerMediator mediator = new ResourceManagerMediator(this);
440 resourceGroveler = new ManifestBasedResourceGroveler(mediator);
442 _neutralResourcesCulture = ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(MainAssembly, ref _fallbackLoc);
445 // Gets the base name for the ResourceManager.
446 public virtual String BaseName
448 get { return BaseNameField; }
451 // Whether we should ignore the capitalization of resources when calling
452 // GetString or GetObject.
453 public virtual bool IgnoreCase
455 get { return _ignoreCase; }
456 set { _ignoreCase = value; }
459 // Returns the Type of the ResourceSet the ResourceManager uses
460 // to construct ResourceSets.
461 public virtual Type ResourceSetType
463 get { return (_userResourceSet == null) ? typeof(RuntimeResourceSet) : _userResourceSet; }
466 protected UltimateResourceFallbackLocation FallbackLocation
468 get { return _fallbackLoc; }
469 set { _fallbackLoc = value; }
472 // Tells the ResourceManager to call Close on all ResourceSets and
473 // release all resources. This will shrink your working set by
474 // potentially a substantial amount in a running application. Any
475 // future resource lookups on this ResourceManager will be as
476 // expensive as the very first lookup, since it will need to search
477 // for files and load resources again.
479 // This may be useful in some complex threading scenarios, where
480 // creating a new ResourceManager isn't quite the correct behavior.
481 public virtual void ReleaseAllResources()
483 Dictionary<String, ResourceSet> localResourceSets = _resourceSets;
485 // If any calls to Close throw, at least leave ourselves in a
487 _resourceSets = new Dictionary<String, ResourceSet>();
488 _lastUsedResourceCache = new CultureNameResourceSetPair();
490 lock (localResourceSets)
492 IDictionaryEnumerator setEnum = localResourceSets.GetEnumerator();
494 while (setEnum.MoveNext())
496 ((ResourceSet)setEnum.Value).Close();
501 public static ResourceManager CreateFileBasedResourceManager(String baseName, String resourceDir, Type usingResourceSet)
503 return new ResourceManager(baseName, resourceDir, usingResourceSet);
506 // Given a CultureInfo, GetResourceFileName generates the name for
507 // the binary file for the given CultureInfo. This method uses
508 // CultureInfo's Name property as part of the file name for all cultures
509 // other than the invariant culture. This method does not touch the disk,
510 // and is used only to construct what a resource file name (suitable for
511 // passing to the ResourceReader constructor) or a manifest resource file
512 // name should look like.
514 // This method can be overriden to look for a different extension,
515 // such as ".ResX", or a completely different format for naming files.
516 protected virtual String GetResourceFileName(CultureInfo culture)
518 // If this is the neutral culture, don't include the culture name.
519 if (culture.HasInvariantCultureName)
521 return BaseNameField + ResFileExtension;
525 CultureInfo.VerifyCultureName(culture.Name, throwException: true);
526 return BaseNameField + "." + culture.Name + ResFileExtension;
530 // WARNING: This function must be kept in sync with ResourceFallbackManager.GetEnumerator()
531 // Return the first ResourceSet, based on the first culture ResourceFallbackManager would return
532 internal ResourceSet GetFirstResourceSet(CultureInfo culture)
534 // Logic from ResourceFallbackManager.GetEnumerator()
535 if (_neutralResourcesCulture != null && culture.Name == _neutralResourcesCulture.Name)
537 culture = CultureInfo.InvariantCulture;
540 if (_lastUsedResourceCache != null)
542 lock (_lastUsedResourceCache)
544 if (culture.Name == _lastUsedResourceCache.lastCultureName)
545 return _lastUsedResourceCache.lastResourceSet;
549 // Look in the ResourceSet table
550 Dictionary<String, ResourceSet> localResourceSets = _resourceSets;
551 ResourceSet rs = null;
552 if (localResourceSets != null)
554 lock (localResourceSets)
556 localResourceSets.TryGetValue(culture.Name, out rs);
562 // update the cache with the most recent ResourceSet
563 if (_lastUsedResourceCache != null)
565 lock (_lastUsedResourceCache)
567 _lastUsedResourceCache.lastCultureName = culture.Name;
568 _lastUsedResourceCache.lastResourceSet = rs;
577 // Looks up a set of resources for a particular CultureInfo. This is
578 // not useful for most users of the ResourceManager - call
579 // GetString() or GetObject() instead.
581 // The parameters let you control whether the ResourceSet is created
582 // if it hasn't yet been loaded and if parent CultureInfos should be
583 // loaded as well for resource inheritance.
585 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
586 public virtual ResourceSet GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
589 throw new ArgumentNullException(nameof(culture));
591 Dictionary<String, ResourceSet> localResourceSets = _resourceSets;
593 if (localResourceSets != null)
595 lock (localResourceSets)
597 if (localResourceSets.TryGetValue(culture.Name, out rs))
602 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
604 if (UseManifest && culture.HasInvariantCultureName)
606 string fileName = GetResourceFileName(culture);
607 RuntimeAssembly mainAssembly = (RuntimeAssembly)MainAssembly;
608 Stream stream = mainAssembly.GetManifestResourceStream(_locationInfo, fileName, m_callingAssembly == MainAssembly, ref stackMark);
609 if (createIfNotExists && stream != null)
611 rs = ((ManifestBasedResourceGroveler)resourceGroveler).CreateResourceSet(stream, MainAssembly);
612 AddResourceSet(localResourceSets, culture.Name, ref rs);
617 // Note: ideally we could plumb through the stack crawl mark here, but we must
618 // call the virtual 3-argument InternalGetResourceSet method for compatibility.
619 // Security-wise, we're not overly interested in protecting access to resources,
620 // since full-trust callers can get them already and most resources are public.
621 // Also, the JIT inliner could always inline a caller into another assembly's
623 // So if we happen to return some resources in cases where we should really be
624 // doing a demand for member access permissions, we're not overly concerned.
625 return InternalGetResourceSet(culture, createIfNotExists, tryParents);
628 // InternalGetResourceSet is a non-threadsafe method where all the logic
629 // for getting a resource set lives. Access to it is controlled by
630 // threadsafe methods such as GetResourceSet, GetString, & GetObject.
631 // This will take a minimal number of locks.
632 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
633 protected virtual ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
635 Debug.Assert(culture != null, "culture != null");
637 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
638 return InternalGetResourceSet(culture, createIfNotExists, tryParents, ref stackMark);
641 // InternalGetResourceSet is a non-threadsafe method where all the logic
642 // for getting a resource set lives. Access to it is controlled by
643 // threadsafe methods such as GetResourceSet, GetString, & GetObject.
644 // This will take a minimal number of locks.
645 private ResourceSet InternalGetResourceSet(CultureInfo requestedCulture, bool createIfNotExists, bool tryParents, ref StackCrawlMark stackMark)
647 Dictionary<String, ResourceSet> localResourceSets = _resourceSets;
648 ResourceSet rs = null;
649 CultureInfo foundCulture = null;
650 lock (localResourceSets)
652 if (localResourceSets.TryGetValue(requestedCulture.Name, out rs))
658 ResourceFallbackManager mgr = new ResourceFallbackManager(requestedCulture, _neutralResourcesCulture, tryParents);
660 foreach (CultureInfo currentCultureInfo in mgr)
662 lock (localResourceSets)
664 if (localResourceSets.TryGetValue(currentCultureInfo.Name, out rs))
666 // we need to update the cache if we fellback
667 if (requestedCulture != currentCultureInfo) foundCulture = currentCultureInfo;
672 // InternalGetResourceSet will never be threadsafe. However, it must
673 // be protected against reentrancy from the SAME THREAD. (ie, calling
674 // GetSatelliteAssembly may send some window messages or trigger the
675 // Assembly load event, which could fail then call back into the
676 // ResourceManager). It's happened.
678 rs = resourceGroveler.GrovelForResourceSet(currentCultureInfo, localResourceSets,
679 tryParents, createIfNotExists, ref stackMark);
681 // found a ResourceSet; we're done
684 foundCulture = currentCultureInfo;
689 if (rs != null && foundCulture != null)
691 // add entries to the cache for the cultures we have gone through
693 // currentCultureInfo now refers to the culture that had resources.
694 // update cultures starting from requested culture up to the culture
695 // that had resources.
696 foreach (CultureInfo updateCultureInfo in mgr)
698 AddResourceSet(localResourceSets, updateCultureInfo.Name, ref rs);
700 // stop when we've added current or reached invariant (top of chain)
701 if (updateCultureInfo == foundCulture)
711 // Simple helper to ease maintenance and improve readability.
712 private static void AddResourceSet(Dictionary<String, ResourceSet> localResourceSets, String cultureName, ref ResourceSet rs)
714 // InternalGetResourceSet is both recursive and reentrant -
715 // assembly load callbacks in particular are a way we can call
716 // back into the ResourceManager in unexpectedly on the same thread.
717 lock (localResourceSets)
719 // If another thread added this culture, return that.
720 ResourceSet lostRace;
721 if (localResourceSets.TryGetValue(cultureName, out lostRace))
723 if (!Object.ReferenceEquals(lostRace, rs))
725 // Note: In certain cases, we can be trying to add a ResourceSet for multiple
726 // cultures on one thread, while a second thread added another ResourceSet for one
727 // of those cultures. If there is a race condition we must make sure our ResourceSet
728 // isn't in our dictionary before closing it.
729 if (!localResourceSets.ContainsValue(rs))
736 localResourceSets.Add(cultureName, rs);
741 protected static Version GetSatelliteContractVersion(Assembly a)
743 // Ensure that the assembly reference is not null
746 throw new ArgumentNullException(nameof(a), SR.ArgumentNull_Assembly);
749 // Return null. The calling code will use the assembly version instead to avoid potential type
750 // and library loads caused by CA lookup. NetCF uses the assembly version always.
754 protected static CultureInfo GetNeutralResourcesLanguage(Assembly a)
756 // This method should be obsolete - replace it with the one below.
757 // Unfortunately, we made it protected.
758 UltimateResourceFallbackLocation ignoringUsefulData = UltimateResourceFallbackLocation.MainAssembly;
759 CultureInfo culture = ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(a, ref ignoringUsefulData);
764 internal static bool CompareNames(String asmTypeName1,
766 AssemblyName asmName2)
768 Debug.Assert(asmTypeName1 != null, "asmTypeName1 was unexpectedly null");
770 // First, compare type names
771 int comma = asmTypeName1.IndexOf(',');
772 if (((comma == -1) ? asmTypeName1.Length : comma) != typeName2.Length)
776 if (String.Compare(asmTypeName1, 0, typeName2, 0, typeName2.Length, StringComparison.Ordinal) != 0)
781 // Now, compare assembly display names (IGNORES VERSION AND PROCESSORARCHITECTURE)
782 // also, for mscorlib ignores everything, since that's what the binder is going to do
783 while (Char.IsWhiteSpace(asmTypeName1[++comma])) ;
786 AssemblyName an1 = new AssemblyName(asmTypeName1.Substring(comma));
787 if (String.Compare(an1.Name, asmName2.Name, StringComparison.OrdinalIgnoreCase) != 0)
790 // to match IsMscorlib() in VM
791 if (String.Compare(an1.Name, System.CoreLib.Name, StringComparison.OrdinalIgnoreCase) == 0)
795 if ((an1.CultureInfo != null) && (asmName2.CultureInfo != null) &&
797 (an1.CultureInfo.LCID != asmName2.CultureInfo.LCID)
799 (an1.CultureInfo.Name != asmName2.CultureInfo.Name)
804 byte[] pkt1 = an1.GetPublicKeyToken();
805 byte[] pkt2 = asmName2.GetPublicKeyToken();
806 if ((pkt1 != null) && (pkt2 != null))
808 if (pkt1.Length != pkt2.Length)
811 for (int i = 0; i < pkt1.Length; i++)
813 if (pkt1[i] != pkt2[i])
822 private string GetStringFromPRI(String stringName, String startingCulture, String neutralResourcesCulture)
824 Debug.Assert(_bUsingModernResourceManagement);
825 Debug.Assert(_WinRTResourceManager != null);
826 Debug.Assert(_PRIonAppXInitialized);
827 Debug.Assert(AppDomain.IsAppXModel());
829 if (stringName.Length == 0)
832 string resourceString = null;
834 // Do not handle exceptions. See the comment in SetAppXConfiguration about throwing
835 // exception types that the ResourceManager class is not documented to throw.
836 resourceString = _WinRTResourceManager.GetString(
838 String.IsNullOrEmpty(startingCulture) ? null : startingCulture,
839 String.IsNullOrEmpty(neutralResourcesCulture) ? null : neutralResourcesCulture);
841 return resourceString;
844 // Since we can't directly reference System.Runtime.WindowsRuntime from mscorlib, we have to get the type via reflection.
845 // It would be better if we could just implement WindowsRuntimeResourceManager in mscorlib, but we can't, because
846 // we can do very little with WinRT in mscorlib.
847 internal static WindowsRuntimeResourceManagerBase GetWinRTResourceManager()
849 Type WinRTResourceManagerType = Type.GetType("System.Resources.WindowsRuntimeResourceManager, " + AssemblyRef.SystemRuntimeWindowsRuntime, true);
850 return (WindowsRuntimeResourceManagerBase)Activator.CreateInstance(WinRTResourceManagerType, true);
855 private bool _bUsingModernResourceManagement; // Written only by SetAppXConfiguration
859 private WindowsRuntimeResourceManagerBase _WinRTResourceManager; // Written only by SetAppXConfiguration
862 private bool _PRIonAppXInitialized; // Written only by SetAppXConfiguration
865 private PRIExceptionInfo _PRIExceptionInfo; // Written only by SetAppXConfiguration
867 // When running under AppX, the following rules apply for resource lookup:
869 // 1) For Framework assemblies, we always use satellite assembly based lookup.
870 // 2) For non-FX assemblies:
872 // a) If the assembly lives under PLATFORM_RESOURCE_ROOTS (as specified by the host during AppDomain creation),
873 // then we will use satellite assembly based lookup in assemblies like *.resources.dll.
875 // b) For any other non-FX assembly, we will use the modern resource manager with the premise that app package
876 // contains the PRI resources.
877 private bool ShouldUseSatelliteAssemblyResourceLookupUnderAppX(RuntimeAssembly resourcesAssembly)
879 bool fUseSatelliteAssemblyResourceLookupUnderAppX = typeof(Object).Assembly == resourcesAssembly;
881 if (!fUseSatelliteAssemblyResourceLookupUnderAppX)
883 // Check to see if the assembly is under PLATFORM_RESOURCE_ROOTS. If it is, then we should use satellite assembly lookup for it.
884 String platformResourceRoots = (String)(AppDomain.CurrentDomain.GetData("PLATFORM_RESOURCE_ROOTS"));
885 if ((platformResourceRoots != null) && (platformResourceRoots != String.Empty))
887 string resourceAssemblyPath = resourcesAssembly.Location;
889 // Loop through the PLATFORM_RESOURCE_ROOTS and see if the assembly is contained in it.
890 foreach (string pathPlatformResourceRoot in platformResourceRoots.Split(Path.PathSeparator))
892 if (resourceAssemblyPath.StartsWith(pathPlatformResourceRoot, StringComparison.CurrentCultureIgnoreCase))
894 // Found the resource assembly to be present in one of the PLATFORM_RESOURCE_ROOT, so stop the enumeration loop.
895 fUseSatelliteAssemblyResourceLookupUnderAppX = true;
902 return fUseSatelliteAssemblyResourceLookupUnderAppX;
904 #endif // FEATURE_APPX
906 // Only call SetAppXConfiguration from ResourceManager constructors, and nowhere else.
907 // Throws MissingManifestResourceException and WinRT HResults
909 private void SetAppXConfiguration()
911 Debug.Assert(_bUsingModernResourceManagement == false); // Only this function writes to this member
913 Debug.Assert(_WinRTResourceManager == null); // Only this function writes to this member
914 Debug.Assert(_PRIonAppXInitialized == false); // Only this function writes to this member
915 Debug.Assert(_PRIExceptionInfo == null); // Only this function writes to this member
917 bool bUsingSatelliteAssembliesUnderAppX = false;
919 RuntimeAssembly resourcesAssembly = (RuntimeAssembly)MainAssembly;
921 if (resourcesAssembly == null)
922 resourcesAssembly = m_callingAssembly;
924 if (resourcesAssembly != null)
926 if (resourcesAssembly != typeof(Object).Assembly) // We are not loading resources for mscorlib
928 // Cannot load the WindowsRuntimeResourceManager when in a compilation process, since it
929 // lives in System.Runtime.WindowsRuntime and only mscorlib may be loaded for execution.
930 if (AppDomain.IsAppXModel())
932 s_IsAppXModel = true;
934 // If we have the type information from the ResourceManager(Type) constructor, we use it. Otherwise, we use BaseNameField.
935 String reswFilename = _locationInfo == null ? BaseNameField : _locationInfo.FullName;
937 // The only way this can happen is if a class inherited from ResourceManager and
938 // did not set the BaseNameField before calling the protected ResourceManager() constructor.
939 // For other constructors, we would already have thrown an ArgumentNullException by now.
940 // Throwing an ArgumentNullException now is not the right thing to do because technically
941 // ResourceManager() takes no arguments, and because it is not documented as throwing
942 // any exceptions. Instead, let's go through the rest of the initialization with this set to
943 // an empty string. We may in fact fail earlier for another reason, but otherwise we will
944 // throw a MissingManifestResourceException when GetString is called indicating that a
945 // resW filename called "" could not be found.
946 if (reswFilename == null)
947 reswFilename = String.Empty;
949 WindowsRuntimeResourceManagerBase WRRM = null;
950 bool bWRRM_Initialized = false;
952 if (AppDomain.IsAppXDesignMode())
954 WRRM = GetWinRTResourceManager();
957 PRIExceptionInfo exceptionInfo; // If the exception info is filled in, we will ignore it.
958 bWRRM_Initialized = WRRM.Initialize(resourcesAssembly.Location, reswFilename, out exceptionInfo);
959 bUsingSatelliteAssembliesUnderAppX = !bWRRM_Initialized;
963 bUsingSatelliteAssembliesUnderAppX = true;
969 if (!bUsingSatelliteAssembliesUnderAppX)
971 _bUsingModernResourceManagement = !ShouldUseSatelliteAssemblyResourceLookupUnderAppX(resourcesAssembly);
973 if (_bUsingModernResourceManagement)
975 // Only now are we certain that we need the PRI file.
977 // Note that if IsAppXDesignMode is false, we haven't checked if the PRI file exists.
978 // This is by design. We will find out in the call to WindowsRuntimeResourceManager.Initialize below.
980 // At this point it is important NOT to set _bUsingModernResourceManagement to false
981 // if the PRI file does not exist because we are now certain we need to load PRI
982 // resources. We want to fail by throwing a MissingManifestResourceException
983 // if WindowsRuntimeResourceManager.Initialize fails to locate the PRI file. We do not
984 // want to fall back to using satellite assemblies anymore. Note that we would not throw
985 // the MissingManifestResourceException from this function, but from GetString. See the
986 // comment below on the reason for this.
988 if (WRRM != null && bWRRM_Initialized)
990 // Reuse the one successfully created earlier
991 _WinRTResourceManager = WRRM;
992 _PRIonAppXInitialized = true;
996 _WinRTResourceManager = GetWinRTResourceManager();
1000 _PRIonAppXInitialized = _WinRTResourceManager.Initialize(resourcesAssembly.Location, reswFilename, out _PRIExceptionInfo);
1002 // Note that _PRIExceptionInfo might be null - this is OK.
1003 // In that case we will just throw the generic
1004 // MissingManifestResource_NoPRIresources exception.
1005 // See the implementation of GetString for more details.
1007 // We would like to be able to throw a MissingManifestResourceException here if PRI resources
1008 // could not be loaded for a recognized reason. However, the ResourceManager constructors
1009 // that call SetAppXConfiguration are not documented as throwing MissingManifestResourceException,
1010 // and since they are part of the portable profile, we cannot start throwing a new exception type
1011 // as that would break existing portable libraries. Hence we must save the exception information
1012 // now and throw the exception on the first call to GetString.
1013 catch (FileNotFoundException)
1015 // We will throw MissingManifestResource_NoPRIresources from GetString
1016 // when we see that _PRIonAppXInitialized is false.
1020 // ERROR_MRM_MAP_NOT_FOUND can be thrown by the call to ResourceManager.get_AllResourceMaps
1021 // in WindowsRuntimeResourceManager.Initialize.
1022 // In this case _PRIExceptionInfo is now null and we will just throw the generic
1023 // MissingManifestResource_NoPRIresources exception.
1024 // See the implementation of GetString for more details.
1025 if (e.HResult != HResults.ERROR_MRM_MAP_NOT_FOUND)
1026 throw; // Unexpected exception code. Bubble it up to the caller.
1029 if (!_PRIonAppXInitialized)
1031 _bUsingModernResourceManagement = false;
1033 // Allow all other exception types to bubble up to the caller.
1035 // Yes, this causes us to potentially throw exception types that are not documented.
1037 // Ultimately the tradeoff is the following:
1038 // -We could ignore unknown exceptions or rethrow them as inner exceptions
1039 // of exceptions that the ResourceManager class is already documented as throwing.
1040 // This would allow existing portable libraries to gracefully recover if they don't care
1041 // too much about the ResourceManager object they are using. However it could
1042 // mask potentially fatal errors that we are not aware of, such as a disk drive failing.
1045 // The alternative, which we chose, is to throw unknown exceptions. This may tear
1046 // down the process if the portable library and app don't expect this exception type.
1047 // On the other hand, this won't mask potentially fatal errors we don't know about.
1054 // resourcesAssembly == null should not happen but it can. See the comment on Assembly.GetCallingAssembly.
1055 // However for the sake of 100% backwards compatibility on Win7 and below, we must leave
1056 // _bUsingModernResourceManagement as false.
1057 #endif // FEATURE_APPX
1060 // Looks up a resource value for a particular name. Looks in the
1061 // current thread's CultureInfo, and if not found, all parent CultureInfos.
1062 // Returns null if the resource wasn't found.
1064 public virtual String GetString(String name)
1066 return GetString(name, (CultureInfo)null);
1069 // Looks up a resource value for a particular name. Looks in the
1070 // specified CultureInfo, and if not found, all parent CultureInfos.
1071 // Returns null if the resource wasn't found.
1073 public virtual String GetString(String name, CultureInfo culture)
1076 throw new ArgumentNullException(nameof(name));
1081 // If the caller explictily passed in a culture that was obtained by calling CultureInfo.CurrentUICulture,
1082 // null it out, so that we re-compute it. If we use modern resource lookup, we may end up getting a "better"
1083 // match, since CultureInfo objects can't represent all the different languages the AppX resource model supports.
1084 // For classic resources, this causes us to ignore the languages list and instead use the older Win32 behavior,
1085 // which is the design choice we've made. (See the call a little later to GetCurrentUICultureNoAppX()).
1086 if (Object.ReferenceEquals(culture, CultureInfo.CurrentUICulture))
1092 if (_bUsingModernResourceManagement)
1094 if (_PRIonAppXInitialized == false)
1096 // Always throw if we did not fully succeed in initializing the WinRT Resource Manager.
1098 if (_PRIExceptionInfo != null && _PRIExceptionInfo._PackageSimpleName != null && _PRIExceptionInfo._ResWFile != null)
1099 throw new MissingManifestResourceException(SR.Format(SR.MissingManifestResource_ResWFileNotLoaded, _PRIExceptionInfo._ResWFile, _PRIExceptionInfo._PackageSimpleName));
1101 throw new MissingManifestResourceException(SR.MissingManifestResource_NoPRIresources);
1104 // Throws WinRT hresults.
1105 return GetStringFromPRI(name,
1106 culture == null ? null : culture.Name,
1107 _neutralResourcesCulture.Name);
1110 #endif // FEATURE_APPX
1112 if (culture == null)
1114 // When running inside AppX we want to ignore the languages list when trying to come up with our CurrentUICulture.
1115 // This line behaves the same way as CultureInfo.CurrentUICulture would have in .NET 4
1116 culture = CultureInfo.CurrentUICulture;
1119 ResourceSet last = GetFirstResourceSet(culture);
1123 String value = last.GetString(name, _ignoreCase);
1129 // This is the CultureInfo hierarchy traversal code for resource
1130 // lookups, similar but necessarily orthogonal to the ResourceSet
1132 ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, true);
1133 foreach (CultureInfo currentCultureInfo in mgr)
1135 ResourceSet rs = InternalGetResourceSet(currentCultureInfo, true, true);
1141 String value = rs.GetString(name, _ignoreCase);
1144 // update last used ResourceSet
1145 if (_lastUsedResourceCache != null)
1147 lock (_lastUsedResourceCache)
1149 _lastUsedResourceCache.lastCultureName = currentCultureInfo.Name;
1150 _lastUsedResourceCache.lastResourceSet = rs;
1165 // Looks up a resource value for a particular name. Looks in the
1166 // current thread's CultureInfo, and if not found, all parent CultureInfos.
1167 // Returns null if the resource wasn't found.
1169 public virtual Object GetObject(String name)
1171 return GetObject(name, (CultureInfo)null, true);
1174 // Looks up a resource value for a particular name. Looks in the
1175 // specified CultureInfo, and if not found, all parent CultureInfos.
1176 // Returns null if the resource wasn't found.
1177 public virtual Object GetObject(String name, CultureInfo culture)
1179 return GetObject(name, culture, true);
1182 private Object GetObject(String name, CultureInfo culture, bool wrapUnmanagedMemStream)
1185 throw new ArgumentNullException(nameof(name));
1190 // If the caller explictily passed in a culture that was obtained by calling CultureInfo.CurrentUICulture,
1191 // null it out, so that we re-compute it based on the Win32 value and not the AppX language list value.
1192 // (See the call a little later to GetCurrentUICultureNoAppX()).
1193 if (Object.ReferenceEquals(culture, CultureInfo.CurrentUICulture))
1200 if (null == culture)
1202 // When running inside AppX we want to ignore the languages list when trying to come up with our CurrentUICulture.
1203 // This line behaves the same way as CultureInfo.CurrentUICulture would have in .NET 4
1204 culture = CultureInfo.GetCurrentUICultureNoAppX();
1207 ResourceSet last = GetFirstResourceSet(culture);
1210 Object value = last.GetObject(name, _ignoreCase);
1214 UnmanagedMemoryStream stream = value as UnmanagedMemoryStream;
1215 if (stream != null && wrapUnmanagedMemStream)
1216 return new UnmanagedMemoryStreamWrapper(stream);
1222 // This is the CultureInfo hierarchy traversal code for resource
1223 // lookups, similar but necessarily orthogonal to the ResourceSet
1225 ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, true);
1227 foreach (CultureInfo currentCultureInfo in mgr)
1229 // Note: Technically this method should be passed in a stack crawl mark that we then pass
1230 // to InternalGetResourceSet for ensuring we demand permissions to read your private resources
1231 // if you're reading resources from an assembly other than yourself. But, we must call our
1232 // three argument overload (without the stack crawl mark) for compatibility. After
1233 // consideration, we aren't worried about the security impact.
1234 ResourceSet rs = InternalGetResourceSet(currentCultureInfo, true, true);
1240 Object value = rs.GetObject(name, _ignoreCase);
1243 // update the last used ResourceSet
1244 if (_lastUsedResourceCache != null)
1246 lock (_lastUsedResourceCache)
1248 _lastUsedResourceCache.lastCultureName = currentCultureInfo.Name;
1249 _lastUsedResourceCache.lastResourceSet = rs;
1253 UnmanagedMemoryStream stream = value as UnmanagedMemoryStream;
1254 if (stream != null && wrapUnmanagedMemStream)
1255 return new UnmanagedMemoryStreamWrapper(stream);
1267 public UnmanagedMemoryStream GetStream(String name)
1269 return GetStream(name, (CultureInfo)null);
1272 public UnmanagedMemoryStream GetStream(String name, CultureInfo culture)
1274 Object obj = GetObject(name, culture, false);
1275 UnmanagedMemoryStream ums = obj as UnmanagedMemoryStream;
1276 if (ums == null && obj != null)
1277 throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotStream_Name, name));
1281 #if RESOURCE_SATELLITE_CONFIG
1282 // Internal helper method - gives an end user the ability to prevent
1283 // satellite assembly probes for certain cultures via a config file.
1284 private bool TryLookingForSatellite(CultureInfo lookForCulture)
1286 if (!_checkedConfigFile)
1290 if (!_checkedConfigFile)
1292 _checkedConfigFile = true;
1293 _installedSatelliteInfo = GetSatelliteAssembliesFromConfig();
1298 if (_installedSatelliteInfo == null)
1301 String[] installedSatellites = (String[])_installedSatelliteInfo[MainAssembly.FullName];
1303 if (installedSatellites == null)
1306 // The config file told us what satellites might be installed.
1307 int pos = Array.IndexOf(installedSatellites, lookForCulture.Name);
1312 // Note: There is one config file per appdomain. This is not
1313 // per-process nor per-assembly.
1314 private Hashtable GetSatelliteAssembliesFromConfig()
1318 #endif // RESOURCE_SATELLITE_CONFIG
1320 internal class ResourceManagerMediator
1322 private ResourceManager _rm;
1324 internal ResourceManagerMediator(ResourceManager rm)
1328 throw new ArgumentNullException(nameof(rm));
1333 // NEEDED ONLY BY FILE-BASED
1334 internal String ModuleDir
1336 get { return _rm.moduleDir; }
1339 // NEEDED BOTH BY FILE-BASED AND ASSEMBLY-BASED
1340 internal Type LocationInfo
1342 get { return _rm._locationInfo; }
1345 internal Type UserResourceSet
1347 get { return _rm._userResourceSet; }
1350 internal String BaseNameField
1352 get { return _rm.BaseNameField; }
1355 internal CultureInfo NeutralResourcesCulture
1357 get { return _rm._neutralResourcesCulture; }
1358 set { _rm._neutralResourcesCulture = value; }
1361 internal String GetResourceFileName(CultureInfo culture)
1363 return _rm.GetResourceFileName(culture);
1366 // NEEDED ONLY BY ASSEMBLY-BASED
1367 internal bool LookedForSatelliteContractVersion
1369 get { return _rm._lookedForSatelliteContractVersion; }
1370 set { _rm._lookedForSatelliteContractVersion = value; }
1373 internal Version SatelliteContractVersion
1375 get { return _rm._satelliteContractVersion; }
1376 set { _rm._satelliteContractVersion = value; }
1379 internal Version ObtainSatelliteContractVersion(Assembly a)
1381 return ResourceManager.GetSatelliteContractVersion(a);
1384 internal UltimateResourceFallbackLocation FallbackLoc
1386 get { return _rm.FallbackLocation; }
1387 set { _rm._fallbackLoc = value; }
1390 internal RuntimeAssembly CallingAssembly
1392 get { return _rm.m_callingAssembly; }
1395 internal RuntimeAssembly MainAssembly
1397 get { return (RuntimeAssembly)_rm.MainAssembly; }
1400 // this is weird because we have BaseNameField accessor above, but we're sticking
1401 // with it for compat.
1402 internal String BaseName
1404 get { return _rm.BaseName; }
1408 #if RESOURCE_SATELLITE_CONFIG
1409 internal bool TryLookingForSatellite(CultureInfo lookForCulture)
1411 return _rm.TryLookingForSatellite(lookForCulture);