using System;
using System.Collections;
using System.IO;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
namespace Python.Runtime
{
///
/// The AssemblyManager maintains information about loaded assemblies
/// namespaces and provides an interface for name-based type lookup.
///
internal class AssemblyManager
{
// modified from event handlers below, potentially triggered from different .NET threads
// therefore this should be a ConcurrentDictionary
static ConcurrentDictionary> namespaces;
//static Dictionary> generics;
static AssemblyLoadEventHandler lhandler;
static ResolveEventHandler rhandler;
// updated only under GIL?
static Dictionary probed;
// modified from event handlers below, potentially triggered from different .NET threads
static AssemblyList assemblies;
internal static List pypath;
private AssemblyManager()
{
}
//===================================================================
// Initialization performed on startup of the Python runtime. Here we
// scan all of the currently loaded assemblies to determine exported
// names, and register to be notified of new assembly loads.
//===================================================================
internal static void Initialize()
{
namespaces = new
ConcurrentDictionary>();
probed = new Dictionary(32);
//generics = new Dictionary>();
assemblies = new AssemblyList(16);
pypath = new List(16);
AppDomain domain = AppDomain.CurrentDomain;
lhandler = new AssemblyLoadEventHandler(AssemblyLoadHandler);
domain.AssemblyLoad += lhandler;
rhandler = new ResolveEventHandler(ResolveHandler);
domain.AssemblyResolve += rhandler;
Assembly[] items = domain.GetAssemblies();
foreach (var a in items)
{
try
{
ScanAssembly(a);
assemblies.Add(a);
}
catch (Exception ex)
{
Debug.WriteLine(string.Format("Error scanning assembly {0}. {1}", a, ex));
}
}
}
//===================================================================
// Cleanup resources upon shutdown of the Python runtime.
//===================================================================
internal static void Shutdown()
{
AppDomain domain = AppDomain.CurrentDomain;
domain.AssemblyLoad -= lhandler;
domain.AssemblyResolve -= rhandler;
}
//===================================================================
// Event handler for assembly load events. At the time the Python
// runtime loads, we scan the app domain to map the assemblies that
// are loaded at the time. We also have to register this event handler
// so that we can know about assemblies that get loaded after the
// Python runtime is initialized.
//===================================================================
static void AssemblyLoadHandler(Object ob, AssemblyLoadEventArgs args)
{
Assembly assembly = args.LoadedAssembly;
assemblies.Add(assembly);
ScanAssembly(assembly);
}
//===================================================================
// Event handler for assembly resolve events. This is needed because
// we augment the assembly search path with the PYTHONPATH when we
// load an assembly from Python. Because of that, we need to listen
// for failed loads, because they might be dependencies of something
// we loaded from Python which also needs to be found on PYTHONPATH.
//===================================================================
static Assembly ResolveHandler(Object ob, ResolveEventArgs args)
{
string name = args.Name.ToLower();
foreach (Assembly a in assemblies)
{
string full = a.FullName.ToLower();
if (full.StartsWith(name))
{
return a;
}
}
return LoadAssemblyPath(args.Name);
}
//===================================================================
// We __really__ want to avoid using Python objects or APIs when
// probing for assemblies to load, since our ResolveHandler may be
// called in contexts where we don't have the Python GIL and can't
// even safely try to get it without risking a deadlock ;(
//
// To work around that, we update a managed copy of sys.path (which
// is the main thing we care about) when UpdatePath is called. The
// import hook calls this whenever it knows its about to use the
// assembly manager, which lets us keep up with changes to sys.path
// in a relatively lightweight and low-overhead way.
//===================================================================
internal static void UpdatePath()
{
IntPtr list = Runtime.PySys_GetObject("path");
int count = Runtime.PyList_Size(list);
if (count != pypath.Count)
{
pypath.Clear();
probed.Clear();
for (int i = 0; i < count; i++)
{
IntPtr item = Runtime.PyList_GetItem(list, i);
string path = Runtime.GetManagedString(item);
if (path != null)
{
pypath.Add(path);
}
}
}
}
//===================================================================
// Given an assembly name, try to find this assembly file using the
// PYTHONPATH. If not found, return null to indicate implicit load
// using standard load semantics (app base directory then GAC, etc.)
//===================================================================
public static string FindAssembly(string name)
{
char sep = Path.DirectorySeparatorChar;
string path;
string temp;
for (int i = 0; i < pypath.Count; i++)
{
string head = pypath[i];
if (head == null || head.Length == 0)
{
path = name;
}
else
{
path = head + sep + name;
}
temp = path + ".dll";
if (File.Exists(temp))
{
return temp;
}
temp = path + ".exe";
if (File.Exists(temp))
{
return temp;
}
}
return null;
}
//===================================================================
// Loads an assembly from the application directory or the GAC
// given a simple assembly name. Returns the assembly if loaded.
//===================================================================
public static Assembly LoadAssembly(string name)
{
Assembly assembly = null;
try
{
assembly = Assembly.Load(name);
}
catch (System.Exception e)
{
//if (!(e is System.IO.FileNotFoundException)) {
// throw;
//}
}
return assembly;
}
//===================================================================
// Loads an assembly using an augmented search path (the python path).
//===================================================================
public static Assembly LoadAssemblyPath(string name)
{
string path = FindAssembly(name);
Assembly assembly = null;
if (path != null)
{
try
{
assembly = Assembly.LoadFrom(path);
}
catch
{
}
}
return assembly;
}
///
/// Loads an assembly using full path.
///
///
///
public static Assembly LoadAssemblyFullPath(string name)
{
Assembly assembly = null;
if (Path.IsPathRooted(name))
{
if (!Path.HasExtension(name))
name = name + ".dll";
if (File.Exists(name))
{
try
{
assembly = Assembly.LoadFrom(name);
}
catch
{
}
}
}
return assembly;
}
//===================================================================
// Returns an assembly that's already been loaded
//===================================================================
public static Assembly FindLoadedAssembly(string name)
{
foreach (Assembly a in assemblies)
{
if (a.GetName().Name == name)
{
return a;
}
}
return null;
}
//===================================================================
// Given a qualified name of the form A.B.C.D, attempt to load
// an assembly named after each of A.B.C.D, A.B.C, A.B, A. This
// will only actually probe for the assembly once for each unique
// namespace. Returns true if any assemblies were loaded.
// TODO item 3 "* Deprecate implicit loading of assemblies":
// Set the fromFile flag if the name of the loaded assembly matches
// the fully qualified name that was requested if the framework
// actually loads an assembly.
// Call ONLY for namespaces that HAVE NOT been cached yet.
//===================================================================
public static bool LoadImplicit(string name, bool warn = true)
{
string[] names = name.Split('.');
bool loaded = false;
string s = "";
Assembly lastAssembly = null;
HashSet assembliesSet = null;
for (int i = 0; i < names.Length; i++)
{
s = (i == 0) ? names[0] : s + "." + names[i];
if (!probed.ContainsKey(s))
{
if (assembliesSet == null)
{
assembliesSet = new HashSet(AppDomain.CurrentDomain.GetAssemblies());
}
Assembly a = FindLoadedAssembly(s);
if (a == null)
{
a = LoadAssemblyPath(s);
}
if (a == null)
{
a = LoadAssembly(s);
}
if (a != null && !assembliesSet.Contains(a))
{
loaded = true;
lastAssembly = a;
}
probed[s] = 1;
}
}
// Deprecation warning
if (warn && loaded)
{
string deprWarning = String.Format(
"\nThe module was found, but not in a referenced namespace.\n" +
"Implicit loading is deprecated. Please use clr.AddReference(\"{0}\").",
Path.GetFileNameWithoutExtension(lastAssembly.Location));
Exceptions.deprecation(deprWarning);
}
return loaded;
}
//===================================================================
// Scans an assembly for exported namespaces, adding them to the
// mapping of valid namespaces. Note that for a given namespace
// a.b.c.d, each of a, a.b, a.b.c and a.b.c.d are considered to
// be valid namespaces (to better match Python import semantics).
//===================================================================
internal static void ScanAssembly(Assembly assembly)
{
// A couple of things we want to do here: first, we want to
// gather a list of all of the namespaces contributed to by
// the assembly.
Type[] types = assembly.GetTypes();
for (int i = 0; i < types.Length; i++)
{
Type t = types[i];
string ns = t.Namespace ?? "";
if (!namespaces.ContainsKey(ns))
{
string[] names = ns.Split('.');
string s = "";
for (int n = 0; n < names.Length; n++)
{
s = (n == 0) ? names[0] : s + "." + names[n];
namespaces.TryAdd(s, new ConcurrentDictionary());
}
}
if (ns != null)
{
namespaces[ns].TryAdd(assembly, String.Empty);
}
if (ns != null && t.IsGenericTypeDefinition)
{
GenericUtil.Register(t);
}
}
}
public static AssemblyName[] ListAssemblies()
{
List names = new List(assemblies.Count);
foreach (Assembly assembly in assemblies)
{
names.Add(assembly.GetName());
}
return names.ToArray();
}
//===================================================================
// Returns true if the given qualified name matches a namespace
// exported by an assembly loaded in the current app domain.
//===================================================================
public static bool IsValidNamespace(string name)
{
return !String.IsNullOrEmpty(name) && namespaces.ContainsKey(name);
}
//===================================================================
// Returns list of assemblies that declare types in a given namespace
//===================================================================
public static IEnumerable GetAssemblies(string nsname)
{
if (!namespaces.ContainsKey(nsname))
return new List();
return namespaces[nsname].Keys;
}
//===================================================================
// Returns the current list of valid names for the input namespace.
//===================================================================
public static List GetNames(string nsname)
{
//Dictionary seen = new Dictionary();
List names = new List(8);
List g = GenericUtil.GetGenericBaseNames(nsname);
if (g != null)
{
foreach (string n in g)
{
names.Add(n);
}
}
if (namespaces.ContainsKey(nsname))
{
foreach (Assembly a in namespaces[nsname].Keys)
{
Type[] types = a.GetTypes();
for (int i = 0; i < types.Length; i++)
{
Type t = types[i];
if ((t.Namespace ?? "") == nsname)
{
names.Add(t.Name);
}
}
}
int nslen = nsname.Length;
foreach (string key in namespaces.Keys)
{
if (key.Length > nslen && key.StartsWith(nsname))
{
//string tail = key.Substring(nslen);
if (key.IndexOf('.') == -1)
{
names.Add(key);
}
}
}
}
return names;
}
//===================================================================
// Returns the System.Type object for a given qualified name,
// looking in the currently loaded assemblies for the named
// type. Returns null if the named type cannot be found.
//===================================================================
public static Type LookupType(string qname)
{
foreach (Assembly assembly in assemblies)
{
Type type = assembly.GetType(qname);
if (type != null)
{
return type;
}
}
return null;
}
///
/// Wrapper around List for thread safe access
///
private class AssemblyList : IEnumerable{
private readonly List _list;
private readonly ReaderWriterLockSlim _lock;
public AssemblyList(int capacity) {
_list = new List(capacity);
_lock = new ReaderWriterLockSlim();
}
public int Count
{
get
{
_lock.EnterReadLock();
try {
return _list.Count;
}
finally {
_lock.ExitReadLock();
}
}
}
public void Add(Assembly assembly) {
_lock.EnterWriteLock();
try
{
_list.Add(assembly);
}
finally
{
_lock.ExitWriteLock();
}
}
public IEnumerator GetEnumerator()
{
return ((IEnumerable) this).GetEnumerator();
}
///
/// Enumerator wrapping around 's enumerator.
/// Acquires and releases a read lock on during enumeration
///
private class Enumerator : IEnumerator
{
private readonly AssemblyList _assemblyList;
private readonly IEnumerator _listEnumerator;
public Enumerator(AssemblyList assemblyList)
{
_assemblyList = assemblyList;
_assemblyList._lock.EnterReadLock();
_listEnumerator = _assemblyList._list.GetEnumerator();
}
public void Dispose()
{
_listEnumerator.Dispose();
_assemblyList._lock.ExitReadLock();
}
public bool MoveNext()
{
return _listEnumerator.MoveNext();
}
public void Reset()
{
_listEnumerator.Reset();
}
public Assembly Current { get { return _listEnumerator.Current; } }
object IEnumerator.Current { get { return Current; } }
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(this);
}
}
}
}