Skip to content

Commit e71a0ef

Browse files
committed
Import hook as a module, take 2
1 parent 73958ed commit e71a0ef

File tree

3 files changed

+72
-27
lines changed

3 files changed

+72
-27
lines changed

src/runtime/importhook.cs

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import sys
1919
class DotNetLoader(importlib.abc.Loader):
2020
2121
def __init__(self):
22-
super(DotNetLoader, self).__init__()
22+
super().__init__()
2323
2424
@classmethod
2525
def exec_module(klass, mod):
@@ -29,21 +29,19 @@ def exec_module(klass, mod):
2929
@classmethod
3030
def create_module(klass, spec):
3131
import clr
32-
return clr._LoadClrModule(spec)
32+
return clr._load_clr_module(spec)
3333
3434
class DotNetFinder(importlib.abc.MetaPathFinder):
3535
3636
def __init__(self):
37-
super(DotNetFinder, self).__init__()
37+
super().__init__()
3838
3939
@classmethod
4040
def find_spec(klass, fullname, paths=None, target=None):
4141
import clr
42-
if (hasattr(clr, '_availableNamespaces') and fullname in clr._availableNamespaces):
42+
if clr._availableNamespaces and fullname in clr._availableNamespaces:
4343
return importlib.machinery.ModuleSpec(fullname, DotNetLoader(), is_package=True)
4444
return None
45-
46-
sys.meta_path.append(DotNetFinder())
4745
";
4846
const string availableNsKey = "_availableNamespaces";
4947

@@ -69,7 +67,7 @@ internal static unsafe void Initialize()
6967

7068
// Add/create the MetaPathLoader
7169
SetupNamespaceTracking();
72-
PythonEngine.Exec(LoaderCode);
70+
SetupImportHook();
7371
}
7472

7573

@@ -114,13 +112,66 @@ internal static void RestoreRuntimeData(RuntimeDataStorage storage)
114112
SetupNamespaceTracking();
115113
}
116114

115+
static void SetupImportHook()
116+
{
117+
// Create the import hook module
118+
var import_hook_module_def = ModuleDefOffset.AllocModuleDef("clr.loader");
119+
var import_hook_module = Runtime.PyModule_Create2(import_hook_module_def, 3);
120+
121+
// Run the python code to create the module's classes.
122+
var mod_dict = Runtime.PyModule_GetDict(new BorrowedReference(import_hook_module));
123+
var builtins = Runtime.PyEval_GetBuiltins();
124+
var exec = Runtime.PyDict_GetItemString(builtins, "exec");
125+
using var args = NewReference.DangerousFromPointer(Runtime.PyTuple_New(2));
126+
127+
var codeStr = Runtime.PyString_FromString(LoaderCode);
128+
Runtime.PyTuple_SetItem(args.DangerousGetAddress(), 0, codeStr);
129+
// PyTuple_SetItem steals a reference, mod_dict is borrowed.
130+
Runtime.XIncref(mod_dict.DangerousGetAddress());
131+
Runtime.PyTuple_SetItem(args.DangerousGetAddress(), 1, mod_dict.DangerousGetAddress());
132+
Runtime.PyObject_Call(exec.DangerousGetAddress(), args.DangerousGetAddress(), IntPtr.Zero);
133+
134+
var loader = Runtime.PyDict_GetItemString(mod_dict, "DotNetLoader").DangerousGetAddressOrNull();
135+
Runtime.XIncref(loader);
136+
137+
// Add the classes to the module
138+
// PyModule_AddObject steals a reference only on success
139+
if (Runtime.PyModule_AddObject(import_hook_module, "DotNetLoader", loader) != 0)
140+
{
141+
Runtime.XDecref(loader);
142+
throw new PythonException();
143+
}
144+
145+
var finder = Runtime.PyDict_GetItemString(mod_dict, "DotNetFinder").DangerousGetAddressOrNull();
146+
Runtime.XIncref(finder);
147+
if (Runtime.PyModule_AddObject(import_hook_module, "DotNetFinder", finder) != 0)
148+
{
149+
Runtime.XDecref(finder);
150+
throw new PythonException();
151+
}
152+
153+
// Set as a sub-module of clr.
154+
Runtime.XIncref(import_hook_module);
155+
if(Runtime.PyModule_AddObject(py_clr_module, "loader", import_hook_module) != 0)
156+
{
157+
Runtime.XDecref(import_hook_module);
158+
throw new PythonException();
159+
}
160+
161+
// Finally, add the hook to the meta path
162+
var finder_inst = Runtime.PyDict_GetItemString(mod_dict, "finder_inst").DangerousGetAddressOrNull();
163+
Runtime.XIncref(finder);
164+
var metapath = Runtime.PySys_GetObject("meta_path");
165+
Runtime.PyList_Append(metapath, finder);
166+
}
167+
117168
/// <summary>
118169
/// Sets up the tracking of loaded namespaces. This makes available to
119170
/// Python, as a Python object, the loaded namespaces. The set of loaded
120171
/// namespaces is used during the import to verify if we can import a
121172
/// CLR assembly as a module or not. The set is stored on the clr module.
122173
/// </summary>
123-
static void SetupNamespaceTracking ()
174+
static void SetupNamespaceTracking()
124175
{
125176
var newset = Runtime.PySet_New(new BorrowedReference(IntPtr.Zero));
126177
try
@@ -130,7 +181,7 @@ static void SetupNamespaceTracking ()
130181
var pyNs = Runtime.PyString_FromString(ns);
131182
try
132183
{
133-
if(Runtime.PySet_Add(newset, new BorrowedReference(pyNs)) != 0)
184+
if (Runtime.PySet_Add(newset, new BorrowedReference(pyNs)) != 0)
134185
{
135186
throw new PythonException();
136187
}
@@ -141,7 +192,7 @@ static void SetupNamespaceTracking ()
141192
}
142193
}
143194

