/// <summary> /// Registers all the components in the specified assembly by looking for loadable classes /// and adding them to the catalog. /// </summary> /// <param name="assembly"> /// The assembly to register. /// </param> /// <param name="throwOnError"> /// true to throw an exception if there are errors with registering the components; /// false to skip any errors. /// </param> public void RegisterAssembly(Assembly assembly, bool throwOnError = true) { lock (_lock) { if (_cachedAssemblies.Add(assembly.FullName)) { foreach (LoadableClassAttributeBase attr in assembly.GetCustomAttributes(typeof(LoadableClassAttributeBase))) { MethodInfo getter = null; ConstructorInfo ctor = null; MethodInfo create = null; bool requireEnvironment = false; if (attr.InstanceType != typeof(void) && !TryGetIniters(attr.InstanceType, attr.LoaderType, attr.CtorTypes, out getter, out ctor, out create, out requireEnvironment)) { if (throwOnError) { throw Contracts.Except( $"Can't instantiate loadable class '{attr.InstanceType.Name}' with name '{attr.LoadNames[0]}'"); } Contracts.Assert(getter == null && ctor == null && create == null); } var info = new LoadableClassInfo(attr, getter, ctor, create, requireEnvironment); AddClass(info, attr.LoadNames, throwOnError); } } } }
private void ScanForEntryPoints(LoadableClassInfo info) { var type = info.LoaderType; // Scan for entry points. foreach (var methodInfo in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) { var attr = methodInfo.GetCustomAttributes(typeof(TlcModule.EntryPointAttribute), false).FirstOrDefault() as TlcModule.EntryPointAttribute; if (attr == null) { continue; } var entryPointInfo = new EntryPointInfo(methodInfo, attr, methodInfo.GetCustomAttributes(typeof(ObsoleteAttribute), false).FirstOrDefault() as ObsoleteAttribute); _entryPoints.Add(entryPointInfo); if (_entryPointMap.ContainsKey(entryPointInfo.Name)) { // Duplicate entry point name. We need to show a warning here. // REVIEW: we will be able to do this once catalog becomes a part of env. continue; } _entryPointMap[entryPointInfo.Name] = entryPointInfo; } // Scan for components. // First scan ourself, and then all nested types, for component info. ScanForComponents(type); foreach (var nestedType in type.GetTypeInfo().GetNestedTypes()) { ScanForComponents(nestedType); } }
private static bool TryCreateInstance <TRes>(IHostEnvironment env, Type signatureType, out TRes result, string name, string options, params object[] extra) where TRes : class { Contracts.CheckValue(env, nameof(env)); env.Check(signatureType.BaseType == typeof(MulticastDelegate)); env.CheckValueOrNull(name); string nameLower = (name ?? "").ToLowerInvariant().Trim(); LoadableClassInfo info = FindClassCore(new LoadableClassInfo.Key(nameLower, signatureType)); if (info == null) { result = null; return(false); } if (!typeof(TRes).IsAssignableFrom(info.Type)) { throw env.Except("Loadable class '{0}' does not derive from '{1}'", name, typeof(TRes).FullName); } int carg = Utils.Size(extra); if (info.ExtraArgCount != carg) { throw env.Except( "Wrong number of extra parameters for loadable class '{0}', need '{1}', given '{2}'", name, info.ExtraArgCount, carg); } if (info.ArgType == null) { if (!string.IsNullOrEmpty(options)) { throw env.Except("Loadable class '{0}' doesn't support settings", name); } result = (TRes)info.CreateInstance(env, null, extra); return(true); } object args = info.CreateArguments(); if (args == null) { throw Contracts.Except("Can't instantiate arguments object '{0}' for '{1}'", info.ArgType.Name, name); } ParseArguments(env, args, options, name); result = (TRes)info.CreateInstance(env, args, extra); return(true); }
private void AddClass(LoadableClassInfo info, string[] loadNames, bool throwOnError) { _classes.Add(info); bool isEntryPoint = false; foreach (var sigType in info.SignatureTypes) { _signatures[sigType] = true; foreach (var name in loadNames) { string nameCi = name.ToLowerInvariant(); var key = new LoadableClassInfo.Key(nameCi, sigType); if (_classesByKey.TryGetValue(key, out var infoCur)) { if (throwOnError) { throw Contracts.Except($"ComponentCatalog cannot map name '{name}' and SignatureType '{sigType}' to {info.Type.Name}, already mapped to {infoCur.Type.Name}."); } } else { _classesByKey.Add(key, info); } } if (sigType == typeof(SignatureEntryPointModule)) { isEntryPoint = true; } } if (isEntryPoint) { ScanForEntryPoints(info); } }
private static void AddClass(LoadableClassInfo info, string[] loadNames) { _classes.Enqueue(info); foreach (var sigType in info.SignatureTypes) { _signatures.TryAdd(sigType, true); foreach (var name in loadNames) { string nameCi = name.ToLowerInvariant(); var key = new LoadableClassInfo.Key(nameCi, sigType); if (!_classesByKey.TryAdd(key, info)) { var infoCur = _classesByKey[key]; // REVIEW: Fix this message to reflect the signature.... Console.Error.WriteLine( "CacheClassesFromAssembly: can't map name {0} to {1}, already mapped to {2}", name, info.Type.Name, infoCur.Type.Name); } } } }
/// <summary> /// This loads assemblies that are in our "root" directory (where this assembly is) and caches /// information for the loadable classes in loaded assemblies. /// </summary> private static void CacheLoadedAssemblies() { // The target assembly is the one containing LoadableClassAttributeBase. If an assembly doesn't reference // the target, then we don't want to scan its assembly attributes (there's no point in doing so). var target = typeof(LoadableClassAttributeBase).Assembly; lock (_lock) { if (_assemblyQueue == null) { // Create the loaded assembly queue and dictionary, set up the AssemblyLoad / AssemblyResolve // event handlers and populate the queue / dictionary with all assemblies that are currently loaded. Contracts.Assert(_assemblyQueue == null); Contracts.Assert(_loadedAssemblies == null); _assemblyQueue = new ConcurrentQueue <Assembly>(); _loadedAssemblies = new ConcurrentDictionary <string, Assembly>(); AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad; AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve; foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) { // Ignore dynamic assemblies. if (a.IsDynamic) { continue; } _assemblyQueue.Enqueue(a); if (!_loadedAssemblies.TryAdd(a.FullName, a)) { // Duplicate loading. Console.Error.WriteLine("Duplicate loaded assembly '{0}'", a.FullName); } } // Load all assemblies in our directory. var moduleName = typeof(ComponentCatalog).Module.FullyQualifiedName; // If were are loaded in the context of SQL CLR then the FullyQualifiedName and Name properties are set to // string "<Unknown>" and we skip scanning current directory. if (moduleName != "<Unknown>") { string dir = Path.GetDirectoryName(moduleName); LoadAssembliesInDir(dir, true); dir = Path.Combine(dir, "AutoLoad"); LoadAssembliesInDir(dir, true); } } Contracts.AssertValue(_assemblyQueue); Contracts.AssertValue(_loadedAssemblies); Assembly assembly; while (_assemblyQueue.TryDequeue(out assembly)) { if (!_cachedAssemblies.Add(assembly.FullName)) { continue; } if (assembly != target) { bool found = false; var targetName = target.GetName(); foreach (var name in assembly.GetReferencedAssemblies()) { if (name.Name == targetName.Name) { found = true; break; } } if (!found) { continue; } } int added = 0; foreach (LoadableClassAttributeBase attr in assembly.GetCustomAttributes(typeof(LoadableClassAttributeBase))) { MethodInfo getter = null; ConstructorInfo ctor = null; MethodInfo create = null; bool requireEnvironment = false; if (attr.InstanceType != typeof(void) && !TryGetIniters(attr.InstanceType, attr.LoaderType, attr.CtorTypes, out getter, out ctor, out create, out requireEnvironment)) { Console.Error.WriteLine( "CacheClassesFromAssembly: can't instantiate loadable class {0} with name {1}", attr.InstanceType.Name, attr.LoadNames[0]); Contracts.Assert(getter == null && ctor == null && create == null); } var info = new LoadableClassInfo(attr, getter, ctor, create, requireEnvironment); AddClass(info, attr.LoadNames); added++; } } } }