using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Python.Runtime
{
///
/// Implements a Python type that provides access to CLR namespaces. The
/// type behaves like a Python module, and can contain other sub-modules.
///
internal class ModuleObject : ExtensionType
{
private Dictionary cache;
internal string moduleName;
internal IntPtr dict;
protected string _namespace;
public ModuleObject(string name)
{
if (name == string.Empty)
{
throw new ArgumentException("Name must not be empty!");
}
moduleName = name;
cache = new Dictionary();
_namespace = name;
// Use the filename from any of the assemblies just so there's something for
// anything that expects __file__ to be set.
var filename = "unknown";
var docstring = "Namespace containing types from the following assemblies:\n\n";
foreach (Assembly a in AssemblyManager.GetAssemblies(name))
{
if (!a.IsDynamic && a.Location != null)
{
filename = a.Location;
}
docstring += "- " + a.FullName + "\n";
}
dict = Runtime.PyDict_New();
IntPtr pyname = Runtime.PyString_FromString(moduleName);
IntPtr pyfilename = Runtime.PyString_FromString(filename);
IntPtr pydocstring = Runtime.PyString_FromString(docstring);
IntPtr pycls = TypeManager.GetTypeHandle(GetType());
Runtime.PyDict_SetItemString(dict, "__name__", pyname);
Runtime.PyDict_SetItemString(dict, "__file__", pyfilename);
Runtime.PyDict_SetItemString(dict, "__doc__", pydocstring);
Runtime.PyDict_SetItemString(dict, "__class__", pycls);
Runtime.XDecref(pyname);
Runtime.XDecref(pyfilename);
Runtime.XDecref(pydocstring);
Marshal.WriteIntPtr(pyHandle, ObjectOffset.DictOffset(pyHandle), dict);
InitializeModuleMembers();
}
///
/// Returns a ClassBase object representing a type that appears in
/// this module's namespace or a ModuleObject representing a child
/// namespace (or null if the name is not found). This method does
/// not increment the Python refcount of the returned object.
///
public ManagedType GetAttribute(string name, bool guess)
{
ManagedType cached = null;
cache.TryGetValue(name, out cached);
if (cached != null)
{
return cached;
}
ModuleObject m;
ClassBase c;
Type type;
//if (AssemblyManager.IsValidNamespace(name))
//{
// IntPtr py_mod_name = Runtime.PyString_FromString(name);
// IntPtr modules = Runtime.PyImport_GetModuleDict();
// IntPtr module = Runtime.PyDict_GetItem(modules, py_mod_name);
// if (module != IntPtr.Zero)
// return (ManagedType)this;
// return null;
//}
string qname = _namespace == string.Empty
? name
: _namespace + "." + name;
// If the fully-qualified name of the requested attribute is
// a namespace exported by a currently loaded assembly, return
// a new ModuleObject representing that namespace.
if (AssemblyManager.IsValidNamespace(qname))
{
m = new ModuleObject(qname);
StoreAttribute(name, m);
return m;
}
// Look for a type in the current namespace. Note that this
// includes types, delegates, enums, interfaces and structs.
// Only public namespace members are exposed to Python.
type = AssemblyManager.LookupType(qname);
if (type != null)
{
if (!type.IsPublic)
{
return null;
}
c = ClassManager.GetClass(type);
StoreAttribute(name, c);
return c;
}
// This is a little repetitive, but it ensures that the right
// thing happens with implicit assembly loading at a reasonable
// cost. Ask the AssemblyManager to do implicit loading for each
// of the steps in the qualified name, then try it again.
bool ignore = name.StartsWith("__");
if (AssemblyManager.LoadImplicit(qname, !ignore))
{
if (AssemblyManager.IsValidNamespace(qname))
{
m = new ModuleObject(qname);
StoreAttribute(name, m);
return m;
}
type = AssemblyManager.LookupType(qname);
if (type != null)
{
if (!type.IsPublic)
{
return null;
}
c = ClassManager.GetClass(type);
StoreAttribute(name, c);
return c;
}
}
// We didn't find the name, so we may need to see if there is a
// generic type with this base name. If so, we'll go ahead and
// return it. Note that we store the mapping of the unmangled
// name to generic type - it is technically possible that some
// future assembly load could contribute a non-generic type to
// the current namespace with the given basename, but unlikely
// enough to complicate the implementation for now.
if (guess)
{
string gname = GenericUtil.GenericNameForBaseName(_namespace, name);
if (gname != null)
{
ManagedType o = GetAttribute(gname, false);
if (o != null)
{
StoreAttribute(name, o);
return o;
}
}
}
return null;
}
///
/// Stores an attribute in the instance dict for future lookups.
///
private void StoreAttribute(string name, ManagedType ob)
{
Runtime.PyDict_SetItemString(dict, name, ob.pyHandle);
cache[name] = ob;
}
///
/// Preloads all currently-known names for the module namespace. This
/// can be called multiple times, to add names from assemblies that
/// may have been loaded since the last call to the method.
///
public void LoadNames()
{
ManagedType m = null;
foreach (string name in AssemblyManager.GetNames(_namespace))
{
cache.TryGetValue(name, out m);
if (m != null)
{
continue;
}
IntPtr attr = Runtime.PyDict_GetItemString(dict, name);
// If __dict__ has already set a custom property, skip it.
if (attr != IntPtr.Zero)
{
continue;
}
GetAttribute(name, true);
}
}
///
/// Initialize module level functions and attributes
///
internal void InitializeModuleMembers()
{
Type funcmarker = typeof(ModuleFunctionAttribute);
Type propmarker = typeof(ModulePropertyAttribute);
Type ftmarker = typeof(ForbidPythonThreadsAttribute);
Type type = GetType();
BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
while (type != null)
{
MethodInfo[] methods = type.GetMethods(flags);
foreach (MethodInfo method in methods)
{
object[] attrs = method.GetCustomAttributes(funcmarker, false);
object[] forbid = method.GetCustomAttributes(ftmarker, false);
bool allow_threads = forbid.Length == 0;
if (attrs.Length > 0)
{
string name = method.Name;
var mi = new MethodInfo[1];
mi[0] = method;
var m = new ModuleFunctionObject(type, name, mi, allow_threads);
StoreAttribute(name, m);
}
}
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
object[] attrs = property.GetCustomAttributes(propmarker, false);
if (attrs.Length > 0)
{
string name = property.Name;
var p = new ModulePropertyObject(property);
StoreAttribute(name, p);
}
}
type = type.BaseType;
}
}
///
/// ModuleObject __getattribute__ implementation. Module attributes
/// are always either classes or sub-modules representing subordinate
/// namespaces. CLR modules implement a lazy pattern - the sub-modules
/// and classes are created when accessed and cached for future use.
///
public static IntPtr tp_getattro(IntPtr ob, IntPtr key)
{
var self = (ModuleObject)GetManagedObject(ob);
if (!Runtime.PyString_Check(key))
{
Exceptions.SetError(Exceptions.TypeError, "string expected");
return IntPtr.Zero;
}
IntPtr op = Runtime.PyDict_GetItem(self.dict, key);
if (op != IntPtr.Zero)
{
Runtime.XIncref(op);
return op;
}
string name = Runtime.GetManagedString(key);
if (name == "__dict__")
{
Runtime.XIncref(self.dict);
return self.dict;
}
ManagedType attr = self.GetAttribute(name, true);
if (attr == null)
{
Exceptions.SetError(Exceptions.AttributeError, name);
return IntPtr.Zero;
}
Runtime.XIncref(attr.pyHandle);
return attr.pyHandle;
}
///
/// ModuleObject __repr__ implementation.
///
public static IntPtr tp_repr(IntPtr ob)
{
var self = (ModuleObject)GetManagedObject(ob);
return Runtime.PyString_FromString($"");
}
}
///
/// The CLR module is the root handler used by the magic import hook
/// to import assemblies. It has a fixed module name "clr" and doesn't
/// provide a namespace.
///
internal class CLRModule : ModuleObject
{
protected static bool hacked = false;
protected static bool interactive_preload = true;
internal static bool preload;
// XXX Test performance of new features //
internal static bool _SuppressDocs = false;
internal static bool _SuppressOverloads = false;
public CLRModule() : base("clr")
{
_namespace = string.Empty;
// This hackery is required in order to allow a plain Python to
// import the managed runtime via the CLR bootstrapper module.
// The standard Python machinery in control at the time of the
// import requires the module to pass PyModule_Check. :(
if (!hacked)
{
IntPtr type = tpHandle;
IntPtr mro = Marshal.ReadIntPtr(type, TypeOffset.tp_mro);
IntPtr ext = Runtime.ExtendTuple(mro, Runtime.PyModuleType);
Marshal.WriteIntPtr(type, TypeOffset.tp_mro, ext);
Runtime.XDecref(mro);
hacked = true;
}
}
public static void Reset()
{
hacked = false;
interactive_preload = true;
preload = false;
// XXX Test performance of new features //
_SuppressDocs = false;
_SuppressOverloads = false;
}
///
/// The initializing of the preload hook has to happen as late as
/// possible since sys.ps1 is created after the CLR module is
/// created.
///
internal void InitializePreload()
{
if (interactive_preload)
{
interactive_preload = false;
if (Runtime.PySys_GetObject("ps1") != IntPtr.Zero)
{
preload = true;
}
else
{
Exceptions.Clear();
preload = false;
}
}
}
[ModuleFunction]
public static bool getPreload()
{
return preload;
}
[ModuleFunction]
public static void setPreload(bool preloadFlag)
{
preload = preloadFlag;
}
//[ModuleProperty]
public static bool SuppressDocs
{
get { return _SuppressDocs; }
set { _SuppressDocs = value; }
}
//[ModuleProperty]
public static bool SuppressOverloads
{
get { return _SuppressOverloads; }
set { _SuppressOverloads = value; }
}
[ModuleFunction]
[ForbidPythonThreads]
public static Assembly AddReference(string name)
{
AssemblyManager.UpdatePath();
Assembly assembly = null;
assembly = AssemblyManager.FindLoadedAssembly(name);
if (assembly == null)
{
assembly = AssemblyManager.LoadAssemblyPath(name);
}
if (assembly == null)
{
assembly = AssemblyManager.LoadAssembly(name);
}
if (assembly == null)
{
assembly = AssemblyManager.LoadAssemblyFullPath(name);
}
if (System.IO.File.Exists(name))
{
var zone = System.Security.Policy.Zone.CreateFromUrl(name);
if (zone.SecurityZone != System.Security.SecurityZone.MyComputer)
{
throw new Exception($"File is blocked (NTFS Security)");
}
}
if (assembly == null)
{
throw new FileNotFoundException($"Unable to find assembly '{name}'.");
}
return assembly;
}
///
/// Get a Type instance for a class object.
/// clr.GetClrType(IComparable) gives you the Type for IComparable,
/// that you can e.g. perform reflection on. Similar to typeof(IComparable) in C#
/// or clr.GetClrType(IComparable) in IronPython.
///
///
///
/// The Type object
[ModuleFunction]
[ForbidPythonThreads]
public static Type GetClrType(Type type)
{
return type;
}
[ModuleFunction]
[ForbidPythonThreads]
public static string FindAssembly(string name)
{
AssemblyManager.UpdatePath();
return AssemblyManager.FindAssembly(name);
}
[ModuleFunction]
public static string[] ListAssemblies(bool verbose)
{
AssemblyName[] assnames = AssemblyManager.ListAssemblies();
var names = new string[assnames.Length];
for (var i = 0; i < assnames.Length; i++)
{
if (verbose)
{
names[i] = assnames[i].FullName;
}
else
{
names[i] = assnames[i].Name;
}
}
return names;
}
[ModuleFunction]
public static int _AtExit()
{
return Runtime.AtExit();
}
}
}