144-
if(Runtime.PyDict_SetItemString(root.dict, availableNsKey, newset.DangerousGetAddress()) != 0)
195+
if (Runtime.PyDict_SetItemString(root.dict, availableNsKey, newset.DangerousGetAddress()) != 0)
145196
{
146197
throw new PythonException();
147198
}
@@ -152,7 +203,7 @@ static void SetupNamespaceTracking ()
152203
}
153204

154205
AssemblyManager.namespaceAdded += OnNamespaceAdded;
155-
PythonEngine.AddShutdownHandler(()=>AssemblyManager.namespaceAdded -= OnNamespaceAdded);
206+
PythonEngine.AddShutdownHandler(() => AssemblyManager.namespaceAdded -= OnNamespaceAdded);
156207
}
157208

158209
/// <summary>
@@ -162,27 +213,21 @@ static void SetupNamespaceTracking ()
162213
static void TeardownNameSpaceTracking()
163214
{
164215
AssemblyManager.namespaceAdded -= OnNamespaceAdded;
165-
// If the C# runtime isn't loaded, then there is no namespaces available
166-
if ((Runtime.PyDict_DelItemString(new BorrowedReference(root.dict), availableNsKey) != 0) &&
167-
(Exceptions.ExceptionMatches(Exceptions.KeyError)))
168-
{
169-
// Trying to remove a key that's not in the dictionary
170-
// raises an error. We don't care about it.
171-
Runtime.PyErr_Clear();
172-
}
216+
// If the C# runtime isn't loaded, then there are no namespaces available
217+
Runtime.PyDict_SetItemString(root.dict, availableNsKey, Runtime.PyNone);
173218
}
174219

175-
static void OnNamespaceAdded (string name)
220+
static void OnNamespaceAdded(string name)
176221
{
177-
using(Py.GIL())
222+
using (Py.GIL())
178223
{
179224
var pyNs = Runtime.PyString_FromString(name);
180225
try
181226
{
182227
var nsSet = Runtime.PyDict_GetItemString(new BorrowedReference(root.dict), availableNsKey);
183-
if (!nsSet.IsNull)
228+
if (!nsSet.IsNull || nsSet.DangerousGetAddress() != Runtime.PyNone)
184229
{
185-
if(Runtime.PySet_Add(nsSet, new BorrowedReference(pyNs)) != 0)
230+
if (Runtime.PySet_Add(nsSet, new BorrowedReference(pyNs)) != 0)
186231
{
187232
throw new PythonException();
188233
}
@@ -225,7 +270,7 @@ public static unsafe NewReference GetCLRModule()
225270
/// <summary>
226271
/// The hook to import a CLR module into Python
227272
/// </summary>
228-
public static ModuleObject __import__(string modname)
273+
public static ModuleObject Import(string modname)
229274
{
230275
// Traverse the qualified module name to get the named module.
231276
// Note that if

src/runtime/moduleobject.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -593,12 +593,12 @@ public static int _AtExit()
593593
/// <returns>A new reference to the imported module, as a PyObject.</returns>
594594
[ModuleFunction]
595595
[ForbidPythonThreads]
596-
public static PyObject _LoadClrModule(PyObject spec)
596+
public static PyObject _load_clr_module(PyObject spec)
597597
{
598598
ModuleObject mod = null;
599599
using (var modname = spec.GetAttr("name"))
600600
{
601-
mod = ImportHook.__import__(modname.ToString());
601+
mod = ImportHook.Import(modname.ToString());
602602
}
603603
// We can't return directly a ModuleObject, because the tpHandle is
604604
// not set, but we can return a PyObject.

src/runtime/native/TypeOffset.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ static void ValidateRequiredOffsetsPresent(PropertyInfo[] offsetProperties)
160160
"getPreload",
161161
"Initialize",
162162
"ListAssemblies",
163-
"_LoadClrModule",
163+
"_load_clr_module",
164164
"Release",
165165
"Reset",
166166
"set_SuppressDocs",

0 commit comments

Comments
 (0)