diff --git a/.travis.yml b/.travis.yml index 6355ce73d..5581f1329 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: python python: - 2.6 - 2.7 + - 3.2 + - 3.4 before_install: - sudo apt-get install software-properties-common - sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu/ trusty main universe" @@ -12,7 +14,8 @@ before_install: - yes | sudo certmgr -ssl -m https://nugetgallery.blob.core.windows.net - yes | sudo certmgr -ssl -m https://nuget.org install: + - pip install six - python setup.py build_ext --inplace script: - - export PYTHONPATH=`pwd` - - ./npython src/tests/runtests.py + - export PYTHONPATH=`pwd`:$PYTHONPATH + - python src/tests/runtests.py diff --git a/Python.Runtime.dll.config b/Python.Runtime.dll.config index 11b4fb0fe..e9821a8a9 100644 --- a/Python.Runtime.dll.config +++ b/Python.Runtime.dll.config @@ -14,10 +14,16 @@ For more information read: + + + + + + diff --git a/Python.Test.csproj b/Python.Test.csproj new file mode 100644 index 000000000..11591e091 --- /dev/null +++ b/Python.Test.csproj @@ -0,0 +1,190 @@ + + + + Debug + AnyCPU + {6F401A34-273B-450F-9A4C-13550BE0767B} + Library + false + Python.Test + Python.Test + OnBuildSuccess + + + + + 3.5 + v4.0 + + + + true + full + false + .\bin\Debug\ + DEBUG;TRACE + + + pdbonly + true + .\bin\Release\ + TRACE + + + true + bin\EmbeddingTest\ + DEBUG;TRACE + full + AnyCPU + + + true + bin\UnitTests\ + DEBUG;TRACE + full + AnyCPU + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + true + false + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + false + false + false + + + true + bin\x86\EmbeddingTest\ + DEBUG;TRACE + full + x86 + false + true + true + + + true + bin\x86\UnitTests\ + DEBUG;TRACE + full + x86 + true + true + + + true + bin\DebugMono_x86\ + DEBUG;TRACE + full + AnyCPU + true + true + + + true + bin\x86\DebugMono_x86\ + DEBUG;TRACE + full + x86 + true + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + true + false + false + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + false + + + true + bin\x64\EmbeddingTest\ + DEBUG;TRACE + full + x64 + false + true + true + + + true + bin\x64\UnitTests\ + DEBUG;TRACE + full + x64 + true + true + + + true + bin\x64\DebugMono_x86\ + DEBUG;TRACE + full + x64 + true + false + false + + + + + + + + + + + + + + + + + + + + + + + + 3.5 + + + + + + {097B4AC0-74E9-4C58-BCF8-C69746EC8271} + Python.Runtime + + + + + + + + copy "$(TargetPath)" "$(SolutionDir)" +copy "$(TargetDir)*.pdb" "$(SolutionDir)" + + + \ No newline at end of file diff --git a/README.md b/README.md index 8b88ac0e9..f6196e5d9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,63 @@ pythonnet ========= -Python for .NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR) and provides a powerful application scripting tool for .NET developers. +Python for .NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR) and provides a powerful application scripting tool for .NET developers. It allows Python code to interact with the CLR, and may also be used to embed Python into a .NET application. -[![Build Status](https://travis-ci.org/pythonnet/pythonnet.png?branch=develop)](https://travis-ci.org/pythonnet/pythonnet) +[![Build Status](https://travis-ci.org/pythonnet/pythonnet.png?branch=master)](https://travis-ci.org/pythonnet/pythonnet) -[![Build status](https://ci.appveyor.com/api/projects/status/65riiu1hvgaxsbwb)](https://ci.appveyor.com/project/davidanthoff/pythonnet) +[![Build status](https://ci.appveyor.com/api/projects/status/c8k0miljb3n1c7be/branch/master)](https://ci.appveyor.com/project/TonyRoberts/pythonnet-480xs) + +**Calling .NET code from Python** + +Python for .NET allows CLR namespaces to be treated essentially as Python packages. + +```python + import clr + from System import String + from System.Collections import * +``` +To load an assembly, use the "AddReference" function in the "clr" module: + +```python + import clr + clr.AddReference("System.Windows.Forms") + from System.Windows.Forms import Form +``` + +**Embedding Python in .NET** + ++ All calls to python should be inside a "using (Py.GIL()) {/* Your code here */}" block. ++ Import python modules using dynamic mod = Py.Import("mod"), then you can call functions as normal, eg mod.func(args). ++ Use mod.func(args, Py.kw("keywordargname", keywordargvalue)) to apply keyword arguments. ++ All python objects should be declared as 'dynamic' type. ++ Mathematical operations involving python and literal/managed types must have the python object first, eg np.pi*2 works, 2*np.pi doesn't + +EG: +```csharp +static void Main(string[] args) +{ + using (Py.GIL()) { + dynamic np = Py.Import("numpy"); + dynamic sin = np.sin; + Console.WriteLine(np.cos(np.pi*2)); + Console.WriteLine(sin(5)); + double c = np.cos(5) + sin(5); + Console.WriteLine(c); + dynamic a = np.array(new List { 1, 2, 3 }); + dynamic b = np.array(new List { 6, 5, 4 }, Py.kw("dtype", np.int32)); + Console.WriteLine(a.dtype); + Console.WriteLine(b.dtype); + Console.WriteLine(a * b); + Console.ReadKey(); + } +} +``` +outputs: +``` +1.0 +-0.958924274663 +-0.6752620892 +float64 +int32 +[ 6. 10. 12.] +``` diff --git a/appveyor.yml b/appveyor.yml index 18f9761c0..d7d46a7c0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,6 +9,10 @@ environment: - pythonurl: http://www.python.org/ftp/python/2.7.6/python-2.7.6.msi - pythonurl: http://www.python.org/ftp/python/2.6.6/python-2.6.6.msi - pythonurl: http://www.python.org/ftp/python/2.6.6/python-2.6.6.amd64.msi + - pythonurl: http://www.python.org/ftp/python/3.2.3/python-3.2.3.msi + - pythonurl: http://www.python.org/ftp/python/3.2.3/python-3.2.3.amd64.msi + - pythonurl: http://www.python.org/ftp/python/3.4.2/python-3.4.2.msi + - pythonurl: http://www.python.org/ftp/python/3.4.2/python-3.4.2.amd64.msi install: - ps: (new-object net.webclient).DownloadFile($env:pythonurl, 'C:\python.msi') @@ -19,6 +23,7 @@ install: - set PATH=C:\Python;%PATH% - C:\Python\python.exe c:\get-pip.py - C:\Python\Scripts\pip.exe install wheel + - C:\Python\Scripts\pip.exe install six build_script: - C:\python\python.exe setup.py bdist_wheel @@ -28,4 +33,3 @@ test_script: - mkdir c:\testdir - ps: copy-item (gci -path build -re -include Python.Test.dll)[0].FullName c:\testdir - c:\python\python.exe src\tests\runtests.py - - c:\python\scripts\npython.exe src\tests\runtests.py diff --git a/setup.py b/setup.py index dc7e9620b..4c2817cc7 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,6 @@ """ from setuptools import setup, Extension from distutils.command.build_ext import build_ext -from distutils.command.build_scripts import build_scripts from distutils.command.install_lib import install_lib from distutils.command.install_data import install_data from distutils.sysconfig import get_config_var @@ -12,19 +11,21 @@ from subprocess import Popen, CalledProcessError, PIPE, check_call from glob import glob import fnmatch -import shutil import sys import os -CONFIG = "Release" # Release or Debug +CONFIG = "Release" # Release or Debug DEVTOOLS = "MsDev" if sys.platform == "win32" else "Mono" -VERBOSITY = "minimal" # quiet, minimal, normal, detailed, diagnostic +VERBOSITY = "minimal" # quiet, minimal, normal, detailed, diagnostic PLATFORM = "x64" if architecture()[0] == "64bit" else "x86" def _find_msbuild_tool(tool="msbuild.exe", use_windows_sdk=False): """Return full path to one of the microsoft build tools""" - import _winreg + try: + import _winreg + except ImportError: + import winreg as _winreg if use_windows_sdk: value_name = "InstallationFolder" @@ -79,13 +80,11 @@ def _find_msbuild_tool(tool="msbuild.exe", use_windows_sdk=False): _xbuild = "\"%s\"" % _find_msbuild_tool("msbuild.exe") _defines_sep = ";" _config = "%sWin" % CONFIG - _npython_exe = "nPython.exe" elif DEVTOOLS == "Mono": _xbuild = "xbuild" _defines_sep = "," _config = "%sMono" % CONFIG - _npython_exe = "npython" else: raise NotImplementedError("DevTools %s not supported (use MsDev or Mono)" % DEVTOOLS) @@ -108,14 +107,41 @@ def build_extension(self, ext): if not os.path.exists(dest_dir): os.makedirs(dest_dir) + # Up to Python 3.2 sys.maxunicode is used to determine the size of Py_UNICODE + # but from 3.3 onwards Py_UNICODE is a typedef of wchar_t. + if sys.version_info[:2] <= (3, 2): + unicode_width = 2 if sys.maxunicode < 0x10FFFF else 4 + else: + import ctypes + unicode_width = ctypes.sizeof(ctypes.c_wchar) + defines = [ "PYTHON%d%s" % (sys.version_info[:2]), - "UCS2" if sys.maxunicode < 0x10FFFF else "UCS4", + "UCS%d" % unicode_width, ] if CONFIG == "Debug": defines.extend(["DEBUG", "TRACE"]) + if sys.platform != "win32" and DEVTOOLS == "Mono": + if sys.platform == "darwin": + defines.append("MONO_OSX") + else: + defines.append("MONO_LINUX") + + # Check if --enable-shared was set when Python was built + enable_shared = get_config_var("Py_ENABLE_SHARED") + if enable_shared == 0: + defines.append("PYTHON_WITHOUT_ENABLE_SHARED") + + if hasattr(sys, "abiflags"): + if "d" in sys.abiflags: + defines.append("PYTHON_WITH_PYDEBUG") + if "m" in sys.abiflags: + defines.append("PYTHON_WITH_PYMALLOC") + if "u" in sys.abiflags: + defines.append("PYTHON_WITH_WIDE_UNICODE") + cmd = [ _xbuild, "pythonnet.sln", @@ -138,7 +164,6 @@ def build_extension(self, ext): if DEVTOOLS == "Mono": self._build_monoclr(ext) - def _get_manifest(self, build_dir): if DEVTOOLS == "MsDev" and sys.version_info[:2] > (2,5): mt = _find_msbuild_tool("mt.exe", use_windows_sdk=True) @@ -148,7 +173,6 @@ def _get_manifest(self, build_dir): check_call(" ".join(cmd), shell=False) return manifest - def _build_monoclr(self, ext): mono_libs = _check_output("pkg-config --libs mono-2", shell=True) mono_cflags = _check_output("pkg-config --cflags mono-2", shell=True) @@ -168,45 +192,6 @@ def _build_monoclr(self, ext): build_ext.build_extension(self, clr_ext) - # build the clr python executable - sources = [ - "src/monoclr/pynetinit.c", - "src/monoclr/python.c", - ] - - macros = ext.define_macros[:] - for undef in ext.undef_macros: - macros.append((undef,)) - - objects = self.compiler.compile(sources, - output_dir=self.build_temp, - macros=macros, - include_dirs=ext.include_dirs, - debug=self.debug, - extra_postargs=cflags.split(" "), - depends=ext.depends) - - output_dir = os.path.dirname(self.get_ext_fullpath(ext.name)) - py_libs = get_config_var("BLDLIBRARY") - libs += " " + py_libs - - # Include the directories python's shared libs were installed to. This - # is case python was built with --enable-shared as then npython will need - # to be able to find libpythonX.X.so. - runtime_library_dirs = (get_config_var("DESTDIRS") or "").split(" ") - if ext.runtime_library_dirs: - runtime_library_dirs.extend(ext.runtime_library_dirs) - - self.compiler.link_executable(objects, - _npython_exe, - output_dir=output_dir, - libraries=self.get_libraries(ext), - library_dirs=ext.library_dirs, - runtime_library_dirs=runtime_library_dirs, - extra_postargs=libs.split(" "), - debug=self.debug) - - def _install_packages(self): """install packages using nuget""" nuget = os.path.join("tools", "nuget", "nuget.exe") @@ -255,25 +240,6 @@ def run(self): self.data_files[i] = dest, data_files[1] return install_data.run(self) - - -class PythonNET_BuildScripts(build_scripts): - - def run(self): - build_scripts.finalize_options(self) - - # fixup scripts to look in the build_ext output folder - if self.scripts: - build_ext = self.get_finalized_command("build_ext") - output_dir = os.path.dirname(build_ext.get_ext_fullpath("clr")) - scripts = [] - for script in self.scripts: - if os.path.exists(os.path.join(output_dir, script)): - script = os.path.join(output_dir, script) - scripts.append(script) - self.scripts = scripts - - return build_scripts.run(self) def _check_output(*popenargs, **kwargs): @@ -289,6 +255,8 @@ def _check_output(*popenargs, **kwargs): if cmd is None: cmd = popenargs[0] raise CalledProcessError(retcode, cmd, output=output) + if sys.version_info[0] > 2: + return output.decode("ascii") return output @@ -328,11 +296,9 @@ def _check_output(*popenargs, **kwargs): "{build_lib}/Python.Runtime.dll", "Python.Runtime.dll.config"]), ], - scripts=[_npython_exe], zip_safe=False, cmdclass={ "build_ext" : PythonNET_BuildExt, - "build_scripts" : PythonNET_BuildScripts, "install_lib" : PythonNET_InstallLib, "install_data": PythonNET_InstallData, } diff --git a/src/clrmodule/ClrModule.cs b/src/clrmodule/ClrModule.cs index 3347d55a9..be4da01a7 100644 --- a/src/clrmodule/ClrModule.cs +++ b/src/clrmodule/ClrModule.cs @@ -30,7 +30,7 @@ // If DEBUG_PRINT is defined in the Build Properties, a few System.Console.WriteLine // calls are made to indicate what's going on during the load... //============================================================================ - +using System; // ReSharper disable CheckNamespace // ReSharper disable InconsistentNaming @@ -38,10 +38,14 @@ public class clrModule // ReSharper restore InconsistentNaming // ReSharper restore CheckNamespace { - - [RGiesecke.DllExport.DllExport("initclr", System.Runtime.InteropServices.CallingConvention.StdCall)] // ReSharper disable InconsistentNaming +#if (PYTHON32 || PYTHON33 || PYTHON34) + [RGiesecke.DllExport.DllExport("PyInit_clr", System.Runtime.InteropServices.CallingConvention.StdCall)] + public static IntPtr PyInit_clr() +#else + [RGiesecke.DllExport.DllExport("initclr", System.Runtime.InteropServices.CallingConvention.StdCall)] public static void initclr() +#endif // ReSharper restore InconsistentNaming { #if DEBUG_PRINT @@ -76,7 +80,7 @@ public static void initclr() System.Console.WriteLine("Success!"); #endif } - catch (System.IO.FileNotFoundException) + catch (System.IO.IOException) { try { @@ -103,13 +107,22 @@ public static void initclr() #if DEBUG_PRINT System.Console.WriteLine("Could not load Python.Runtime, so sad."); #endif +#if (PYTHON32 || PYTHON33 || PYTHON34) + return IntPtr.Zero; +#else return; +#endif } } // Once here, we've successfully loaded SOME version of Python.Runtime // So now we get the PythonEngine and execute the InitExt method on it. var pythonEngineType = pythonRuntime.GetType("Python.Runtime.PythonEngine"); + +#if (PYTHON32 || PYTHON33 || PYTHON34) + return (IntPtr)pythonEngineType.InvokeMember("InitExt", System.Reflection.BindingFlags.InvokeMethod, null, null, null); +#else pythonEngineType.InvokeMember("InitExt", System.Reflection.BindingFlags.InvokeMethod, null, null, null); +#endif } } diff --git a/src/clrmodule/clrmodule.csproj b/src/clrmodule/clrmodule.csproj index 92dc2a945..f6d1a41b5 100644 --- a/src/clrmodule/clrmodule.csproj +++ b/src/clrmodule/clrmodule.csproj @@ -1,126 +1,131 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {86E834DE-1139-4511-96CC-69636A56E7AC} - Library - Properties - clrmodule - clrmodule - v4.0 - 512 - ..\..\ - $(SolutionDir) - true - - - true - bin\x86\DebugMono\ - DEBUG;TRACE - full - x86 - prompt - true - true - false - - - true - bin\x64\DebugMono\ - DEBUG;TRACE - full - x64 - prompt - true - true - false - - - bin\x86\ReleaseMono\ - - true - pdbonly - x86 - prompt - true - true - false - - - bin\x64\ReleaseMono\ - - true - pdbonly - x64 - prompt - true - true - false - - - true - bin\x86\DebugWin\ - TRACE;DEBUG;DEBUG_PRINT - full - x86 - prompt - true - false - false - - - true - bin\x64\DebugWin\ - DEBUG;TRACE - full - x64 - prompt - true - true - false - - - bin\x86\ReleaseWin\ - - true - pdbonly - x86 - prompt - true - true - false - - - bin\x64\ReleaseWin\ - - true - pdbonly - x64 - prompt - true - true - false - - - - ..\..\packages\UnmanagedExports.1.2.6\lib\net\RGiesecke.DllExport.Metadata.dll - False - - - - - - - - - - - - - - - - \ No newline at end of file + + + + Debug + x86 + 8.0.30703 + 2.0 + {86E834DE-1139-4511-96CC-69636A56E7AC} + Library + Properties + clrmodule + clrmodule + v4.0 + 512 + ..\..\ + $(SolutionDir) + true + + + true + bin\x86\DebugMono\ + DEBUG;TRACE + full + x86 + prompt + true + true + false + + + true + bin\x64\DebugMono\ + DEBUG;TRACE + full + x64 + prompt + true + true + false + + + bin\x86\ReleaseMono\ + + true + pdbonly + x86 + prompt + true + true + false + + + bin\x64\ReleaseMono\ + + true + pdbonly + x64 + prompt + true + true + false + + + true + bin\x86\DebugWin\ + TRACE;DEBUG;DEBUG_PRINT + full + x86 + prompt + true + false + false + + + true + bin\x64\DebugWin\ + DEBUG;TRACE + full + x64 + prompt + true + true + false + + + bin\x86\ReleaseWin\ + + true + pdbonly + x86 + prompt + true + true + false + + + bin\x64\ReleaseWin\ + + true + pdbonly + x64 + prompt + true + true + false + + + + ..\..\packages\UnmanagedExports.1.2.6\lib\net\RGiesecke.DllExport.Metadata.dll + False + + + + + + + + + + + + + $(TargetPath) + $(TargetDir)$(TargetName).pdb + + + + + + + diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 14f97f5fb..e4a9750b8 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -172,7 +172,12 @@ + + $(TargetPath) + $(TargetDir)$(TargetName).pdb + - + + diff --git a/src/monoclr/clrmod.c b/src/monoclr/clrmod.c index 8b809b28f..c6de71eeb 100644 --- a/src/monoclr/clrmod.c +++ b/src/monoclr/clrmod.c @@ -25,21 +25,54 @@ PyDoc_STRVAR(clr_module_doc, static PyNet_Args *pn_args; char** environ = NULL; +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef clrdef = { + PyModuleDef_HEAD_INIT, + "clr", /* m_name */ + clr_module_doc, /* m_doc */ + -1, /* m_size */ + clr_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; +#endif + +static PyObject *_initclr() { + PyObject *m; + + /* Create the module and add the functions */ +#if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&clrdef); +#else + m = Py_InitModule3("clr", clr_methods, clr_module_doc); +#endif + if (m == NULL) + return NULL; + PyModule_AddObject(m, "facade", Py_True); + Py_INCREF(Py_True); + + pn_args = PyNet_Init(1); + if (pn_args->error) { + return NULL; + } + + if (NULL != pn_args->module) + return pn_args->module; + + return m; +} + +#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC +PyInit_clr(void) { + return _initclr(); +} +#else PyMODINIT_FUNC -initclr(void) -{ - PyObject *m; - - /* Create the module and add the functions */ - m = Py_InitModule3("clr", clr_methods, clr_module_doc); - if (m == NULL) - return; - PyModule_AddObject(m, "facade", Py_True); - Py_INCREF(Py_True); - - pn_args = PyNet_Init(1); - if (pn_args->error) { - return; - } +initclr(void) { + _initclr(); } +#endif diff --git a/src/monoclr/clrpython.c b/src/monoclr/clrpython.c deleted file mode 100644 index 5fddabf22..000000000 --- a/src/monoclr/clrpython.c +++ /dev/null @@ -1,29 +0,0 @@ -// ========================================================================== -// This software is subject to the provisions of the Zope Public License, -// Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -// FOR A PARTICULAR PURPOSE. -// ========================================================================== -// -// Example how to integrate Python, PythonNet and Mono into a C application -// It provides a command prompt equal to PythonNet's console but using a -// different path. -// -// Author: Christian Heimes -// - -#include "pynetclr.h" - -int main(int argc, char **argv) { - PyNet_Args *pn_args; - pn_args = PyNet_Init(0); - if (pn_args->error) { - exit(1); - } - int rc = Py_Main(argc, argv); - PyNet_Finalize(pn_args); - exit(rc); -} - diff --git a/src/monoclr/pynetclr.h b/src/monoclr/pynetclr.h index c97db10cb..3a6a60c9c 100644 --- a/src/monoclr/pynetclr.h +++ b/src/monoclr/pynetclr.h @@ -32,6 +32,7 @@ typedef struct { char *error; char *init_name; char *shutdown_name; + PyObject* module; } PyNet_Args; PyNet_Args* PyNet_Init(int); diff --git a/src/monoclr/pynetinit.c b/src/monoclr/pynetinit.c index eaa1d9c8b..f6487802c 100644 --- a/src/monoclr/pynetinit.c +++ b/src/monoclr/pynetinit.c @@ -10,11 +10,16 @@ // Author: Christian Heimes #include "pynetclr.h" +#include "stdlib.h" #ifndef _WIN32 #include "dirent.h" +#include "dlfcn.h" +#include "libgen.h" +#include "alloca.h" #endif + // initialize Mono and PythonNet PyNet_Args* PyNet_Init(int ext) { PyNet_Args *pn_args; @@ -22,6 +27,7 @@ PyNet_Args* PyNet_Init(int ext) { pn_args->pr_file = PR_ASSEMBLY; pn_args->error = NULL; pn_args->shutdown = NULL; + pn_args->module = NULL; if (ext == 0) { pn_args->init_name = "Python.Runtime:Initialize()"; @@ -91,8 +97,29 @@ void main_thread_handler (gpointer user_data) { MonoImage *pr_image; MonoClass *pythonengine; MonoObject *exception = NULL; + MonoObject *init_result; #ifndef _WIN32 + // Get the filename of the python shared object and set + // LD_LIBRARY_PATH so Mono can find it. + Dl_info dlinfo = {0}; + if (0 != dladdr(&Py_Initialize, &dlinfo)) { + char* fname = alloca(strlen(dlinfo.dli_fname) + 1); + strcpy(fname, dlinfo.dli_fname); + char* py_libdir = dirname(fname); + char* ld_library_path = getenv("LD_LIBRARY_PATH"); + if (NULL == ld_library_path) { + setenv("LD_LIBRARY_PATH", py_libdir, 1); + } else { + char* new_ld_library_path = alloca(strlen(py_libdir) + + strlen(ld_library_path) + + 2); + strcpy(new_ld_library_path, py_libdir); + strcat(new_ld_library_path, ":"); + strcat(new_ld_library_path, ld_library_path); + setenv("LD_LIBRARY_PATH", py_libdir, 1); + } + } //get python path system variable PyObject* syspath = PySys_GetObject("path"); @@ -102,11 +129,25 @@ void main_thread_handler (gpointer user_data) { int ii = 0; for (ii = 0; ii < PyList_Size(syspath); ++ii) { +#if PY_MAJOR_VERSION > 2 + Py_ssize_t wlen; + wchar_t *wstr = PyUnicode_AsWideCharString(PyList_GetItem(syspath, ii), &wlen); + char* pydir = (char*)malloc(wlen + 1); + size_t mblen = wcstombs(pydir, wstr, wlen + 1); + if (mblen > wlen) + pydir[wlen] = '\0'; + PyMem_Free(wstr); +#else const char* pydir = PyString_AsString(PyList_GetItem(syspath, ii)); +#endif char* curdir = (char*) malloc(1024); strncpy(curdir, strlen(pydir) > 0 ? pydir : ".", 1024); strncat(curdir, slash, 1024); +#if PY_MAJOR_VERSION > 2 + free(pydir); +#endif + //look in this directory for the pn_args->pr_file DIR* dirp = opendir(curdir); if (dirp != NULL) { @@ -170,11 +211,17 @@ void main_thread_handler (gpointer user_data) { return; } - mono_runtime_invoke(init, NULL, NULL, &exception); + init_result = mono_runtime_invoke(init, NULL, NULL, &exception); if (exception) { pn_args->error = PyNet_ExceptionToString(exception); return; } + +#if PY_MAJOR_VERSION >= 3 + if (NULL != init_result) + pn_args->module = *(PyObject**)mono_object_unbox(init_result); +#endif + } // Get string from a Mono exception diff --git a/src/monoclr/python.c b/src/monoclr/python.c deleted file mode 100644 index aa340491f..000000000 --- a/src/monoclr/python.c +++ /dev/null @@ -1,22 +0,0 @@ -// ========================================================================== -// This software is subject to the provisions of the Zope Public License, -// Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -// FOR A PARTICULAR PURPOSE. -// ========================================================================== -// -// python.c provides a python executable with is dynamically linked agaist -// libpython2.x.so. For example Ubuntu's python executables aren't linked -// against libpython :( -// -// Author: Christian Heimes -// - -#include - -int main(int argc, char **argv) { - return Py_Main(argc, argv); -} - diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index f5bf3378a..80f911734 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -1,190 +1,202 @@ - - - - Debug - x86 - {097B4AC0-74E9-4C58-BCF8-C69746EC8271} - Library - false - Python.Runtime - Python.Runtime - ..\..\ - $(SolutionDir) - - - bin\x86\ReleaseMono\ - PYTHON27, UCS4 - true - true - pdbonly - x86 - false - true - - - bin\x64\ReleaseMono\ - PYTHON27, UCS4 - true - true - pdbonly - x64 - false - true - - - bin\x86\ReleaseWin\ - PYTHON27, UCS2 - true - true - pdbonly - x86 - false - true - - - bin\x64\ReleaseWin\ - PYTHON27, UCS2 - true - true - pdbonly - x64 - false - true - - - true - bin\x86\DebugMono\ - TRACE;DEBUG;PYTHON27,UCS4 - true - false - full - x86 - false - false - false - - - true - bin\x64\DebugMono\ - TRACE;DEBUG;PYTHON27,UCS4 - true - false - full - x64 - - - true - bin\x86\DebugWin\ - TRACE;DEBUG;PYTHON27,UCS2 - true - false - full - x86 - false - false - false - - - true - bin\x64\DebugWin\ - TRACE;DEBUG;PYTHON27,UCS2 - true - false - full - x64 - - - - - - False - ..\..\packages\MonoGAC\Mono.Posix\4.0.0.0__0738eb9f132ed756\Mono.Posix.dll - - - - - - - False - ..\..\packages\MonoGAC\Mono.Posix\4.0.0.0__0738eb9f132ed756\Mono.Posix.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Debug + x86 + {097B4AC0-74E9-4C58-BCF8-C69746EC8271} + Library + false + Python.Runtime + Python.Runtime + ..\..\ + $(SolutionDir) + + + bin\x86\ReleaseMono\ + PYTHON27, UCS4 + true + true + pdbonly + x86 + false + true + PYTHON27,UCS2 + + + bin\x64\ReleaseMono\ + PYTHON27, UCS4 + true + true + pdbonly + x64 + false + true + + + bin\x86\ReleaseWin\ + PYTHON27, UCS2 + true + true + pdbonly + x86 + false + true + + + bin\x64\ReleaseWin\ + PYTHON27, UCS2 + true + true + pdbonly + x64 + false + true + + + true + bin\x86\DebugMono\ + TRACE;DEBUG;PYTHON27,UCS4 + true + false + full + x86 + false + false + false + + + true + bin\x64\DebugMono\ + TRACE;DEBUG;PYTHON27,UCS4 + true + false + full + x64 + + + true + bin\x86\DebugWin\ + TRACE;DEBUG;PYTHON27,UCS2 + true + false + full + x86 + false + false + false + + + true + bin\x64\DebugWin\ + TRACE;DEBUG;PYTHON27,UCS2 + true + false + full + x64 + + + + + + False + ..\..\packages\MonoGAC\Mono.Posix\4.0.0.0__0738eb9f132ed756\Mono.Posix.dll + + + + + + + False + ..\..\packages\MonoGAC\Mono.Posix\4.0.0.0__0738eb9f132ed756\Mono.Posix.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + clr.py + + + + + + + + $(TargetPath) + $(TargetDir)$(TargetName).pdb + + + + + + diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index 583b5c945..35badb71a 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -282,7 +282,7 @@ public static bool LoadImplicit(string name, bool warn=true) { // be valid namespaces (to better match Python import semantics). //=================================================================== - static void ScanAssembly(Assembly assembly) { + 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 @@ -291,8 +291,8 @@ static void ScanAssembly(Assembly assembly) { Type[] types = assembly.GetTypes(); for (int i = 0; i < types.Length; i++) { Type t = types[i]; - string ns = t.Namespace; - if ((ns != null) && (!namespaces.ContainsKey(ns))) { + string ns = t.Namespace ?? ""; + if (!namespaces.ContainsKey(ns)) { string[] names = ns.Split('.'); string s = ""; for (int n = 0; n < names.Length; n++) { @@ -336,6 +336,16 @@ public static bool IsValidNamespace(string name) { return 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. @@ -357,7 +367,7 @@ public static List GetNames(string nsname) { Type[] types = a.GetTypes(); for (int i = 0; i < types.Length; i++) { Type t = types[i]; - if (t.Namespace == nsname) { + if ((t.Namespace ?? "") == nsname) { names.Add(t.Name); } } diff --git a/src/runtime/buildclrmodule.bat b/src/runtime/buildclrmodule.bat index 125ff9090..549902d7f 100644 --- a/src/runtime/buildclrmodule.bat +++ b/src/runtime/buildclrmodule.bat @@ -1,36 +1,36 @@ -:: Call with buildclrmodule.bat - -@echo off - -set TARGET_PLATFORM=%1 -set INPUT_DIRECTORY=%~2 -set INPUT_PATH="%INPUT_DIRECTORY%\clrmodule.il" -set OUTPUT_PATH=%3 - -if %TARGET_PLATFORM%==x86 goto SETUP32 -if %TARGET_PLATFORM%==x64 goto SETUP64 -goto ERROR_BAD_PLATFORM - -:SETUP32 -set INCLUDE_PATH="%INPUT_DIRECTORY%\x86" -goto BUILD_CLR_MODULE - -:SETUP64 -set INCLUDE_PATH="%INPUT_DIRECTORY%\x64" -set ILASM_EXTRA_ARGS=/pe64 /x64 -goto BUILD_CLR_MODULE - -:ERROR_BAD_PLATFORM -echo Unknown target platform: %TARGET_PLATFORM% -exit /b 1 - -:ERROR_MISSING_INPUT -echo Can't find input file: %INPUT_PATH% -exit /b 1 - -:BUILD_CLR_MODULE -if not exist %INPUT_PATH% goto ERROR_MISSING_INPUT -%windir%\Microsoft.NET\Framework\v4.0.30319\ilasm /nologo /quiet /dll %ILASM_EXTRA_ARGS% /include=%INCLUDE_PATH% /output=%OUTPUT_PATH% %INPUT_PATH% - -::: 2.0 or 3.5 -:::%windir%\Microsoft.NET\Framework\v2.0.50727\ilasm /nologo /quiet /dll %ILASM_EXTRA_ARGS% /include=%INCLUDE_PATH% /output=%OUTPUT_PATH% %INPUT_PATH% +:: Call with buildclrmodule.bat + +@echo off + +set TARGET_PLATFORM=%1 +set INPUT_DIRECTORY=%~2 +set INPUT_PATH="%INPUT_DIRECTORY%\clrmodule.il" +set OUTPUT_PATH=%3 + +if %TARGET_PLATFORM%==x86 goto SETUP32 +if %TARGET_PLATFORM%==x64 goto SETUP64 +goto ERROR_BAD_PLATFORM + +:SETUP32 +set INCLUDE_PATH="%INPUT_DIRECTORY%\x86" +goto BUILD_CLR_MODULE + +:SETUP64 +set INCLUDE_PATH="%INPUT_DIRECTORY%\x64" +set ILASM_EXTRA_ARGS=/pe64 /x64 +goto BUILD_CLR_MODULE + +:ERROR_BAD_PLATFORM +echo Unknown target platform: %TARGET_PLATFORM% +exit /b 1 + +:ERROR_MISSING_INPUT +echo Can't find input file: %INPUT_PATH% +exit /b 1 + +:BUILD_CLR_MODULE +if not exist %INPUT_PATH% goto ERROR_MISSING_INPUT +%windir%\Microsoft.NET\Framework\v4.0.30319\ilasm /nologo /quiet /dll %ILASM_EXTRA_ARGS% /include=%INCLUDE_PATH% /output=%OUTPUT_PATH% %INPUT_PATH% + +::: 2.0 or 3.5 +:::%windir%\Microsoft.NET\Framework\v2.0.50727\ilasm /nologo /quiet /dll %ILASM_EXTRA_ARGS% /include=%INCLUDE_PATH% /output=%OUTPUT_PATH% %INPUT_PATH% diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index 1541b12cd..09c4d65b5 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -57,7 +57,48 @@ public virtual IntPtr type_subscript(IntPtr idx) { //==================================================================== // Standard comparison implementation for instances of reflected types. //==================================================================== +#if (PYTHON32 || PYTHON33 || PYTHON34) + public static IntPtr tp_richcompare(IntPtr ob, IntPtr other, int op) { + if (op != Runtime.Py_EQ && op != Runtime.Py_NE) + { + Runtime.Incref(Runtime.PyNotImplemented); + return Runtime.PyNotImplemented; + } + + IntPtr pytrue = Runtime.PyTrue; + IntPtr pyfalse = Runtime.PyFalse; + + // swap true and false for NE + if (op != Runtime.Py_EQ) + { + pytrue = Runtime.PyFalse; + pyfalse = Runtime.PyTrue; + } + + if (ob == other) { + Runtime.Incref(pytrue); + return pytrue; + } + + CLRObject co1 = GetManagedObject(ob) as CLRObject; + CLRObject co2 = GetManagedObject(other) as CLRObject; + if (null == co2) { + Runtime.Incref(pyfalse); + return pyfalse; + } + Object o1 = co1.inst; + Object o2 = co2.inst; + + if (Object.Equals(o1, o2)) { + Runtime.Incref(pytrue); + return pytrue; + } + + Runtime.Incref(pyfalse); + return pyfalse; + } +#else public static int tp_compare(IntPtr ob, IntPtr other) { if (ob == other) { return 0; @@ -73,6 +114,7 @@ public static int tp_compare(IntPtr ob, IntPtr other) { } return -1; } +#endif //==================================================================== @@ -128,7 +170,17 @@ public static IntPtr tp_str(IntPtr ob) { if (co == null) { return Exceptions.RaiseTypeError("invalid object"); } - return Runtime.PyString_FromString(co.inst.ToString()); + try { + return Runtime.PyString_FromString(co.inst.ToString()); + } + catch (Exception e) + { + if (e.InnerException != null) { + e = e.InnerException; + } + Exceptions.SetError(e); + return IntPtr.Zero; + } } @@ -154,7 +206,7 @@ public static int tp_is_gc(IntPtr type) { public static void tp_dealloc(IntPtr ob) { ManagedType self = GetManagedObject(ob); - IntPtr dict = Marshal.ReadIntPtr(ob, ObjectOffset.ob_dict); + IntPtr dict = Marshal.ReadIntPtr(ob, ObjectOffset.DictOffset(ob)); if (dict != IntPtr.Zero) { Runtime.Decref(dict); } diff --git a/src/runtime/classderived.cs b/src/runtime/classderived.cs new file mode 100644 index 000000000..685becef9 --- /dev/null +++ b/src/runtime/classderived.cs @@ -0,0 +1,855 @@ +// ========================================================================== +// This software is subject to the provisions of the Zope Public License, +// Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +// FOR A PARTICULAR PURPOSE. +// ========================================================================== + +using System; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; +using System.Collections.Generic; +using System.Threading; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Python.Runtime +{ + + /// + /// Managed class that provides the implementation for reflected types. + /// Managed classes and value types are represented in Python by actual + /// Python type objects. Each of those type objects is associated with + /// an instance of ClassObject, which provides its implementation. + /// + + // interface used to idenfity which C# types were dynamically created as python subclasses + public interface IPythonDerivedType + { + } + + internal class ClassDerivedObject : ClassObject + { + static private Dictionary assemblyBuilders; + static private Dictionary, ModuleBuilder> moduleBuilders; + + static ClassDerivedObject() + { + assemblyBuilders = new Dictionary(); + moduleBuilders = new Dictionary, ModuleBuilder>(); + } + + internal ClassDerivedObject(Type tp) + : base(tp) + { + } + + /// + /// Implements __new__ for derived classes of reflected classes. + /// + new public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) + { + ClassDerivedObject cls = GetManagedObject(tp) as ClassDerivedObject; + + // call the managed constructor + Object obj = cls.binder.InvokeRaw(IntPtr.Zero, args, kw); + if (obj == null) + return IntPtr.Zero; + + // return the pointer to the python object + // (this indirectly calls ClassDerivedObject.ToPython) + return Converter.ToPython(obj, cls.GetType()); + } + + new public static void tp_dealloc(IntPtr ob) + { + CLRObject self = (CLRObject)GetManagedObject(ob); + + // don't let the python GC destroy this object + Runtime.PyObject_GC_UnTrack(self.pyHandle); + + // The python should now have a ref count of 0, but we don't actually want to + // deallocate the object until the C# object that references it is destroyed. + // So we don't call PyObject_GC_Del here and instead we set the python + // reference to a weak reference so that the C# object can be collected. + GCHandle gc = GCHandle.Alloc(self, GCHandleType.Weak); + Marshal.WriteIntPtr(self.pyHandle, ObjectOffset.magic(self.tpHandle), (IntPtr)gc); + self.gcHandle.Free(); + self.gcHandle = gc; + } + + // Called from Converter.ToPython for types that are python subclasses of managed types. + // The referenced python object is returned instead of a new wrapper. + internal static IntPtr ToPython(IPythonDerivedType obj) + { + // derived types have a __pyobj__ field that gets set to the python + // object in the overriden constructor + FieldInfo fi = obj.GetType().GetField("__pyobj__"); + CLRObject self = (CLRObject)fi.GetValue(obj); + + Runtime.Incref(self.pyHandle); + + // when the C# constructor creates the python object it starts as a weak + // reference with a reference count of 0. Now we're passing this object + // to Python the reference count needs to be incremented and the reference + // needs to be replaced with a strong reference to stop the C# object being + // collected while Python still has a reference to it. + if (Runtime.Refcount(self.pyHandle) == 1) + { + GCHandle gc = GCHandle.Alloc(self, GCHandleType.Normal); + Marshal.WriteIntPtr(self.pyHandle, ObjectOffset.magic(self.tpHandle), (IntPtr)gc); + self.gcHandle.Free(); + self.gcHandle = gc; + + // now the object has a python reference it's safe for the python GC to track it + Runtime.PyObject_GC_Track(self.pyHandle); + } + + return self.pyHandle; + } + + /// + /// Creates a new managed type derived from a base type with any virtual + /// methods overriden to call out to python if the associated python + /// object has overriden the method. + /// + internal static Type CreateDerivedType(string name, + Type baseType, + IntPtr py_dict, + string namespaceStr, + string assemblyName, + string moduleName="Python.Runtime.Dynamic.dll") + { + if (null != namespaceStr) + name = namespaceStr + "." + name; + + if (null == assemblyName) + assemblyName = Assembly.GetExecutingAssembly().FullName; + + ModuleBuilder moduleBuilder = GetModuleBuilder(assemblyName, moduleName); + TypeBuilder typeBuilder; + + Type baseClass = baseType; + List interfaces = new List { typeof(IPythonDerivedType) }; + + // if the base type is an interface then use System.Object as the base class + // and add the base type to the list of interfaces this new class will implement. + if (baseType.IsInterface) + { + interfaces.Add(baseType); + baseClass = typeof(System.Object); + } + + typeBuilder = moduleBuilder.DefineType(name, + TypeAttributes.Public | TypeAttributes.Class, + baseClass, + interfaces.ToArray()); + + // add a field for storing the python object pointer + FieldBuilder fb = typeBuilder.DefineField("__pyobj__", typeof(CLRObject), FieldAttributes.Public); + + // override any constructors + ConstructorInfo[] constructors = baseClass.GetConstructors(); + foreach (ConstructorInfo ctor in constructors) + { + AddConstructor(ctor, baseType, typeBuilder); + } + + // Override any properties explicitly overriden in python + HashSet pyProperties = new HashSet(); + if (py_dict != IntPtr.Zero && Runtime.PyDict_Check(py_dict)) + { + Runtime.Incref(py_dict); + using (PyDict dict = new PyDict(py_dict)) + using (PyObject keys = dict.Keys()) + { + foreach (PyObject pyKey in keys) + { + using (PyObject value = dict[pyKey]) + if (value.HasAttr("_clr_property_type_")) + { + string propertyName = pyKey.ToString(); + pyProperties.Add(propertyName); + + // Add the property to the type + AddPythonProperty(propertyName, value, typeBuilder); + } + } + } + } + + // override any virtual methods not already overriden by the properties above + MethodInfo[] methods = baseType.GetMethods(); + HashSet virtualMethods = new HashSet(); + foreach (MethodInfo method in methods) + { + if (!method.Attributes.HasFlag(MethodAttributes.Virtual) | method.Attributes.HasFlag(MethodAttributes.Final)) + continue; + + // skip if this property has already been overriden + if ((method.Name.StartsWith("get_") || method.Name.StartsWith("set_")) + && pyProperties.Contains(method.Name.Substring(4))) + continue; + + // keep track of the virtual methods redirected to the python instance + virtualMethods.Add(method.Name); + + // override the virtual method to call out to the python method, if there is one. + AddVirtualMethod(method, baseType, typeBuilder); + } + + // Add any additional methods and properties explicitly exposed from Python. + if (py_dict != IntPtr.Zero && Runtime.PyDict_Check(py_dict)) + { + Runtime.Incref(py_dict); + using (PyDict dict = new PyDict(py_dict)) + using (PyObject keys = dict.Keys()) + { + foreach (PyObject pyKey in keys) + { + using (PyObject value = dict[pyKey]) + if (value.HasAttr("_clr_return_type_") && value.HasAttr("_clr_arg_types_")) + { + string methodName = pyKey.ToString(); + + // if this method has already been redirected to the python method skip it + if (virtualMethods.Contains(methodName)) + continue; + + // Add the method to the type + AddPythonMethod(methodName, value, typeBuilder); + } + } + } + } + + // add the destructor so the python object created in the constructor gets destroyed + MethodBuilder methodBuilder = typeBuilder.DefineMethod("Finalize", + MethodAttributes.Family | + MethodAttributes.Virtual | + MethodAttributes.HideBySig, + CallingConventions.Standard, + typeof(void), + Type.EmptyTypes); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("Finalize")); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Call, baseClass.GetMethod("Finalize", BindingFlags.NonPublic | BindingFlags.Instance)); + il.Emit(OpCodes.Ret); + + Type type = typeBuilder.CreateType(); + + // scan the assembly so the newly added class can be imported + Assembly assembly = Assembly.GetAssembly(type); + AssemblyManager.ScanAssembly(assembly); + + AssemblyBuilder assemblyBuilder = assemblyBuilders[assemblyName]; + + return type; + } + + /// + /// Add a constructor override that calls the python ctor after calling the base type constructor. + /// + /// constructor to be called before calling the python ctor + /// Python callable object + /// TypeBuilder for the new type the ctor is to be added to + private static void AddConstructor(ConstructorInfo ctor, Type baseType, TypeBuilder typeBuilder) + { + ParameterInfo[] parameters = ctor.GetParameters(); + Type[] parameterTypes = (from param in parameters select param.ParameterType).ToArray(); + + // create a method for calling the original constructor + string baseCtorName = "_" + baseType.Name + "__cinit__"; + MethodBuilder methodBuilder = typeBuilder.DefineMethod(baseCtorName, + MethodAttributes.Public | + MethodAttributes.Final | + MethodAttributes.HideBySig, + typeof(void), + parameterTypes); + + // emit the assembly for calling the original method using call instead of callvirt + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + for (int i = 0; i < parameters.Length; ++i) + il.Emit(OpCodes.Ldarg, i + 1); + il.Emit(OpCodes.Call, ctor); + il.Emit(OpCodes.Ret); + + // override the original method with a new one that dispatches to python + ConstructorBuilder cb = typeBuilder.DefineConstructor(MethodAttributes.Public | + MethodAttributes.ReuseSlot | + MethodAttributes.HideBySig, + ctor.CallingConvention, + parameterTypes); + il = cb.GetILGenerator(); + il.DeclareLocal(typeof(Object[])); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldstr, baseCtorName); + il.Emit(OpCodes.Ldc_I4, parameters.Length); + il.Emit(OpCodes.Newarr, typeof(System.Object)); + il.Emit(OpCodes.Stloc_0); + for (int i = 0; i < parameters.Length; ++i) + { + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldc_I4, i); + il.Emit(OpCodes.Ldarg, i + 1); + if (parameterTypes[i].IsValueType) + il.Emit(OpCodes.Box, parameterTypes[i]); + il.Emit(OpCodes.Stelem, typeof(Object)); + } + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeCtor")); + il.Emit(OpCodes.Ret); + } + + /// + /// Add a virtual method override that checks for an override on the python instance + /// and calls it, otherwise fall back to the base class method. + /// + /// virtual method to be overriden + /// Python callable object + /// TypeBuilder for the new type the method is to be added to + private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuilder typeBuilder) + { + + ParameterInfo[] parameters = method.GetParameters(); + Type[] parameterTypes = (from param in parameters select param.ParameterType).ToArray(); + + // create a method for calling the original method + string baseMethodName = "_" + baseType.Name + "__" + method.Name; + MethodBuilder methodBuilder = typeBuilder.DefineMethod(baseMethodName, + MethodAttributes.Public | + MethodAttributes.Final | + MethodAttributes.HideBySig, + method.ReturnType, + parameterTypes); + + // emit the assembly for calling the original method using call instead of callvirt + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + for (int i = 0; i < parameters.Length; ++i) + il.Emit(OpCodes.Ldarg, i + 1); + il.Emit(OpCodes.Call, method); + il.Emit(OpCodes.Ret); + + // override the original method with a new one that dispatches to python + methodBuilder = typeBuilder.DefineMethod(method.Name, + MethodAttributes.Public | + MethodAttributes.ReuseSlot | + MethodAttributes.Virtual | + MethodAttributes.HideBySig, + method.CallingConvention, + method.ReturnType, + parameterTypes); + il = methodBuilder.GetILGenerator(); + il.DeclareLocal(typeof(Object[])); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldstr, method.Name); + il.Emit(OpCodes.Ldstr, baseMethodName); + il.Emit(OpCodes.Ldc_I4, parameters.Length); + il.Emit(OpCodes.Newarr, typeof(System.Object)); + il.Emit(OpCodes.Stloc_0); + for (int i = 0; i < parameters.Length; ++i) + { + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldc_I4, i); + il.Emit(OpCodes.Ldarg, i + 1); + if (parameterTypes[i].IsValueType) + il.Emit(OpCodes.Box, parameterTypes[i]); + il.Emit(OpCodes.Stelem, typeof(Object)); + } + il.Emit(OpCodes.Ldloc_0); + if (method.ReturnType == typeof(void)) + { + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethodVoid")); + } + else + { + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethod").MakeGenericMethod(method.ReturnType)); + } + il.Emit(OpCodes.Ret); + } + + /// + /// Python method may have the following function attributes set to control how they're exposed: + /// - _clr_return_type_ - method return type (required) + /// - _clr_arg_types_ - list of method argument types (required) + /// - _clr_method_name_ - method name, if different from the python method name (optional) + /// + /// Method name to add to the type + /// Python callable object + /// TypeBuilder for the new type the method/property is to be added to + private static void AddPythonMethod(string methodName, PyObject func, TypeBuilder typeBuilder) + { + if (func.HasAttr("_clr_method_name_")) + { + using (PyObject pyMethodName = func.GetAttr("_clr_method_name_")) + methodName = pyMethodName.ToString(); + } + + using (PyObject pyReturnType = func.GetAttr("_clr_return_type_")) + using (PyObject pyArgTypes = func.GetAttr("_clr_arg_types_")) + { + Type returnType = pyReturnType.AsManagedObject(typeof(Type)) as Type; + if (returnType == null) + returnType = typeof(void); + + if (!pyArgTypes.IsIterable()) + throw new ArgumentException("_clr_arg_types_ must be a list or tuple of CLR types"); + + List argTypes = new List(); + foreach (PyObject pyArgType in pyArgTypes) + { + Type argType = pyArgType.AsManagedObject(typeof(Type)) as Type; + if (argType == null) + throw new ArgumentException("_clr_arg_types_ must be a list or tuple of CLR types"); + argTypes.Add(argType); + } + + // add the method to call back into python + MethodAttributes methodAttribs = MethodAttributes.Public | + MethodAttributes.Virtual | + MethodAttributes.ReuseSlot | + MethodAttributes.HideBySig; + + MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodName, + methodAttribs, + returnType, + argTypes.ToArray()); + + ILGenerator il = methodBuilder.GetILGenerator(); + il.DeclareLocal(typeof(Object[])); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldstr, methodName); + il.Emit(OpCodes.Ldnull); // don't fall back to the base type's method + il.Emit(OpCodes.Ldc_I4, argTypes.Count); + il.Emit(OpCodes.Newarr, typeof(System.Object)); + il.Emit(OpCodes.Stloc_0); + for (int i = 0; i < argTypes.Count; ++i) + { + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldc_I4, i); + il.Emit(OpCodes.Ldarg, i + 1); + if (argTypes[i].IsValueType) + il.Emit(OpCodes.Box, argTypes[i]); + il.Emit(OpCodes.Stelem, typeof(Object)); + } + il.Emit(OpCodes.Ldloc_0); + if (returnType == typeof(void)) + { + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethodVoid")); + } + else + { + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethod").MakeGenericMethod(returnType)); + } + il.Emit(OpCodes.Ret); + } + } + + /// + /// Python properties may have the following function attributes set to control how they're exposed: + /// - _clr_property_type_ - property type (required) + /// + /// Property name to add to the type + /// Python property object + /// TypeBuilder for the new type the method/property is to be added to + private static void AddPythonProperty(string propertyName, PyObject func, TypeBuilder typeBuilder) + { + // add the method to call back into python + MethodAttributes methodAttribs = MethodAttributes.Public | + MethodAttributes.Virtual | + MethodAttributes.ReuseSlot | + MethodAttributes.HideBySig | + MethodAttributes.SpecialName; + + using (PyObject pyPropertyType = func.GetAttr("_clr_property_type_")) + { + Type propertyType = pyPropertyType.AsManagedObject(typeof(Type)) as Type; + if (propertyType == null) + throw new ArgumentException("_clr_property_type must be a CLR type"); + + PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, + PropertyAttributes.None, + propertyType, + null); + + if (func.HasAttr("fget")) + { + using (PyObject pyfget = func.GetAttr("fget")) + if (pyfget.IsTrue()) + { + MethodBuilder methodBuilder = typeBuilder.DefineMethod("get_" + propertyName, + methodAttribs, + propertyType, + null); + + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldstr, propertyName); + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeGetProperty").MakeGenericMethod(propertyType)); + il.Emit(OpCodes.Ret); + + propertyBuilder.SetGetMethod(methodBuilder); + } + } + + if (func.HasAttr("fset")) + { + using (PyObject pyset = func.GetAttr("fset")) + if (pyset.IsTrue()) + { + MethodBuilder methodBuilder = typeBuilder.DefineMethod("set_" + propertyName, + methodAttribs, + null, + new Type[]{propertyType}); + + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldstr, propertyName); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeSetProperty").MakeGenericMethod(propertyType)); + il.Emit(OpCodes.Ret); + + propertyBuilder.SetSetMethod(methodBuilder); + } + } + } + } + + private static ModuleBuilder GetModuleBuilder(string assemblyName, string moduleName) + { + // find or create a dynamic assembly and module + AppDomain domain = AppDomain.CurrentDomain; + ModuleBuilder moduleBuilder = null; + + if (moduleBuilders.ContainsKey(Tuple.Create(assemblyName, moduleName))) + { + moduleBuilder = moduleBuilders[Tuple.Create(assemblyName, moduleName)]; + } + else + { + AssemblyBuilder assemblyBuilder = null; + if (assemblyBuilders.ContainsKey(assemblyName)) + { + assemblyBuilder = assemblyBuilders[assemblyName]; + } + else + { + assemblyBuilder = domain.DefineDynamicAssembly(new AssemblyName(assemblyName), + AssemblyBuilderAccess.Run); + assemblyBuilders[assemblyName] = assemblyBuilder; + } + + moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName); + moduleBuilders[Tuple.Create(assemblyName, moduleName)] = moduleBuilder; + } + + return moduleBuilder; + } + } + + // + // PythonDerivedType contains static methods used by the dynamically created + // derived type that allow it to call back into python from overriden virtual + // methods, and also handle the construction and destruction of the python + // object. + // + // This has to be public as it's called from methods on dynamically built classes + // potentially in other assemblies. + // + public class PythonDerivedType + { + //==================================================================== + // This is the implementaion of the overriden methods in the derived + // type. It looks for a python method with the same name as the method + // on the managed base class and if it exists and isn't the managed + // method binding (ie it has been overriden in the derived python + // class) it calls it, otherwise it calls the base method. + //==================================================================== + public static T InvokeMethod(IPythonDerivedType obj, string methodName, string origMethodName, Object[] args) + { + FieldInfo fi = obj.GetType().GetField("__pyobj__"); + CLRObject self = (CLRObject)fi.GetValue(obj); + + if (null != self) + { + List disposeList = new List(); + IntPtr gs = Runtime.PyGILState_Ensure(); + try + { + Runtime.Incref(self.pyHandle); + PyObject pyself = new PyObject(self.pyHandle); + disposeList.Add(pyself); + + Runtime.Incref(Runtime.PyNone); + PyObject pynone = new PyObject(Runtime.PyNone); + disposeList.Add(pynone); + + PyObject method = pyself.GetAttr(methodName, pynone); + disposeList.Add(method); + if (method.Handle != Runtime.PyNone) + { + // if the method hasn't been overriden then it will be a managed object + ManagedType managedMethod = ManagedType.GetManagedObject(method.Handle); + if (null == managedMethod) + { + PyObject[] pyargs = new PyObject[args.Length]; + for (int i = 0; i < args.Length; ++i) + { + pyargs[i] = new PyObject(Converter.ToPythonImplicit(args[i])); + disposeList.Add(pyargs[i]); + } + + PyObject py_result = method.Invoke(pyargs); + disposeList.Add(py_result); + return (T)py_result.AsManagedObject(typeof(T)); + } + } + } + finally + { + foreach (PyObject x in disposeList) { + if (x != null) + x.Dispose(); + } + Runtime.PyGILState_Release(gs); + } + } + + if (origMethodName == null) + throw new NullReferenceException("Python object does not have a '" + methodName + "' method"); + + return (T)obj.GetType().InvokeMember(origMethodName, + BindingFlags.InvokeMethod, + null, + obj, + args); + } + + public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, string origMethodName, Object[] args) + { + FieldInfo fi = obj.GetType().GetField("__pyobj__"); + CLRObject self = (CLRObject)fi.GetValue(obj); + if (null != self) + { + List disposeList = new List(); + IntPtr gs = Runtime.PyGILState_Ensure(); + try + { + Runtime.Incref(self.pyHandle); + PyObject pyself = new PyObject(self.pyHandle); + disposeList.Add(pyself); + + Runtime.Incref(Runtime.PyNone); + PyObject pynone = new PyObject(Runtime.PyNone); + disposeList.Add(pynone); + + PyObject method = pyself.GetAttr(methodName, pynone); + disposeList.Add(method); + if (method.Handle != Runtime.PyNone) + { + // if the method hasn't been overriden then it will be a managed object + ManagedType managedMethod = ManagedType.GetManagedObject(method.Handle); + if (null == managedMethod) + { + PyObject[] pyargs = new PyObject[args.Length]; + for (int i = 0; i < args.Length; ++i) + { + pyargs[i] = new PyObject(Converter.ToPythonImplicit(args[i])); + disposeList.Add(pyargs[i]); + } + + PyObject py_result = method.Invoke(pyargs); + disposeList.Add(py_result); + return; + } + } + } + finally + { + foreach (PyObject x in disposeList) { + if (x != null) + x.Dispose(); + } + Runtime.PyGILState_Release(gs); + } + } + + if (origMethodName == null) + throw new NullReferenceException("Python object does not have a '" + methodName + "' method"); + + obj.GetType().InvokeMember(origMethodName, + BindingFlags.InvokeMethod, + null, + obj, + args); + } + + public static T InvokeGetProperty(IPythonDerivedType obj, string propertyName) + { + FieldInfo fi = obj.GetType().GetField("__pyobj__"); + CLRObject self = (CLRObject)fi.GetValue(obj); + + if (null == self) + throw new NullReferenceException("Instance must be specified when getting a property"); + + IntPtr gs = Runtime.PyGILState_Ensure(); + try + { + Runtime.Incref(self.pyHandle); + using (PyObject pyself = new PyObject(self.pyHandle)) + using (PyObject pyvalue = pyself.GetAttr(propertyName)) + return (T)pyvalue.AsManagedObject(typeof(T)); + } + finally + { + Runtime.PyGILState_Release(gs); + } + } + + public static void InvokeSetProperty(IPythonDerivedType obj, string propertyName, T value) + { + FieldInfo fi = obj.GetType().GetField("__pyobj__"); + CLRObject self = (CLRObject)fi.GetValue(obj); + + if (null == self) + throw new NullReferenceException("Instance must be specified when setting a property"); + + IntPtr gs = Runtime.PyGILState_Ensure(); + try + { + Runtime.Incref(self.pyHandle); + using (PyObject pyself = new PyObject(self.pyHandle)) + using (PyObject pyvalue = new PyObject(Converter.ToPythonImplicit(value))) + pyself.SetAttr(propertyName, pyvalue); + } + finally + { + Runtime.PyGILState_Release(gs); + } + } + + public static void InvokeCtor(IPythonDerivedType obj, string origCtorName, Object[] args) + { + // call the base constructor + obj.GetType().InvokeMember(origCtorName, + BindingFlags.InvokeMethod, + null, + obj, + args); + + List disposeList = new List(); + CLRObject self = null; + IntPtr gs = Runtime.PyGILState_Ensure(); + try + { + // create the python object + IntPtr type = TypeManager.GetTypeHandle(obj.GetType()); + self = new CLRObject(obj, type); + + // set __pyobj__ to self and deref the python object which will allow this + // object to be collected. + FieldInfo fi = obj.GetType().GetField("__pyobj__"); + fi.SetValue(obj, self); + + Runtime.Incref(self.pyHandle); + PyObject pyself = new PyObject(self.pyHandle); + disposeList.Add(pyself); + + Runtime.Incref(Runtime.PyNone); + PyObject pynone = new PyObject(Runtime.PyNone); + disposeList.Add(pynone); + + // call __init__ + PyObject init = pyself.GetAttr("__init__", pynone); + disposeList.Add(init); + if (init.Handle != Runtime.PyNone) + { + // if __init__ hasn't been overriden then it will be a managed object + ManagedType managedMethod = ManagedType.GetManagedObject(init.Handle); + if (null == managedMethod) + { + PyObject[] pyargs = new PyObject[args.Length]; + for (int i = 0; i < args.Length; ++i) + { + pyargs[i] = new PyObject(Converter.ToPython(args[i], args[i].GetType())); + disposeList.Add(pyargs[i]); + } + + disposeList.Add(init.Invoke(pyargs)); + } + } + } + finally + { + foreach (PyObject x in disposeList) { + if (x != null) + x.Dispose(); + } + + // Decrement the python object's reference count. + // This doesn't actually destroy the object, it just sets the reference to this object + // to be a weak reference and it will be destroyed when the C# object is destroyed. + if (null != self) + Runtime.Decref(self.pyHandle); + + Runtime.PyGILState_Release(gs); + } + } + + public static void Finalize(IPythonDerivedType obj) + { + FieldInfo fi = obj.GetType().GetField("__pyobj__"); + CLRObject self = (CLRObject)fi.GetValue(obj); + + // If python's been terminated then just free the gchandle. + lock (Runtime.IsFinalizingLock) + { + if (0 == Runtime.Py_IsInitialized() || Runtime.IsFinalizing) + { + self.gcHandle.Free(); + return; + } + } + + // delete the python object in an asnyc task as we may not be able to acquire + // the GIL immediately and we don't want to block the GC thread. + var t = Task.Factory.StartNew(() => + { + lock (Runtime.IsFinalizingLock) + { + // If python's been terminated then just free the gchandle. + if (0 == Runtime.Py_IsInitialized() || Runtime.IsFinalizing) + { + self.gcHandle.Free(); + return; + } + + IntPtr gs = Runtime.PyGILState_Ensure(); + try + { + // the C# object is being destroyed which must mean there are no more + // references to the Python object as well so now we can dealloc the + // python object. + IntPtr dict = Marshal.ReadIntPtr(self.pyHandle, ObjectOffset.DictOffset(self.pyHandle)); + if (dict != IntPtr.Zero) + Runtime.Decref(dict); + Runtime.PyObject_GC_Del(self.pyHandle); + self.gcHandle.Free(); + } + finally + { + Runtime.PyGILState_Release(gs); + } + } + }); + } + } +} diff --git a/src/runtime/classmanager.cs b/src/runtime/classmanager.cs index 164c37cb6..8744de417 100644 --- a/src/runtime/classmanager.cs +++ b/src/runtime/classmanager.cs @@ -105,7 +105,12 @@ private static ClassBase CreateClass(Type type) { impl = new ExceptionClassObject(type); } - else { + else if (null != type.GetField("__pyobj__")) { + impl = new ClassDerivedObject(type); + } + + else + { impl = new ClassObject(type); } @@ -345,7 +350,7 @@ private static ClassInfo GetClassInfo(Type type) { typeof(MethodInfo) ); - ob = new MethodObject(name, mlist); + ob = new MethodObject(type, name, mlist); ci.members[name] = ob; } diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index c61f9523d..1de49aede 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -25,15 +25,15 @@ internal CLRObject(Object ob, IntPtr tp) : base() { int flags = (int)Marshal.ReadIntPtr(tp, TypeOffset.tp_flags); if ((flags & TypeFlags.Subclass) != 0) { - IntPtr dict = Marshal.ReadIntPtr(py, ObjectOffset.ob_dict); + IntPtr dict = Marshal.ReadIntPtr(py, ObjectOffset.DictOffset(tp)); if (dict == IntPtr.Zero) { dict = Runtime.PyDict_New(); - Marshal.WriteIntPtr(py, ObjectOffset.ob_dict, dict); + Marshal.WriteIntPtr(py, ObjectOffset.DictOffset(tp), dict); } } GCHandle gc = GCHandle.Alloc(this); - Marshal.WriteIntPtr(py, ObjectOffset.magic(), (IntPtr)gc); + Marshal.WriteIntPtr(py, ObjectOffset.magic(tp), (IntPtr)gc); this.tpHandle = tp; this.pyHandle = py; this.gcHandle = gc; diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 650a6178a..4d0c06c11 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -12,6 +12,7 @@ using System.Runtime.InteropServices; using System.Globalization; using System.Security; +using System.Collections; namespace Python.Runtime { @@ -33,7 +34,7 @@ private Converter() {} static Type int64Type; static Type flagsType; static Type boolType; - //static Type typeType; + static Type typeType; static Converter () { nfi = NumberFormatInfo.InvariantInfo; @@ -44,7 +45,7 @@ static Converter () { doubleType = typeof(Double); flagsType = typeof(FlagsAttribute); boolType = typeof(Boolean); - //typeType = typeof(Type); + typeType = typeof(Type); } @@ -91,6 +92,14 @@ internal static IntPtr ToPython(Object value, Type type) { return result; } + // it the type is a python subclass of a managed type then return the + // underying python object rather than construct a new wrapper object. + IPythonDerivedType pyderived = value as IPythonDerivedType; + if (null != pyderived) + { + return ClassDerivedObject.ToPython(pyderived); + } + // hmm - from Python, we almost never care what the declared // type is. we'd rather have the object bound to the actual // implementing class. @@ -165,6 +174,16 @@ internal static IntPtr ToPython(Object value, Type type) { return Runtime.PyLong_FromUnsignedLongLong((ulong)value); default: + if (value is IEnumerable) { + using (var resultlist = new PyList()) { + foreach (object o in (IEnumerable)value) { + using (var p = new PyObject(ToPython(o, o.GetType()))) + resultlist.Append(p); + } + Runtime.Incref(resultlist.Handle); + return resultlist.Handle; + } + } result = CLRObject.GetInstHandle(value, type); return result; } @@ -307,6 +326,57 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return false; } + // Conversion to 'Type' is done using the same mappings as above + // for objects. + + if (obType == typeType) + { + if (value == Runtime.PyStringType) + { + result = stringType; + return true; + } + + else if (value == Runtime.PyBoolType) + { + result = boolType; + return true; + } + + else if (value == Runtime.PyIntType) + { + result = int32Type; + return true; + } + + else if (value == Runtime.PyLongType) + { + result = int64Type; + return true; + } + + else if (value == Runtime.PyFloatType) + { + result = doubleType; + return true; + } + + else if (value == Runtime.PyListType || value == Runtime.PyTupleType) + { + result = typeof(object[]); + return true; + } + + if (setError) + { + Exceptions.SetError(Exceptions.TypeError, + "value cannot be converted to Type" + ); + } + + return false; + } + return ToPrimitive(value, obType, out result, setError); } @@ -335,6 +405,7 @@ static bool ToPrimitive(IntPtr value, Type obType, out Object result, return true; case TypeCode.Int32: +#if !(PYTHON32 || PYTHON33 || PYTHON34) // Trickery to support 64-bit platforms. if (IntPtr.Size == 4) { op = Runtime.PyNumber_Int(value); @@ -357,6 +428,10 @@ static bool ToPrimitive(IntPtr value, Type obType, out Object result, return true; } else { +#else + // When using Python3 always use the PyLong API + { +#endif op = Runtime.PyNumber_Long(value); if (op == IntPtr.Zero) { if (Exceptions.ExceptionMatches(overflow)) { @@ -381,6 +456,18 @@ static bool ToPrimitive(IntPtr value, Type obType, out Object result, return true; case TypeCode.Byte: +#if (PYTHON32 || PYTHON33 || PYTHON34) + if (Runtime.PyObject_TypeCheck(value, Runtime.PyBytesType)) + { + if (Runtime.PyBytes_Size(value) == 1) + { + op = Runtime.PyBytes_AS_STRING(value); + result = (byte)Marshal.ReadByte(op); + return true; + } + goto type_error; + } +#else if (Runtime.PyObject_TypeCheck(value, Runtime.PyStringType)) { if (Runtime.PyString_Size(value) == 1) { op = Runtime.PyString_AS_STRING(value); @@ -389,6 +476,7 @@ static bool ToPrimitive(IntPtr value, Type obType, out Object result, } goto type_error; } +#endif op = Runtime.PyNumber_Int(value); if (op == IntPtr.Zero) { @@ -408,6 +496,16 @@ static bool ToPrimitive(IntPtr value, Type obType, out Object result, return true; case TypeCode.SByte: +#if (PYTHON32 || PYTHON33 || PYTHON34) + if (Runtime.PyObject_TypeCheck(value, Runtime.PyBytesType)) { + if (Runtime.PyBytes_Size(value) == 1) { + op = Runtime.PyBytes_AS_STRING(value); + result = (byte)Marshal.ReadByte(op); + return true; + } + goto type_error; + } +#else if (Runtime.PyObject_TypeCheck(value, Runtime.PyStringType)) { if (Runtime.PyString_Size(value) == 1) { op = Runtime.PyString_AS_STRING(value); @@ -416,6 +514,7 @@ static bool ToPrimitive(IntPtr value, Type obType, out Object result, } goto type_error; } +#endif op = Runtime.PyNumber_Int(value); if (op == IntPtr.Zero) { @@ -435,7 +534,16 @@ static bool ToPrimitive(IntPtr value, Type obType, out Object result, return true; case TypeCode.Char: - +#if (PYTHON32 || PYTHON33 || PYTHON34) + if (Runtime.PyObject_TypeCheck(value, Runtime.PyBytesType)) { + if (Runtime.PyBytes_Size(value) == 1) { + op = Runtime.PyBytes_AS_STRING(value); + result = (byte)Marshal.ReadByte(op); + return true; + } + goto type_error; + } +#else if (Runtime.PyObject_TypeCheck(value, Runtime.PyStringType)) { if (Runtime.PyString_Size(value) == 1) { op = Runtime.PyString_AS_STRING(value); @@ -444,7 +552,7 @@ static bool ToPrimitive(IntPtr value, Type obType, out Object result, } goto type_error; } - +#endif else if (Runtime.PyObject_TypeCheck(value, Runtime.PyUnicodeType)) { if (Runtime.PyUnicode_GetSize(value) == 1) { @@ -713,10 +821,13 @@ static bool ToEnum(IntPtr value, Type obType, out Object result, return false; } - - - } - + public static class ConverterExtension + { + public static PyObject ToPython(this object o) + { + return new PyObject(Converter.ToPython(o, o.GetType())); + } + } } diff --git a/src/runtime/delegateobject.cs b/src/runtime/delegateobject.cs index 839fb71e5..473b2e81c 100644 --- a/src/runtime/delegateobject.cs +++ b/src/runtime/delegateobject.cs @@ -103,7 +103,36 @@ public static IntPtr tp_call(IntPtr ob, IntPtr args, IntPtr kw) { //==================================================================== // Implements __cmp__ for reflected delegate types. //==================================================================== +#if (PYTHON32 || PYTHON33 || PYTHON34) + public static new IntPtr tp_richcompare(IntPtr ob, IntPtr other, int op) { + if (op != Runtime.Py_EQ && op != Runtime.Py_NE) + { + Runtime.Incref(Runtime.PyNotImplemented); + return Runtime.PyNotImplemented; + } + + IntPtr pytrue = Runtime.PyTrue; + IntPtr pyfalse = Runtime.PyFalse; + + // swap true and false for NE + if (op != Runtime.Py_EQ) + { + pytrue = Runtime.PyFalse; + pyfalse = Runtime.PyTrue; + } + + Delegate d1 = GetTrueDelegate(ob); + Delegate d2 = GetTrueDelegate(other); + if (d1 == d2) + { + Runtime.Incref(pytrue); + return pytrue; + } + Runtime.Incref(pyfalse); + return pyfalse; + } +#else public static new int tp_compare(IntPtr ob, IntPtr other) { Delegate d1 = GetTrueDelegate(ob); Delegate d2 = GetTrueDelegate(other); @@ -112,7 +141,7 @@ public static IntPtr tp_call(IntPtr ob, IntPtr args, IntPtr kw) { } return -1; } - +#endif } diff --git a/src/runtime/exceptions.cs b/src/runtime/exceptions.cs index f08217dac..2abf1f29d 100644 --- a/src/runtime/exceptions.cs +++ b/src/runtime/exceptions.cs @@ -31,7 +31,7 @@ internal class ExceptionClassObject : ClassObject { internal ExceptionClassObject(Type tp) : base(tp) { } -#if (PYTHON25 || PYTHON26 || PYTHON27) +#if (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) internal static Exception ToException(IntPtr ob) { CLRObject co = GetManagedObject(ob) as CLRObject; if (co == null) { @@ -114,7 +114,7 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key) return Runtime.PyObject_GenericGetAttr(ob, key); } -#endif // (PYTHON25 || PYTHON26 || PYTHON27) +#endif // (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) } /// @@ -136,7 +136,11 @@ private Exceptions() {} //=================================================================== internal static void Initialize() { +#if (PYTHON32 || PYTHON33 || PYTHON34) + exceptions_module = Runtime.PyImport_ImportModule("builtins"); +#else exceptions_module = Runtime.PyImport_ImportModule("exceptions"); +#endif Exceptions.ErrorCheck(exceptions_module); warnings_module = Runtime.PyImport_ImportModule("warnings"); Exceptions.ErrorCheck(warnings_module); @@ -164,16 +168,19 @@ internal static void Initialize() { //=================================================================== internal static void Shutdown() { - Type type = typeof(Exceptions); - foreach (FieldInfo fi in type.GetFields(BindingFlags.Public | - BindingFlags.Static)) { - IntPtr op = (IntPtr)fi.GetValue(type); - if (op != IntPtr.Zero) { - Runtime.Decref(op); + if (0 != Runtime.Py_IsInitialized()) { + Type type = typeof(Exceptions); + foreach (FieldInfo fi in type.GetFields(BindingFlags.Public | + BindingFlags.Static)) { + IntPtr op = (IntPtr)fi.GetValue(type); + if (op != IntPtr.Zero) { + Runtime.Decref(op); + } } + Runtime.Decref(exceptions_module); + Runtime.PyObject_HasAttrString(warnings_module, "xx"); + Runtime.Decref(warnings_module); } - Runtime.Decref(exceptions_module); - Runtime.Decref(warnings_module); } /// @@ -565,15 +572,17 @@ internal static IntPtr RaiseTypeError(string message) { puplic static variables on the Exceptions class filled in from the python class using reflection in Initialize() looked up by name, not posistion. */ -#if (PYTHON25 || PYTHON26 || PYTHON27) +#if (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) public static IntPtr BaseException; #endif public static IntPtr Exception; public static IntPtr StopIteration; -#if (PYTHON25 || PYTHON26 || PYTHON27) +#if (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) public static IntPtr GeneratorExit; #endif +#if !(PYTHON32 || PYTHON33 || PYTHON34) public static IntPtr StandardError; +#endif public static IntPtr ArithmeticError; public static IntPtr LookupError; @@ -628,7 +637,7 @@ puplic static variables on the Exceptions class filled in from public static IntPtr SyntaxWarning; public static IntPtr RuntimeWarning; public static IntPtr FutureWarning; -#if (PYTHON25 || PYTHON26 || PYTHON27) +#if (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) public static IntPtr ImportWarning; public static IntPtr UnicodeWarning; //PyAPI_DATA(PyObject *) PyExc_BytesWarning; diff --git a/src/runtime/extensiontype.cs b/src/runtime/extensiontype.cs index b0499bb0a..75ac67e59 100644 --- a/src/runtime/extensiontype.cs +++ b/src/runtime/extensiontype.cs @@ -40,7 +40,7 @@ public ExtensionType() : base() { IntPtr py = Runtime.PyType_GenericAlloc(tp, 0); GCHandle gc = GCHandle.Alloc(this); - Marshal.WriteIntPtr(py, ObjectOffset.magic(), (IntPtr)gc); + Marshal.WriteIntPtr(py, ObjectOffset.magic(tp), (IntPtr)gc); // We have to support gc because the type machinery makes it very // hard not to - but we really don't have a need for it in most diff --git a/src/runtime/genericutil.cs b/src/runtime/genericutil.cs index c3de0aa56..e646af098 100644 --- a/src/runtime/genericutil.cs +++ b/src/runtime/genericutil.cs @@ -37,6 +37,9 @@ static GenericUtil() { //==================================================================== internal static void Register(Type t) { + if (null == t.Namespace || null == t.Name) + return; + Dictionary> nsmap = null; mapping.TryGetValue(t.Namespace, out nsmap); if (nsmap == null) { diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs index c736f0645..9b44b240c 100644 --- a/src/runtime/importhook.cs +++ b/src/runtime/importhook.cs @@ -23,29 +23,53 @@ internal class ImportHook { static CLRModule root; static MethodWrapper hook; +#if (PYTHON32 || PYTHON33 || PYTHON34) + static IntPtr py_clr_module; + static IntPtr module_def; +#endif + //=================================================================== // Initialization performed on startup of the Python runtime. //=================================================================== internal static void Initialize() { - // Initialize the Python <--> CLR module hook. We replace the // built-in Python __import__ with our own. This isn't ideal, // but it provides the most "Pythonic" way of dealing with CLR // modules (Python doesn't provide a way to emulate packages). - IntPtr dict = Runtime.PyImport_GetModuleDict(); +#if (PYTHON32 || PYTHON33 || PYTHON34) + IntPtr mod = Runtime.PyImport_ImportModule("builtins"); + py_import = Runtime.PyObject_GetAttrString(mod, "__import__"); +#else IntPtr mod = Runtime.PyDict_GetItemString(dict, "__builtin__"); py_import = Runtime.PyObject_GetAttrString(mod, "__import__"); - +#endif hook = new MethodWrapper(typeof(ImportHook), "__import__"); Runtime.PyObject_SetAttrString(mod, "__import__", hook.ptr); Runtime.Decref(hook.ptr); root = new CLRModule(); + +#if (PYTHON32 || PYTHON33 || PYTHON34) + // create a python module with the same methods as the clr module-like object + module_def = ModuleDefOffset.AllocModuleDef("clr"); + py_clr_module = Runtime.PyModule_Create2(module_def, 3); + + // both dicts are borrowed references + IntPtr mod_dict = Runtime.PyModule_GetDict(py_clr_module); + IntPtr clr_dict = Runtime._PyObject_GetDictPtr(root.pyHandle); // PyObject** + clr_dict = (IntPtr)Marshal.PtrToStructure(clr_dict, typeof(IntPtr)); + + Runtime.PyDict_Update(mod_dict, clr_dict); + Runtime.PyDict_SetItemString(dict, "CLR", py_clr_module); + Runtime.PyDict_SetItemString(dict, "clr", py_clr_module); +#else Runtime.Incref(root.pyHandle); // we are using the module two times Runtime.PyDict_SetItemString(dict, "CLR", root.pyHandle); Runtime.PyDict_SetItemString(dict, "clr", root.pyHandle); +#endif + } @@ -54,11 +78,74 @@ internal static void Initialize() { //=================================================================== internal static void Shutdown() { - Runtime.Decref(root.pyHandle); - Runtime.Decref(root.pyHandle); - Runtime.Decref(py_import); +#if (PYTHON32 || PYTHON33 || PYTHON34) + if (0 != Runtime.Py_IsInitialized()) { + Runtime.Decref(py_clr_module); + Runtime.Decref(root.pyHandle); + } + ModuleDefOffset.FreeModuleDef(module_def); +#else + if (0 != Runtime.Py_IsInitialized()) { + Runtime.Decref(root.pyHandle); + Runtime.Decref(root.pyHandle); + } +#endif + if (0 != Runtime.Py_IsInitialized()) { + Runtime.Decref(py_import); + } } + //=================================================================== + // Return the clr python module (new reference) + //=================================================================== + public static IntPtr GetCLRModule(IntPtr? fromList=null) { + root.InitializePreload(); +#if (PYTHON32 || PYTHON33 || PYTHON34) + // update the module dictionary with the contents of the root dictionary + root.LoadNames(); + IntPtr py_mod_dict = Runtime.PyModule_GetDict(py_clr_module); + IntPtr clr_dict = Runtime._PyObject_GetDictPtr(root.pyHandle); // PyObject** + clr_dict = (IntPtr)Marshal.PtrToStructure(clr_dict, typeof(IntPtr)); + Runtime.PyDict_Update(py_mod_dict, clr_dict); + + // find any items from the fromlist and get them from the root if they're not + // aleady in the module dictionary + if (fromList != null && fromList != IntPtr.Zero) { + if (Runtime.PyTuple_Check(fromList.GetValueOrDefault())) + { + Runtime.Incref(py_mod_dict); + using(PyDict mod_dict = new PyDict(py_mod_dict)) { + Runtime.Incref(fromList.GetValueOrDefault()); + using (PyTuple from = new PyTuple(fromList.GetValueOrDefault())) { + foreach (PyObject item in from) { + if (mod_dict.HasKey(item)) + continue; + + string s = item.AsManagedObject(typeof(string)) as string; + if (null == s) + continue; + + ManagedType attr = root.GetAttribute(s, true); + if (null == attr) + continue; + + Runtime.Incref(attr.pyHandle); + using (PyObject obj = new PyObject(attr.pyHandle)) { + mod_dict.SetItem(s, obj); + } + } + } + } + } + } + + Runtime.Incref(py_clr_module); + return py_clr_module; +#else + Runtime.Incref(root.pyHandle); + return root.pyHandle; +#endif + } //=================================================================== // The actual import hook that ties Python to the managed world. @@ -102,19 +189,31 @@ public static IntPtr __import__(IntPtr self, IntPtr args, IntPtr kw) { // do the Incref()ed return here, since we've already found // the module. if (mod_name == "clr") { - root.InitializePreload(); - Runtime.Incref(root.pyHandle); - return root.pyHandle; + IntPtr clr_module = GetCLRModule(fromList); + if (clr_module != IntPtr.Zero) { + IntPtr sys_modules = Runtime.PyImport_GetModuleDict(); + if (sys_modules != IntPtr.Zero) { + Runtime.PyDict_SetItemString(sys_modules, "clr", clr_module); + } + } + return clr_module; } if (mod_name == "CLR") { Exceptions.deprecation("The CLR module is deprecated. " + "Please use 'clr'."); - root.InitializePreload(); - Runtime.Incref(root.pyHandle); - return root.pyHandle; + IntPtr clr_module = GetCLRModule(fromList); + if (clr_module != IntPtr.Zero) { + IntPtr sys_modules = Runtime.PyImport_GetModuleDict(); + if (sys_modules != IntPtr.Zero) { + Runtime.PyDict_SetItemString(sys_modules, "clr", clr_module); + } + } + return clr_module; } string realname = mod_name; + string clr_prefix = null; if (mod_name.StartsWith("CLR.")) { + clr_prefix = "CLR."; // prepend when adding the module to sys.modules realname = mod_name.Substring(4); string msg = String.Format("Importing from the CLR.* namespace "+ "is deprecated. Please import '{0}' directly.", realname); @@ -174,6 +273,9 @@ public static IntPtr __import__(IntPtr self, IntPtr args, IntPtr kw) { Runtime.Incref(module); return module; } + if (clr_prefix != null) { + return GetCLRModule(fromList); + } module = Runtime.PyDict_GetItemString(modules, names[0]); Runtime.Incref(module); return module; @@ -209,9 +311,18 @@ public static IntPtr __import__(IntPtr self, IntPtr args, IntPtr kw) { if (CLRModule.preload) { tail.LoadNames(); } - Runtime.PyDict_SetItemString(modules, tail.moduleName, - tail.pyHandle - ); + + // Add the module to sys.modules + Runtime.PyDict_SetItemString(modules, + tail.moduleName, + tail.pyHandle); + + // If imported from CLR add CLR. to sys.modules as well + if (clr_prefix != null) { + Runtime.PyDict_SetItemString(modules, + clr_prefix + tail.moduleName, + tail.pyHandle); + } } ModuleObject mod = fromlist ? tail : head; diff --git a/src/runtime/interop.cs b/src/runtime/interop.cs index 9aad4c6e4..401926082 100644 --- a/src/runtime/interop.cs +++ b/src/runtime/interop.cs @@ -12,6 +12,7 @@ using System.Collections.Specialized; using System.Runtime.InteropServices; using System.Reflection; +using System.Text; namespace Python.Runtime { @@ -77,11 +78,37 @@ static ObjectOffset() { ob_data = (n+3) * size; } - public static int magic() { + public static int magic(IntPtr ob) { +#if (PYTHON32 || PYTHON33 || PYTHON34) + if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) || + (Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException)))) + { + return ExceptionOffset.ob_data; + } +#endif return ob_data; } - public static int Size() { + public static int DictOffset(IntPtr ob) + { +#if (PYTHON32 || PYTHON33 || PYTHON34) + if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) || + (Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException)))) + { + return ExceptionOffset.ob_dict; + } +#endif + return ob_dict; + } + + public static int Size(IntPtr ob) { +#if (PYTHON32 || PYTHON33 || PYTHON34) + if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) || + (Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException)))) + { + return ExceptionOffset.Size(); + } +#endif #if (Py_DEBUG) return 6 * IntPtr.Size; #else @@ -95,10 +122,43 @@ public static int Size() { #endif public static int ob_refcnt; public static int ob_type; + private static int ob_dict; + private static int ob_data; + } + +#if (PYTHON32 || PYTHON33 || PYTHON34) + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal class ExceptionOffset + { + static ExceptionOffset() + { + Type type = typeof(ExceptionOffset); + FieldInfo[] fi = type.GetFields(); + int size = IntPtr.Size; + for (int i = 0; i < fi.Length; i++) + { + fi[i].SetValue(null, (i * size) + ObjectOffset.ob_type + size); + } + } + + public static int Size() + { + return ob_data + IntPtr.Size; + } + + // PyException_HEAD + // (start after PyObject_HEAD) + public static int dict = 0; + public static int args = 0; + public static int traceback = 0; + public static int context = 0; + public static int cause = 0; + + // extra c# data public static int ob_dict; public static int ob_data; } - +#endif [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] internal class TypeOffset { @@ -139,7 +199,7 @@ public static int magic() { public static int tp_print = 0; public static int tp_getattr = 0; public static int tp_setattr = 0; - public static int tp_compare = 0; + public static int tp_compare = 0; /* tp_reserved in Python 3 */ public static int tp_repr = 0; /* Method suites for standard classes */ @@ -198,9 +258,12 @@ public static int magic() { public static int tp_subclasses = 0; public static int tp_weaklist = 0; public static int tp_del = 0; -#if (PYTHON26 || PYTHON27) +#if (PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) /* Type attribute cache version tag. Added in version 2.6 */ public static int tp_version_tag; +#endif +#if (PYTHON34) + public static int tp_finalize = 0; #endif // COUNT_ALLOCS adds some more stuff to PyTypeObject #if (Py_COUNT_ALLOCS) @@ -212,11 +275,14 @@ public static int magic() { public static int tp_next = 0; #endif //} PyTypeObject; + //typedef struct { public static int nb_add = 0; public static int nb_subtract = 0; public static int nb_multiply = 0; +#if !(PYTHON32 || PYTHON33 || PYTHON34) public static int nb_divide = 0; +#endif public static int nb_remainder = 0; public static int nb_divmod = 0; public static int nb_power = 0; @@ -230,17 +296,23 @@ public static int magic() { public static int nb_and = 0; public static int nb_xor = 0; public static int nb_or = 0; +#if !(PYTHON32 || PYTHON33 || PYTHON34) public static int nb_coerce = 0; +#endif public static int nb_int = 0; public static int nb_long = 0; public static int nb_float = 0; +#if !(PYTHON32 || PYTHON33 || PYTHON34) public static int nb_oct = 0; public static int nb_hex = 0; +#endif /* Added in release 2.0 */ public static int nb_inplace_add = 0; public static int nb_inplace_subtract = 0; public static int nb_inplace_multiply = 0; +#if !(PYTHON32 || PYTHON33 || PYTHON34) public static int nb_inplace_divide = 0; +#endif public static int nb_inplace_remainder = 0; public static int nb_inplace_power = 0; public static int nb_inplace_lshift = 0; @@ -254,7 +326,7 @@ public static int magic() { public static int nb_true_divide = 0; public static int nb_inplace_floor_divide = 0; public static int nb_inplace_true_divide = 0; -#if (PYTHON25 || PYTHON26 || PYTHON27) +#if (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) /* Added in release 2.5 */ public static int nb_index = 0; #endif @@ -278,11 +350,13 @@ public static int magic() { public static int sq_inplace_repeat = 0; //} PySequenceMethods; //typedef struct { +#if !(PYTHON32 || PYTHON33 || PYTHON34) public static int bf_getreadbuffer = 0; public static int bf_getwritebuffer = 0; public static int bf_getsegcount = 0; public static int bf_getcharbuffer = 0; -#if (PYTHON26 || PYTHON27) +#endif +#if (PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) // This addition is not actually noted in the 2.6.5 object.h public static int bf_getbuffer = 0; public static int bf_releasebuffer = 0; @@ -291,10 +365,107 @@ public static int magic() { //PyObject *ht_name, *ht_slots; public static int name = 0; public static int slots = 0; + +#if (PYTHON33 || PYTHON34) + public static int qualname = 0; + public static int cached_keys; +#endif + /* here are optional user slots, followed by the members. */ public static int members = 0; } +#if (PYTHON32 || PYTHON33 || PYTHON34) + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal class BytesOffset + { + static BytesOffset() + { + Type type = typeof(BytesOffset); + FieldInfo[] fi = type.GetFields(); + int size = IntPtr.Size; + for (int i = 0; i < fi.Length; i++) + { + fi[i].SetValue(null, i * size); + } + } + + /* The *real* layout of a type object when allocated on the heap */ + //typedef struct _heaptypeobject { +#if (Py_DEBUG) // #ifdef Py_TRACE_REFS +/* _PyObject_HEAD_EXTRA defines pointers to support a doubly-linked list of all live heap objects. */ + public static int _ob_next = 0; + public static int _ob_prev = 0; +#endif + // PyObject_VAR_HEAD { + // PyObject_HEAD { + public static int ob_refcnt = 0; + public static int ob_type = 0; + // } + public static int ob_size = 0; /* Number of items in _VAR_iable part */ + // } + public static int ob_shash = 0; + public static int ob_sval = 0; /* start of data */ + + /* Invariants: + * ob_sval contains space for 'ob_size+1' elements. + * ob_sval[ob_size] == 0. + * ob_shash is the hash of the string or -1 if not computed yet. + */ + //} PyBytesObject; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal class ModuleDefOffset + { + static ModuleDefOffset() + { + Type type = typeof(ModuleDefOffset); + FieldInfo[] fi = type.GetFields(); + int size = IntPtr.Size; + for (int i = 0; i < fi.Length; i++) + { + fi[i].SetValue(null, (i * size) + TypeOffset.ob_size); + } + } + + public static IntPtr AllocModuleDef(string modulename) { + byte[] ascii = Encoding.ASCII.GetBytes(modulename); + int size = name + ascii.Length + 1; + IntPtr ptr = Marshal.AllocHGlobal(size); + for (int i = 0; i <= m_free; i += IntPtr.Size) + Marshal.WriteIntPtr(ptr, i, IntPtr.Zero); + Marshal.Copy(ascii, 0, (IntPtr)(ptr + name), ascii.Length); + Marshal.WriteIntPtr(ptr, m_name, (IntPtr)(ptr + name)); + Marshal.WriteByte(ptr, name + ascii.Length, 0); + return ptr; + } + + public static void FreeModuleDef(IntPtr ptr) { + Marshal.FreeHGlobal(ptr); + } + + // typedef struct PyModuleDef{ + // typedef struct PyModuleDef_Base { + // starts after PyObject_HEAD (TypeOffset.ob_type + 1) + public static int m_init = 0; + public static int m_index = 0; + public static int m_copy = 0; + // } PyModuleDef_Base + public static int m_name = 0; + public static int m_doc = 0; + public static int m_size = 0; + public static int m_methods = 0; + public static int m_reload = 0; + public static int m_traverse = 0; + public static int m_clear = 0; + public static int m_free = 0; + // } PyModuleDef + + public static int name = 0; + } +#endif // PYTHON3 + /// /// TypeFlags(): The actual bit values for the Type Flags stored /// in a class. @@ -302,6 +473,8 @@ public static int magic() { /// to good use as PythonNet specific flags (Managed and Subclass) /// internal class TypeFlags { +#if (PYTHON23 || PYTHON24 || PYTHON25 || PYTHON26 || PYTHON27) + // these flags were removed in Python 3 public static int HaveGetCharBuffer = (1 << 0); public static int HaveSequenceIn = (1 << 1); public static int GC = 0; @@ -311,6 +484,7 @@ internal class TypeFlags { public static int HaveWeakRefs = (1 << 6); public static int HaveIter = (1 << 7); public static int HaveClass = (1 << 8); +#endif public static int HeapType = (1 << 9); public static int BaseType = (1 << 10); public static int Ready = (1 << 12); @@ -321,10 +495,10 @@ internal class TypeFlags { /* XXX Reusing reserved constants */ public static int Managed = (1 << 15); // PythonNet specific public static int Subclass = (1 << 16); // PythonNet specific -#if (PYTHON25 || PYTHON26 || PYTHON27) +#if (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) public static int HaveIndex = (1 << 17); #endif -#if (PYTHON26 || PYTHON27) +#if (PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) /* Objects support nb_index in PyNumberMethods */ public static int HaveVersionTag = (1 << 18); public static int ValidVersionTag = (1 << 19); @@ -341,7 +515,11 @@ internal class TypeFlags { public static int BaseExceptionSubclass = (1 << 30); public static int TypeSubclass = (1 << 31); #endif - public static int Default = (HaveGetCharBuffer | + +// Default flags for Python 2 +#if (PYTHON23 || PYTHON24 || PYTHON25 || PYTHON26 || PYTHON27) + public static int Default = ( + HaveGetCharBuffer | HaveSequenceIn | HaveInPlaceOps | HaveRichCompare | @@ -349,10 +527,19 @@ internal class TypeFlags { HaveIter | HaveClass | HaveStacklessExtension | -#if (PYTHON25 || PYTHON26 || PYTHON27) + #if (PYTHON25 || PYTHON26 || PYTHON27) HaveIndex | -#endif + #endif 0); +#endif + +// Default flags for Python 3 +#if (PYTHON32 || PYTHON33 || PYTHON34) + public static int Default = ( + HaveStacklessExtension | + HaveVersionTag); +#endif + } @@ -410,7 +597,9 @@ static Interop() { pmap["nb_add"] = p["BinaryFunc"]; pmap["nb_subtract"] = p["BinaryFunc"]; pmap["nb_multiply"] = p["BinaryFunc"]; +#if !(PYTHON32 || PYTHON33 || PYTHON34) pmap["nb_divide"] = p["BinaryFunc"]; +#endif pmap["nb_remainder"] = p["BinaryFunc"]; pmap["nb_divmod"] = p["BinaryFunc"]; pmap["nb_power"] = p["TernaryFunc"]; @@ -433,7 +622,9 @@ static Interop() { pmap["nb_inplace_add"] = p["BinaryFunc"]; pmap["nb_inplace_subtract"] = p["BinaryFunc"]; pmap["nb_inplace_multiply"] = p["BinaryFunc"]; +#if !(PYTHON32 || PYTHON33 || PYTHON34) pmap["nb_inplace_divide"] = p["BinaryFunc"]; +#endif pmap["nb_inplace_remainder"] = p["BinaryFunc"]; pmap["nb_inplace_power"] = p["TernaryFunc"]; pmap["nb_inplace_lshift"] = p["BinaryFunc"]; @@ -445,7 +636,7 @@ static Interop() { pmap["nb_true_divide"] = p["BinaryFunc"]; pmap["nb_inplace_floor_divide"] = p["BinaryFunc"]; pmap["nb_inplace_true_divide"] = p["BinaryFunc"]; -#if (PYTHON25 || PYTHON26 || PYTHON27) +#if (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) pmap["nb_index"] = p["UnaryFunc"]; #endif @@ -541,5 +732,4 @@ public Thunk(Delegate d) { fn = d; } } - } diff --git a/src/runtime/managedtype.cs b/src/runtime/managedtype.cs index 670bcd2b3..78e29c2b6 100644 --- a/src/runtime/managedtype.cs +++ b/src/runtime/managedtype.cs @@ -42,7 +42,7 @@ internal static ManagedType GetManagedObject(IntPtr ob) { if ((flags & TypeFlags.Managed) != 0) { IntPtr op = (tp == ob) ? Marshal.ReadIntPtr(tp, TypeOffset.magic()) : - Marshal.ReadIntPtr(ob, ObjectOffset.magic()); + Marshal.ReadIntPtr(ob, ObjectOffset.magic(ob)); GCHandle gc = (GCHandle)op; return (ManagedType)gc.Target; } diff --git a/src/runtime/metatype.cs b/src/runtime/metatype.cs index 305437c84..881723e8f 100644 --- a/src/runtime/metatype.cs +++ b/src/runtime/metatype.cs @@ -46,7 +46,7 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) { return Exceptions.RaiseTypeError("invalid argument list"); } - //IntPtr name = Runtime.PyTuple_GetItem(args, 0); + IntPtr name = Runtime.PyTuple_GetItem(args, 0); IntPtr bases = Runtime.PyTuple_GetItem(args, 1); IntPtr dict = Runtime.PyTuple_GetItem(args, 2); @@ -88,12 +88,19 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) { ); } - // hack for now... fix for 1.0 - //return TypeManager.CreateSubType(args); - - - // right way + // If __assembly__ or __namespace__ are in the class dictionary then create + // a managed sub type. + // This creates a new managed type that can be used from .net to call back + // into python. + if (IntPtr.Zero != dict) { + Runtime.Incref(dict); + using (PyDict clsDict = new PyDict(dict)) { + if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__")) + return TypeManager.CreateSubType(name, base_type, dict); + } + } + // otherwise just create a basic type without reflecting back into the managed side. IntPtr func = Marshal.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_new); IntPtr type = NativeCall.Call_3(func, tp, args, kw); @@ -123,9 +130,6 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) { IntPtr gc = Marshal.ReadIntPtr(base_type, TypeOffset.magic()); Marshal.WriteIntPtr(type, TypeOffset.magic(), gc); - //DebugUtil.DumpType(base_type); - //DebugUtil.DumpType(type); - return type; } diff --git a/src/runtime/methodbinding.cs b/src/runtime/methodbinding.cs index 0459d36b2..64ab3edbe 100644 --- a/src/runtime/methodbinding.cs +++ b/src/runtime/methodbinding.cs @@ -9,6 +9,7 @@ using System; using System.Reflection; +using System.Collections.Generic; namespace Python.Runtime { @@ -23,14 +24,25 @@ internal class MethodBinding : ExtensionType { internal MethodInfo info; internal MethodObject m; internal IntPtr target; + internal IntPtr targetType; - public MethodBinding(MethodObject m, IntPtr target) : base() { + public MethodBinding(MethodObject m, IntPtr target, IntPtr targetType) : base() { Runtime.Incref(target); this.target = target; + + Runtime.Incref(targetType); + if (targetType == IntPtr.Zero) + targetType = Runtime.PyObject_Type(target); + this.targetType = targetType; + this.info = null; this.m = m; } + public MethodBinding(MethodObject m, IntPtr target) : this(m, target, IntPtr.Zero) + { + } + //==================================================================== // Implement binding of generic methods using the subscript syntax []. //==================================================================== @@ -114,25 +126,63 @@ public static IntPtr tp_call(IntPtr ob, IntPtr args, IntPtr kw) { // as the first argument. Note that this is not supported if any // of the overloads are static since we can't know if the intent // was to call the static method or the unbound instance method. + List disposeList = new List(); + try + { + IntPtr target = self.target; + + if ((target == IntPtr.Zero) && (!self.m.IsStatic())) + { + int len = Runtime.PyTuple_Size(args); + if (len < 1) + { + Exceptions.SetError(Exceptions.TypeError, "not enough arguments"); + return IntPtr.Zero; + } + target = Runtime.PyTuple_GetItem(args, 0); + Runtime.Incref(target); + disposeList.Add(target); + + args = Runtime.PyTuple_GetSlice(args, 1, len); + disposeList.Add(args); + } - if ((self.target == IntPtr.Zero) && (!self.m.IsStatic())) - { - int len = Runtime.PyTuple_Size(args); - if (len < 1) - { - Exceptions.SetError(Exceptions.TypeError, "not enough arguments"); - return IntPtr.Zero; - } - IntPtr uargs = Runtime.PyTuple_GetSlice(args, 1, len); - IntPtr inst = Runtime.PyTuple_GetItem(args, 0); - Runtime.Incref(inst); - IntPtr r = self.m.Invoke(inst, uargs, kw, self.info); - Runtime.Decref(inst); - Runtime.Decref(uargs); - return r; - } + // if the class is a IPythonDerivedClass and target is not the same as self.targetType + // (eg if calling the base class method) then call the original base class method instead + // of the target method. + IntPtr superType = IntPtr.Zero; + if (Runtime.PyObject_TYPE(target) != self.targetType) + { + CLRObject inst = CLRObject.GetManagedObject(target) as CLRObject; + if (inst != null && (inst.inst as IPythonDerivedType) != null) + { + ClassBase baseType = GetManagedObject(self.targetType) as ClassBase; + if (baseType != null) + { + string baseMethodName = "_" + baseType.type.Name + "__" + self.m.name; + IntPtr baseMethod = Runtime.PyObject_GetAttrString(target, baseMethodName); + if (baseMethod != IntPtr.Zero) + { + MethodBinding baseSelf = GetManagedObject(baseMethod) as MethodBinding; + if (baseSelf != null) + self = baseSelf; + Runtime.Decref(baseMethod); + } + else + { + Runtime.PyErr_Clear(); + } + } + } + } - return self.m.Invoke(self.target, args, kw, self.info); + return self.m.Invoke(target, args, kw, self.info); + } + finally + { + foreach (IntPtr ptr in disposeList) + Runtime.Decref(ptr); + } } @@ -184,6 +234,7 @@ public static IntPtr tp_repr(IntPtr ob) { public static new void tp_dealloc(IntPtr ob) { MethodBinding self = (MethodBinding)GetManagedObject(ob); Runtime.Decref(self.target); + Runtime.Decref(self.targetType); ExtensionType.FinalizeObject(self); } diff --git a/src/runtime/methodobject.cs b/src/runtime/methodobject.cs index 15a5cd547..45e7de709 100644 --- a/src/runtime/methodobject.cs +++ b/src/runtime/methodobject.cs @@ -27,19 +27,21 @@ internal class MethodObject : ExtensionType { internal MethodBinder binder; internal bool is_static = false; internal IntPtr doc; + internal Type type; - public MethodObject(string name, MethodInfo[] info) : base() { - _MethodObject(name, info); + public MethodObject(Type type, string name, MethodInfo[] info) : base() { + _MethodObject(type, name, info); } - public MethodObject(string name, MethodInfo[] info, bool allow_threads) : base() + public MethodObject(Type type, string name, MethodInfo[] info, bool allow_threads) : base() { - _MethodObject(name, info); + _MethodObject(type, name, info); binder.allow_threads = allow_threads; } - private void _MethodObject(string name, MethodInfo[] info) + private void _MethodObject(Type type, string name, MethodInfo[] info) { + this.type = type; this.name = name; this.info = info; binder = new MethodBinder(); @@ -144,7 +146,7 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp) { if (ob == IntPtr.Zero) { if (self.unbound == null) { - self.unbound = new MethodBinding(self, IntPtr.Zero); + self.unbound = new MethodBinding(self, IntPtr.Zero, tp); } binding = self.unbound; Runtime.Incref(binding.pyHandle);; @@ -155,7 +157,22 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp) { return Exceptions.RaiseTypeError("invalid argument"); } - binding = new MethodBinding(self, ob); + // If the object this descriptor is being called with is a subclass of the type + // this descriptor was defined on then it will be because the base class method + // is being called via super(Derived, self).method(...). + // In which case create a MethodBinding bound to the base class. + CLRObject obj = GetManagedObject(ob) as CLRObject; + if (obj != null + && obj.inst.GetType() != self.type + && obj.inst is IPythonDerivedType + && self.type.IsAssignableFrom(obj.inst.GetType())) + { + ClassBase basecls = ClassManager.GetClass(self.type); + binding = new MethodBinding(self, ob, basecls.pyHandle); + return binding.pyHandle; + } + + binding = new MethodBinding(self, ob, tp); return binding.pyHandle; } diff --git a/src/runtime/methodwrapper.cs b/src/runtime/methodwrapper.cs index 04a49d592..486e5c59d 100644 --- a/src/runtime/methodwrapper.cs +++ b/src/runtime/methodwrapper.cs @@ -34,9 +34,13 @@ public MethodWrapper(Type type, string name) { // XXX - here we create a Python string object, then take the // char * of the internal string to pass to our methoddef // structure. Its a hack, and the name is leaked! - +#if (PYTHON32 || PYTHON33 || PYTHON34) + IntPtr ps = Runtime.PyBytes_FromString(name); + IntPtr sp = Runtime.PyBytes_AS_STRING(ps); +#else IntPtr ps = Runtime.PyString_FromString(name); IntPtr sp = Runtime.PyString_AS_STRING(ps); +#endif // Allocate and initialize a PyMethodDef structure to represent // the managed method, then create a PyCFunction. @@ -44,9 +48,9 @@ public MethodWrapper(Type type, string name) { mdef = Runtime.PyMem_Malloc(4 * IntPtr.Size); Marshal.WriteIntPtr(mdef, sp); Marshal.WriteIntPtr(mdef, (1 * IntPtr.Size), fp); - Marshal.WriteIntPtr(mdef, (2 * IntPtr.Size), (IntPtr)0x0002); + Marshal.WriteIntPtr(mdef, (2 * IntPtr.Size), (IntPtr)0x0003); // METH_VARARGS | METH_KEYWORDS Marshal.WriteIntPtr(mdef, (3 * IntPtr.Size), IntPtr.Zero); - ptr = Runtime.PyCFunction_New(mdef, IntPtr.Zero); + ptr = Runtime.PyCFunction_NewEx(mdef, IntPtr.Zero, IntPtr.Zero); } public IntPtr Call(IntPtr args, IntPtr kw) { diff --git a/src/runtime/modulefunctionobject.cs b/src/runtime/modulefunctionobject.cs index 5c9a4de21..2aa8c2306 100644 --- a/src/runtime/modulefunctionobject.cs +++ b/src/runtime/modulefunctionobject.cs @@ -19,8 +19,8 @@ namespace Python.Runtime internal class ModuleFunctionObject : MethodObject { - public ModuleFunctionObject(string name, MethodInfo[] info, bool allow_threads) - : base(name, info, allow_threads) + public ModuleFunctionObject(Type type, string name, MethodInfo[] info, bool allow_threads) + : base(type, name, info, allow_threads) { for (int i = 0; i < info.Length; i++) { diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index c5735ca4a..5cd6f2af5 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -37,14 +37,29 @@ public ModuleObject(string name) : base() { 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. + string filename = "unknown"; + string docstring = "Namespace containing types from the following assemblies:\n\n"; + foreach (Assembly a in AssemblyManager.GetAssemblies(name)) { + 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(this.GetType()); Runtime.PyDict_SetItemString(dict, "__name__", pyname); - Runtime.PyDict_SetItemString(dict, "__file__", Runtime.PyNone); - Runtime.PyDict_SetItemString(dict, "__doc__", Runtime.PyNone); + Runtime.PyDict_SetItemString(dict, "__file__", pyfilename); + Runtime.PyDict_SetItemString(dict, "__doc__", pydocstring); + Runtime.PyDict_SetItemString(dict, "__class__", pycls); Runtime.Decref(pyname); + Runtime.Decref(pyfilename); + Runtime.Decref(pydocstring); - Marshal.WriteIntPtr(this.pyHandle, ObjectOffset.ob_dict, dict); + Marshal.WriteIntPtr(this.pyHandle, ObjectOffset.DictOffset(this.pyHandle), dict); InitializeModuleMembers(); } @@ -216,7 +231,7 @@ internal void InitializeModuleMembers() string name = method.Name; MethodInfo[] mi = new MethodInfo[1]; mi[0] = method; - ModuleFunctionObject m = new ModuleFunctionObject(name, mi, allow_threads); + ModuleFunctionObject m = new ModuleFunctionObject(type, name, mi, allow_threads); StoreAttribute(name, m); } } @@ -425,6 +440,12 @@ public static String[] ListAssemblies(bool verbose) return names; } + [ModuleFunctionAttribute()] + public static int _AtExit() + { + return Runtime.AtExit(); + } + } } diff --git a/src/runtime/pydict.cs b/src/runtime/pydict.cs index cd85c7126..e2ceaa6d6 100644 --- a/src/runtime/pydict.cs +++ b/src/runtime/pydict.cs @@ -102,7 +102,8 @@ public bool HasKey(PyObject key) { /// public bool HasKey(string key) { - return HasKey(new PyString(key)); + using (PyString str = new PyString(key)) + return HasKey(str); } diff --git a/src/runtime/pyfloat.cs b/src/runtime/pyfloat.cs index 960892594..c6995887c 100644 --- a/src/runtime/pyfloat.cs +++ b/src/runtime/pyfloat.cs @@ -76,10 +76,11 @@ public PyFloat(double value) : base() { /// public PyFloat(string value) : base() { - PyString s = new PyString(value); - obj = Runtime.PyFloat_FromString(s.obj, IntPtr.Zero); - if (obj == IntPtr.Zero) { - throw new PythonException(); + using (PyString s = new PyString(value)) { + obj = Runtime.PyFloat_FromString(s.obj, IntPtr.Zero); + if (obj == IntPtr.Zero) { + throw new PythonException(); + } } } diff --git a/src/runtime/pyiter.cs b/src/runtime/pyiter.cs index 8d8ad44d7..c8599c2a3 100644 --- a/src/runtime/pyiter.cs +++ b/src/runtime/pyiter.cs @@ -21,42 +21,57 @@ public class PyIter : PyObject, IEnumerator private PyObject _current = null; /// - /// PyIter Constructor - /// - /// - /// - /// Creates a new PyIter from an existing iterator reference. Note - /// that the instance assumes ownership of the object reference. - /// The object reference is not checked for type-correctness. - /// + /// PyIter Constructor + /// + /// + /// + /// Creates a new PyIter from an existing iterator reference. Note + /// that the instance assumes ownership of the object reference. + /// The object reference is not checked for type-correctness. + /// - public PyIter(IntPtr ptr) : base(ptr) {} + public PyIter(IntPtr ptr) : base(ptr) {} /// - /// PyIter Constructor - /// - /// - /// - /// Creates a Python iterator from an iterable. Like doing "iter(iterable)" in python. - /// + /// PyIter Constructor + /// + /// + /// + /// Creates a Python iterator from an iterable. Like doing "iter(iterable)" in python. + /// - public PyIter(PyObject iterable) : base() + public PyIter(PyObject iterable) : base() { obj = Runtime.PyObject_GetIter(iterable.obj); if (obj == IntPtr.Zero) throw new PythonException(); } + protected override void Dispose(bool disposing) + { + if (null != _current) + { + _current.Dispose(); + _current = null; + } + base.Dispose(disposing); + } + #region IEnumerator Members public bool MoveNext() { + // dispose of the previous object, if there was one + if (null != _current) + { + _current.Dispose(); + _current = null; + } + IntPtr next = Runtime.PyIter_Next(obj); if (next == IntPtr.Zero) - { - _current = null; //release reference return false; - } + _current = new PyObject(next); return true; } diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 099b9fdf4..33c716599 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -8,6 +8,9 @@ // ========================================================================== using System; +using System.Dynamic; +using System.Linq.Expressions; +using System.Collections; namespace Python.Runtime { @@ -17,7 +20,7 @@ namespace Python.Runtime { /// http://www.python.org/doc/current/api/object.html for details. /// - public class PyObject : IDisposable { + public class PyObject : DynamicObject, IDisposable { protected internal IntPtr obj = IntPtr.Zero; private bool disposed = false; @@ -95,7 +98,7 @@ public static PyObject FromManagedObject(object ob) { public object AsManagedObject(Type t) { Object result; - if (!Converter.ToManaged(this.Handle, t, out result, false)) { + if (!Converter.ToManaged(this.obj, t, out result, false)) { throw new InvalidCastException("cannot convert object to target type"); } return result; @@ -115,19 +118,23 @@ public object AsManagedObject(Type t) { /// collection occurs. /// - public void Dispose() { + protected virtual void Dispose(bool disposing) { if (!disposed) { if (Runtime.Py_IsInitialized() > 0) { IntPtr gs = PythonEngine.AcquireLock(); Runtime.Decref(obj); - obj = IntPtr.Zero; + obj = IntPtr.Zero; PythonEngine.ReleaseLock(gs); } - GC.SuppressFinalize(this); disposed = true; } } + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + /// /// GetPythonType Method @@ -361,7 +368,8 @@ public virtual PyObject GetItem(PyObject key) { /// public virtual PyObject GetItem(string key) { - return GetItem(new PyString(key)); + using (PyString pyKey = new PyString(key)) + return GetItem(pyKey); } @@ -410,7 +418,8 @@ public virtual void SetItem(PyObject key, PyObject value) { /// public virtual void SetItem(string key, PyObject value) { - SetItem(new PyString(key), value); + using (PyString pyKey = new PyString(key)) + SetItem(pyKey, value); } @@ -425,7 +434,9 @@ public virtual void SetItem(string key, PyObject value) { /// public virtual void SetItem(int index, PyObject value) { - SetItem(new PyInt(index), value); + using (PyInt pyindex = new PyInt(index)) { + SetItem(pyindex, value); + } } @@ -458,7 +469,8 @@ public virtual void DelItem(PyObject key) { /// public virtual void DelItem(string key) { - DelItem(new PyString(key)); + using (PyString pyKey = new PyString(key)) + DelItem(pyKey); } @@ -473,7 +485,8 @@ public virtual void DelItem(string key) { /// public virtual void DelItem(int index) { - DelItem(new PyInt(index)); + using (PyInt pyindex = new PyInt(index)) + DelItem(pyindex); } @@ -559,6 +572,21 @@ public PyObject GetIterator() { return new PyObject(r); } + /// + /// GetEnumerator Method + /// + /// + /// + /// Return a new PyIter object for the object. This allows any iterable + /// python object to be iterated over in C#. A PythonException will be + /// raised if the object is not iterable. + /// + + public IEnumerator GetEnumerator() + { + return new PyIter(this); + } + /// /// Invoke Method @@ -609,7 +637,7 @@ public PyObject Invoke(PyTuple args) { public PyObject Invoke(PyObject[] args, PyDict kw) { PyTuple t = new PyTuple(args); - IntPtr r = Runtime.PyObject_Call(obj, t.obj, kw.obj); + IntPtr r = Runtime.PyObject_Call(obj, t.obj, kw != null ? kw.obj : IntPtr.Zero); t.Dispose(); if (r == IntPtr.Zero) { throw new PythonException(); @@ -628,7 +656,7 @@ public PyObject Invoke(PyObject[] args, PyDict kw) { /// public PyObject Invoke(PyTuple args, PyDict kw) { - IntPtr r = Runtime.PyObject_Call(obj, args.obj, kw.obj); + IntPtr r = Runtime.PyObject_Call(obj, args.obj, kw != null ? kw.obj : IntPtr.Zero); if (r == IntPtr.Zero) { throw new PythonException(); } @@ -758,6 +786,22 @@ public bool IsCallable() { } + /// + /// IsIterable Method + /// + /// + /// + /// Returns true if the object is iterable object. This method + /// always succeeds. + /// + + public bool IsIterable() + { + return Runtime.PyIter_Check(obj); + } + + + /// /// IsTrue Method /// @@ -862,8 +906,242 @@ public override int GetHashCode() { return Runtime.PyObject_Hash(obj).ToInt32(); } + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (this.HasAttr(binder.Name)) + { + result = this.GetAttr(binder.Name); + return true; + } + else + return base.TryGetMember(binder, out result); + } + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (this.HasAttr(binder.Name)) + { + this.SetAttr(binder.Name, (PyObject)value); + return true; + } + else + return base.TrySetMember(binder, value); + } + private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs) + { + int arg_count; + for (arg_count = 0; arg_count < inargs.Length && !(inargs[arg_count] is Py.KeywordArguments); ++arg_count); + IntPtr argtuple = Runtime.PyTuple_New(arg_count); + for (int i = 0; i < arg_count; i++) + { + IntPtr ptr; + if (inargs[i] is PyObject) + { + ptr = ((PyObject)inargs[i]).Handle; + Runtime.Incref(ptr); + } + else + { + ptr = Converter.ToPython(inargs[i], inargs[i].GetType()); + } + if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0) + throw new PythonException(); + } + args = new PyTuple(argtuple); + kwargs = null; + for (int i = arg_count; i < inargs.Length; i++) + { + if (!(inargs[i] is Py.KeywordArguments)) + throw new ArgumentException("Keyword arguments must come after normal arguments."); + if (kwargs == null) + kwargs = (Py.KeywordArguments)inargs[i]; + else + kwargs.Update((Py.KeywordArguments)inargs[i]); + } } + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + if (this.HasAttr(binder.Name) && this.GetAttr(binder.Name).IsCallable()) + { + PyTuple pyargs = null; + PyDict kwargs = null; + try + { + GetArgs(args, out pyargs, out kwargs); + result = InvokeMethod(binder.Name, pyargs, kwargs); + } + finally + { + if (null != pyargs) + pyargs.Dispose(); + if (null != kwargs) + kwargs.Dispose(); + } + return true; + } + else + return base.TryInvokeMember(binder, args, out result); + } + public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) + { + if (this.IsCallable()) + { + PyTuple pyargs = null; + PyDict kwargs = null; + try + { + GetArgs(args, out pyargs, out kwargs); + result = Invoke(pyargs, kwargs); + } + finally + { + if (null != pyargs) + pyargs.Dispose(); + if (null != kwargs) + kwargs.Dispose(); + } + return true; + } + else + return base.TryInvoke(binder, args, out result); + } + + public override bool TryConvert(ConvertBinder binder, out object result) + { + return Converter.ToManaged(this.obj, binder.Type, out result, false); + } + + public override bool TryBinaryOperation(BinaryOperationBinder binder, Object arg, out Object result) { + IntPtr res; + if (!(arg is PyObject)) + arg = arg.ToPython(); + + switch (binder.Operation) + { + case ExpressionType.Add: + res = Runtime.PyNumber_Add(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.AddAssign: + res = Runtime.PyNumber_InPlaceAdd(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.Subtract: + res = Runtime.PyNumber_Subtract(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.SubtractAssign: + res = Runtime.PyNumber_InPlaceSubtract(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.Multiply: + res = Runtime.PyNumber_Multiply(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.MultiplyAssign: + res = Runtime.PyNumber_InPlaceMultiply(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.Divide: + res = Runtime.PyNumber_Divide(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.DivideAssign: + res = Runtime.PyNumber_InPlaceDivide(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.And: + res = Runtime.PyNumber_And(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.AndAssign: + res = Runtime.PyNumber_InPlaceAnd(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.ExclusiveOr: + res = Runtime.PyNumber_Xor(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.ExclusiveOrAssign: + res = Runtime.PyNumber_InPlaceXor(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.GreaterThan: + result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) > 0; + return true; + case ExpressionType.GreaterThanOrEqual: + result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) >= 0; + return true; + case ExpressionType.LeftShift: + res = Runtime.PyNumber_Lshift(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.LeftShiftAssign: + res = Runtime.PyNumber_InPlaceLshift(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.LessThan: + result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) < 0; + return true; + case ExpressionType.LessThanOrEqual: + result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) <= 0; + return true; + case ExpressionType.Modulo: + res = Runtime.PyNumber_Remainder(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.ModuloAssign: + res = Runtime.PyNumber_InPlaceRemainder(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.NotEqual: + result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) != 0; + return true; + case ExpressionType.Or: + res = Runtime.PyNumber_Or(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.OrAssign: + res = Runtime.PyNumber_InPlaceOr(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.Power: + res = Runtime.PyNumber_Power(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.RightShift: + res = Runtime.PyNumber_Rshift(this.obj, ((PyObject)arg).obj); + break; + case ExpressionType.RightShiftAssign: + res = Runtime.PyNumber_InPlaceRshift(this.obj, ((PyObject)arg).obj); + break; + default: + result = null; + return false; + } + result = new PyObject(res); + return true; + } + + public override bool TryUnaryOperation(UnaryOperationBinder binder, out Object result) + { + int r; + IntPtr res; + switch (binder.Operation) + { + case ExpressionType.Negate: + res = Runtime.PyNumber_Negative(this.obj); + break; + case ExpressionType.UnaryPlus: + res = Runtime.PyNumber_Positive(this.obj); + break; + case ExpressionType.OnesComplement: + res = Runtime.PyNumber_Invert(this.obj); + break; + case ExpressionType.Not: + r = Runtime.PyObject_Not(this.obj); + result = r == 1; + return r != -1; + case ExpressionType.IsFalse: + r = Runtime.PyObject_IsTrue(this.obj); + result = r == 0; + return r != -1; + case ExpressionType.IsTrue: + r = Runtime.PyObject_IsTrue(this.obj); + result = r == 1; + return r != -1; + case ExpressionType.Decrement: + case ExpressionType.Increment: + default: + result = null; + return false; + } + result = new PyObject(res); + return true; + } + } } diff --git a/src/runtime/pysequence.cs b/src/runtime/pysequence.cs index 9b41c308b..855cad72e 100644 --- a/src/runtime/pysequence.cs +++ b/src/runtime/pysequence.cs @@ -158,15 +158,6 @@ public PyObject Repeat(int count) { } return new PyObject(op); } - - #region IEnumerable Members - - public IEnumerator GetEnumerator() - { - return new PyIter(this); - } - - #endregion } } diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 07326185f..1a84eb198 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -8,7 +8,9 @@ // ========================================================================== using System; +using System.IO; using System.Threading; +using System.Reflection; namespace Python.Runtime { @@ -64,6 +66,22 @@ public static string PythonHome { } } + public static string PythonPath { + get + { + string result = Runtime.Py_GetPath(); + if (result == null) + { + return ""; + } + return result; + } + set + { + Runtime.Py_SetPath(value); + } + } + public static string Version { get { return Runtime.Py_GetVersion(); @@ -117,55 +135,122 @@ public static void Initialize() { Runtime.Initialize(); initialized = true; Exceptions.Clear(); + + // register the atexit callback (this doesn't use Py_AtExit as the C atexit + // callbacks are called after python is fully finalized but the python ones + // are called while the python engine is still running). + string code = + "import atexit, clr\n" + + "atexit.register(clr._AtExit)\n"; + PyObject r = PythonEngine.RunString(code); + if (r != null) + r.Dispose(); + + // Load the clr.py resource into the clr module + IntPtr clr = Python.Runtime.ImportHook.GetCLRModule(); + IntPtr clr_dict = Runtime.PyModule_GetDict(clr); + + PyDict locals = new PyDict(); + try + { + IntPtr module = Runtime.PyImport_AddModule("clr._extras"); + IntPtr module_globals = Runtime.PyModule_GetDict(module); + IntPtr builtins = Runtime.PyEval_GetBuiltins(); + Runtime.PyDict_SetItemString(module_globals, "__builtins__", builtins); + + var assembly = Assembly.GetExecutingAssembly(); + using (Stream stream = assembly.GetManifestResourceStream("clr.py")) + using (StreamReader reader = new StreamReader(stream)) + { + // add the contents of clr.py to the module + string clr_py = reader.ReadToEnd(); + PyObject result = RunString(clr_py, module_globals, locals.Handle); + if (null == result) + throw new PythonException(); + result.Dispose(); + } + + // add the imported module to the clr module, and copy the API functions + // and decorators into the main clr module. + Runtime.PyDict_SetItemString(clr_dict, "_extras", module); + foreach (PyObject key in locals.Keys()) + { + if (!key.ToString().StartsWith("_")){ + PyObject value = locals[key]; + Runtime.PyDict_SetItem(clr_dict, key.Handle, value.Handle); + value.Dispose(); + } + key.Dispose(); + } + } + finally + { + locals.Dispose(); + } } } - //==================================================================== // A helper to perform initialization from the context of an active // CPython interpreter process - this bootstraps the managed runtime // when it is imported by the CLR extension module. //==================================================================== - +#if (PYTHON32 || PYTHON33 || PYTHON34) + public static IntPtr InitExt() { +#else public static void InitExt() { - Initialize(); - - // Trickery - when the import hook is installed into an already - // running Python, the standard import machinery is still in - // control for the duration of the import that caused bootstrap. - // - // That is problematic because the std machinery tries to get - // sub-names directly from the module __dict__ rather than going - // through our module object's getattr hook. This workaround is - // evil ;) We essentially climb up the stack looking for the - // import that caused the bootstrap to happen, then re-execute - // the import explicitly after our hook has been installed. By - // doing this, the original outer import should work correctly. - // - // Note that this is only needed during the execution of the - // first import that installs the CLR import hook. This hack - // still doesn't work if you use the interactive interpreter, - // since there is no line info to get the import line ;( - - string code = - - "import traceback\n" + - "for item in traceback.extract_stack():\n" + - " line = item[3]\n" + - " if line is not None:\n" + - " if line.startswith('import CLR') or \\\n" + - " line.startswith('import clr') or \\\n" + - " line.startswith('from clr') or \\\n" + - " line.startswith('from CLR'):\n" + - " exec line\n" + - " break\n"; - - PyObject r = PythonEngine.RunString(code); - if (r != null) { - r.Dispose(); +#endif + try + { + Initialize(); + + // Trickery - when the import hook is installed into an already + // running Python, the standard import machinery is still in + // control for the duration of the import that caused bootstrap. + // + // That is problematic because the std machinery tries to get + // sub-names directly from the module __dict__ rather than going + // through our module object's getattr hook. This workaround is + // evil ;) We essentially climb up the stack looking for the + // import that caused the bootstrap to happen, then re-execute + // the import explicitly after our hook has been installed. By + // doing this, the original outer import should work correctly. + // + // Note that this is only needed during the execution of the + // first import that installs the CLR import hook. This hack + // still doesn't work if you use the interactive interpreter, + // since there is no line info to get the import line ;( + + string code = + + "import traceback\n" + + "for item in traceback.extract_stack():\n" + + " line = item[3]\n" + + " if line is not None:\n" + + " if line.startswith('import CLR') or \\\n" + + " line.startswith('import clr') or \\\n" + + " line.startswith('from clr') or \\\n" + + " line.startswith('from CLR'):\n" + + " exec(line)\n" + + " break\n"; + + PyObject r = PythonEngine.RunString(code); + if (r != null) { + r.Dispose(); + } + } + catch (PythonException e) + { + e.Restore(); +#if (PYTHON32 || PYTHON33 || PYTHON34) + return IntPtr.Zero; +#endif } - } +#if (PYTHON32 || PYTHON33 || PYTHON34) + return Python.Runtime.ImportHook.GetCLRModule(); +#endif + } /// /// Shutdown Method @@ -347,9 +432,70 @@ public static PyObject RunString(string code) { return new PyObject(result); } + public static PyObject RunString(string code, IntPtr globals, IntPtr locals) + { + IntPtr flag = (IntPtr)257; /* Py_file_input */ + IntPtr result = Runtime.PyRun_String(code, flag, globals, locals); + if (result == IntPtr.Zero) { + return null; + } + return new PyObject(result); + } + } + public static class Py + { + public static GILState GIL() + { + if (!PythonEngine.IsInitialized) + PythonEngine.Initialize(); - } + return new GILState(); + } + public class GILState : IDisposable + { + private IntPtr state; + internal GILState() + { + state = PythonEngine.AcquireLock(); + } + public void Dispose() + { + PythonEngine.ReleaseLock(state); + GC.SuppressFinalize(this); + } + ~GILState() + { + Dispose(); + } + } + + public class KeywordArguments : PyDict { } + + public static KeywordArguments kw(params object[] kv) + { + var dict = new KeywordArguments(); + if (kv.Length % 2 != 0) + throw new ArgumentException("Must have an equal number of keys and values"); + for (int i = 0; i < kv.Length; i += 2) + { + IntPtr value; + if (kv[i + 1] is PyObject) + value = ((PyObject)kv[i + 1]).Handle; + else + value = Converter.ToPython(kv[i + 1], kv[i + 1].GetType()); + if (Runtime.PyDict_SetItemString(dict.Handle, (string)kv[i], value) != 0) + throw new ArgumentException(string.Format("Cannot add key '{0}' to dictionary.", (string)kv[i])); + if (!(kv[i + 1] is PyObject)) + Runtime.Decref(value); + } + return dict; + } + public static PyObject Import(string name) + { + return PythonEngine.ImportModule(name); + } + } } diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index 592e9ea37..0d0b2e3e6 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -34,14 +34,21 @@ public PythonException() : base() Runtime.Incref(_pyTB); if ((_pyType != IntPtr.Zero) && (_pyValue != IntPtr.Zero)) { - string type = new PyObject(_pyType).GetAttr("__name__").ToString(); + string type; + using (PyObject pyType = new PyObject(_pyType)) + using (PyObject pyTypeName = pyType.GetAttr("__name__")) + { + type = pyTypeName.ToString(); + } string message = Runtime.GetManagedString(_pyValue); _message = type + " : " + message; } if (_pyTB != IntPtr.Zero) { PyObject tb_module = PythonEngine.ImportModule("traceback"); - _tb = tb_module.InvokeMethod("format_tb", new PyObject(_pyTB)).ToString(); + using (PyObject pyTB = new PyObject(_pyTB)) { + _tb = tb_module.InvokeMethod("format_tb", pyTB).ToString(); + } } PythonEngine.ReleaseLock(gs); } @@ -53,6 +60,18 @@ public PythonException() : base() Dispose(); } + /// + /// Restores python error. + /// + public void Restore() + { + IntPtr gs = PythonEngine.AcquireLock(); + Runtime.PyErr_Restore(_pyType, _pyValue, _pyTB); + _pyType = IntPtr.Zero; + _pyValue = IntPtr.Zero; + _pyTB = IntPtr.Zero; + PythonEngine.ReleaseLock(gs); + } /// /// PyType Property diff --git a/src/runtime/resources/clr.py b/src/runtime/resources/clr.py new file mode 100644 index 000000000..1fbd272b5 --- /dev/null +++ b/src/runtime/resources/clr.py @@ -0,0 +1,83 @@ +""" +Code in this module gets loaded into the main clr module. +""" + +class clrproperty(object): + """ + Property decorator for exposing python properties to .NET. + The property type must be specified as the only argument to clrproperty. + + e.g.:: + + class X(object): + @clrproperty(string) + def test(self): + return "x" + + Properties decorated this way can be called from .NET, e.g.:: + + dynamic x = getX(); // get an instance of X declared in Python + string z = x.test; // calls into python and returns "x" + """ + + def __init__(self, type_, fget=None, fset=None): + self.__name__ = getattr(fget, "__name__", None) + self._clr_property_type_ = type_ + self.fget = fget + self.fset = fset + + def __call__(self, fget): + return self.__class__(self._clr_property_type_, + fget=fget, + fset=self.fset) + + def setter(self, fset): + self.fset = fset + return self + + def getter(self, fget): + self.fget = fget + return self + + def __get__(self, instance, owner): + return self.fget.__get__(instance, owner)() + + def __set__(self, instance, value): + if not self.fset: + raise AttributeError("%s is read-only" % self.__name__) + return self.fset.__get__(instance, None)(value) + + +class clrmethod(object): + """ + Method decorator for exposing python methods to .NET. + The argument and return types must be specified as arguments to clrmethod. + + e.g.:: + + class X(object): + @clrmethod(int, [str]) + def test(self, x): + return len(x) + + Methods decorated this way can be called from .NET, e.g.:: + + dynamic x = getX(); // get an instance of X declared in Python + int z = x.test("hello"); // calls into python and returns len("hello") + """ + + def __init__(self, return_type, arg_types, clrname=None, func=None): + self.__name__ = getattr(func, "__name__", None) + self._clr_return_type_ = return_type + self._clr_arg_types_ = arg_types + self._clr_method_name_ = clrname or self.__name__ + self.__func = func + + def __call__(self, func): + return self.__class__(self._clr_return_type_, + self._clr_arg_types_, + clrname=self._clr_method_name_, + func=func) + + def __get__(self, instance, owner): + return self.__func.__get__(instance, owner) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 19aa2ca2d..cce085223 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -15,10 +15,86 @@ using Mono.Unix; #endif +#if (UCS2 && (PYTHON32 || PYTHON33 || PYTHON34)) +using System.Text; +#endif + namespace Python.Runtime { [SuppressUnmanagedCodeSecurityAttribute()] + static class NativeMethods + { +#if (MONO_LINUX || MONO_OSX) + static public IntPtr LoadLibrary(string fileName) { + return dlopen(fileName, RTLD_NOW | RTLD_SHARED); + } + + static public void FreeLibrary(IntPtr handle) { + dlclose(handle); + } + + static public IntPtr GetProcAddress(IntPtr dllHandle, string name) { + // look in the exe if dllHandle is NULL + if (IntPtr.Zero == dllHandle) + dllHandle = RTLD_DEFAULT; + + // clear previous errors if any + dlerror(); + var res = dlsym(dllHandle, name); + var errPtr = dlerror(); + if (errPtr != IntPtr.Zero) { + throw new Exception("dlsym: " + Marshal.PtrToStringAnsi(errPtr)); + } + return res; + } + +#if (MONO_OSX) + static int RTLD_NOW = 0x2; + static int RTLD_SHARED = 0x20; + static IntPtr RTLD_DEFAULT = new IntPtr(-2); + + [DllImport("__Internal")] + private static extern IntPtr dlopen(String fileName, int flags); + + [DllImport("__Internal")] + private static extern IntPtr dlsym(IntPtr handle, String symbol); + + [DllImport("__Internal")] + private static extern int dlclose(IntPtr handle); + + [DllImport("__Internal")] + private static extern IntPtr dlerror(); +#else + static int RTLD_NOW = 0x2; + static int RTLD_SHARED = 0x20; + static IntPtr RTLD_DEFAULT = IntPtr.Zero; + + [DllImport("libdl.so")] + private static extern IntPtr dlopen(String fileName, int flags); + + [DllImport("libdl.so")] + private static extern IntPtr dlsym(IntPtr handle, String symbol); + + [DllImport("libdl.so")] + private static extern int dlclose(IntPtr handle); + + [DllImport("libdl.so")] + private static extern IntPtr dlerror(); +#endif + +#else + [DllImport("kernel32.dll")] + public static extern IntPtr LoadLibrary(string dllToLoad); + + [DllImport("kernel32.dll")] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); + + [DllImport("kernel32.dll")] + public static extern bool FreeLibrary(IntPtr hModule); +#endif + } + public class Runtime { /// @@ -37,34 +113,104 @@ public class Runtime { #endif #if (PYTHON23) - public const string dll = "python23"; public const string pyversion = "2.3"; public const int pyversionnumber = 23; #endif #if (PYTHON24) - public const string dll = "python24"; public const string pyversion = "2.4"; public const int pyversionnumber = 24; #endif #if (PYTHON25) - public const string dll = "python25"; public const string pyversion = "2.5"; public const int pyversionnumber = 25; #endif #if (PYTHON26) - public const string dll = "python26"; public const string pyversion = "2.6"; public const int pyversionnumber = 26; #endif #if (PYTHON27) - public const string dll = "python27"; public const string pyversion = "2.7"; public const int pyversionnumber = 27; #endif -#if ! (PYTHON23 || PYTHON24 || PYTHON25 || PYTHON26 || PYTHON27) -#error You must define one of PYTHON23 to PYTHON27 +#if (PYTHON32) + public const string pyversion = "3.2"; + public const int pyversionnumber = 32; +#endif +#if (PYTHON33) + public const string pyversion = "3.3"; + public const int pyversionnumber = 33; +#endif +#if (PYTHON34) + public const string pyversion = "3.4"; + public const int pyversionnumber = 34; +#endif +#if ! (PYTHON23 || PYTHON24 || PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) +#error You must define one of PYTHON23 to PYTHON34 +#endif + +#if (PYTHON23) + internal const string dllBase = "python23"; +#endif +#if (PYTHON24) + internal const string dllBase = "python24"; +#endif +#if (PYTHON25) + internal const string dllBase = "python25"; +#endif +#if (PYTHON26) + internal const string dllBase = "python26"; +#endif +#if (PYTHON27) + internal const string dllBase = "python27"; +#endif +#if (MONO_LINUX || MONO_OSX) +#if (PYTHON32) + internal const string dllBase = "python3.2"; +#endif +#if (PYTHON33) + internal const string dllBase = "python3.3"; +#endif +#if (PYTHON34) + internal const string dllBase = "python3.4"; +#endif +#else +#if (PYTHON32) + internal const string dllBase = "python32"; +#endif +#if (PYTHON33) + internal const string dllBase = "python33"; +#endif +#if (PYTHON34) + internal const string dllBase = "python34"; +#endif +#endif + +#if (PYTHON_WITH_PYDEBUG) + internal const string dllWithPyDebug = "d"; +#else + internal const string dllWithPyDebug = ""; +#endif +#if (PYTHON_WITH_PYMALLOC) + internal const string dllWithPyMalloc = "m"; +#else + internal const string dllWithPyMalloc = ""; +#endif +#if (PYTHON_WITH_WIDE_UNICODE) + internal const string dllWithWideUnicode = "u"; +#else + internal const string dllWithWideUnicode = ""; +#endif + +#if (PYTHON_WITHOUT_ENABLE_SHARED) + public const string dll = "__Internal"; +#else + public const string dll = dllBase + dllWithPyDebug + dllWithPyMalloc + dllWithWideUnicode; #endif + // set to true when python is finalizing + internal static Object IsFinalizingLock = new Object(); + internal static bool IsFinalizing = false; + internal static bool wrap_exceptions; internal static bool is32bit; @@ -72,19 +218,27 @@ public class Runtime { /// Intitialize the runtime... /// internal static void Initialize() { - + is32bit = IntPtr.Size == 4; - if (0 == Runtime.Py_IsInitialized()) { + if (0 == Runtime.Py_IsInitialized()) + { Runtime.Py_Initialize(); } - // make sure threads are initialized even if python was initialized already - Runtime.PyEval_InitThreads(); + if (0 == Runtime.PyEval_ThreadsInitialized()) + { + Runtime.PyEval_InitThreads(); + } +#if (PYTHON32 || PYTHON33 || PYTHON34) + IntPtr op = Runtime.PyImport_ImportModule("builtins"); + IntPtr dict = Runtime.PyObject_GetAttrString(op, "__dict__"); + PyNotImplemented = Runtime.PyObject_GetAttrString(op, "NotImplemented"); +#else IntPtr dict = Runtime.PyImport_GetModuleDict(); IntPtr op = Runtime.PyDict_GetItemString(dict, "__builtin__"); - +#endif PyBaseObjectType = Runtime.PyObject_GetAttrString(op, "object"); PyModuleType = Runtime.PyObject_Type(op); @@ -100,6 +254,11 @@ internal static void Initialize() { PyMethodType = Runtime.PyObject_Type(op); Runtime.Decref(op); +#if (PYTHON32 || PYTHON33 || PYTHON34) + Runtime.Decref(dict); + Runtime.Decref(op); +#endif + op = Runtime.PyString_FromString("string"); PyStringType = Runtime.PyObject_Type(op); Runtime.Decref(op); @@ -108,6 +267,12 @@ internal static void Initialize() { PyUnicodeType = Runtime.PyObject_Type(op); Runtime.Decref(op); +#if (PYTHON32 || PYTHON33 || PYTHON34) + op = Runtime.PyBytes_FromString("bytes"); + PyBytesType = Runtime.PyObject_Type(op); + Runtime.Decref(op); +#endif + op = Runtime.PyTuple_New(0); PyTupleType = Runtime.PyObject_Type(op); Runtime.Decref(op); @@ -132,8 +297,13 @@ internal static void Initialize() { PyFloatType = Runtime.PyObject_Type(op); Runtime.Decref(op); +#if (PYTHON32 || PYTHON33 || PYTHON34) + PyClassType = IntPtr.Zero; + PyInstanceType = IntPtr.Zero; +#else IntPtr s = Runtime.PyString_FromString("_temp"); IntPtr d = Runtime.PyDict_New(); + IntPtr c = Runtime.PyClass_New(IntPtr.Zero, d, s); PyClassType = Runtime.PyObject_Type(c); @@ -144,14 +314,29 @@ internal static void Initialize() { Runtime.Decref(i); Runtime.Decref(c); Runtime.Decref(d); +#endif Error = new IntPtr(-1); +#if (PYTHON32 || PYTHON33 || PYTHON34) + IntPtr dll = IntPtr.Zero; + if ("__Internal" != Runtime.dll) { + NativeMethods.LoadLibrary(Runtime.dll); + } + _PyObject_NextNotImplemented = NativeMethods.GetProcAddress(dll, "_PyObject_NextNotImplemented"); +#if !(MONO_LINUX || MONO_OSX) + if (IntPtr.Zero != dll) { + NativeMethods.FreeLibrary(dll); + } +#endif +#endif + + // Determine whether we need to wrap exceptions for versions of // of the Python runtime that do not allow new-style classes to // be used as exceptions (Python versions 2.4 and lower). -#if (PYTHON25 || PYTHON26 || PYTHON27) +#if (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) wrap_exceptions = false; #else IntPtr m = PyImport_ImportModule("exceptions"); @@ -188,6 +373,14 @@ internal static void Shutdown() { Py_Finalize(); } + // called *without* the GIL aquired by clr._AtExit + internal static int AtExit() { + lock (IsFinalizingLock) { + IsFinalizing = true; + } + return 0; + } + internal static IntPtr Py_single_input = (IntPtr)256; internal static IntPtr Py_file_input = (IntPtr)257; internal static IntPtr Py_eval_input = (IntPtr)258; @@ -211,6 +404,17 @@ internal static void Shutdown() { internal static IntPtr PyNoneType; internal static IntPtr PyTypeType; +#if (PYTHON32 || PYTHON33 || PYTHON34) + internal static IntPtr PyBytesType; + internal static IntPtr PyNotImplemented; + internal const int Py_LT = 0; + internal const int Py_LE = 1; + internal const int Py_EQ = 2; + internal const int Py_NE = 3; + internal const int Py_GT = 4; + internal static IntPtr _PyObject_NextNotImplemented; +#endif + internal static IntPtr PyTrue; internal static IntPtr PyFalse; internal static IntPtr PyNone; @@ -362,6 +566,17 @@ internal unsafe static void Decref(IntPtr op) { #endif } + internal unsafe static long Refcount(IntPtr op) + { + void* p = (void*)op; + if ((void*)0 != p) + { + if (is32bit) { return (*(int*)p); } + else { return (*(long*)p); } + } + return 0; + } + #if (Py_DEBUG) // Py_IncRef and Py_DecRef are taking care of the extra payload // in Py_DEBUG builds of Python like _Py_RefTotal @@ -448,16 +663,28 @@ internal unsafe static extern void internal unsafe static extern IntPtr PyGILState_GetThisThreadState(); +#if (PYTHON32 || PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + public unsafe static extern int + Py_Main(int argc, [MarshalAsAttribute(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr)] string[] argv); +#else [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] public unsafe static extern int Py_Main(int argc, string[] argv); +#endif [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern void PyEval_InitThreads(); + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern int + PyEval_ThreadsInitialized(); + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern void @@ -504,25 +731,70 @@ internal unsafe static extern IntPtr PyEval_GetLocals(); +#if PYTHON32 || PYTHON33 || PYTHON34 [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] + [return: MarshalAs(UnmanagedType.LPWStr)] internal unsafe static extern string Py_GetProgramName(); [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern void - Py_SetProgramName(string name); + Py_SetProgramName([MarshalAsAttribute(UnmanagedType.LPWStr)]string name); [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] + [return: MarshalAs(UnmanagedType.LPWStr)] internal unsafe static extern string Py_GetPythonHome(); [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern void - Py_SetPythonHome(string home); + Py_SetPythonHome([MarshalAsAttribute(UnmanagedType.LPWStr)]string home); + + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + [return: MarshalAs(UnmanagedType.LPWStr)] + internal unsafe static extern string + Py_GetPath(); + + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern void + Py_SetPath([MarshalAsAttribute(UnmanagedType.LPWStr)]string home); +#else + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern string + Py_GetProgramName(); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern void + Py_SetProgramName(string name); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern string + Py_GetPythonHome(); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern void + Py_SetPythonHome(string home); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern string + Py_GetPath(); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern void + Py_SetPath(string home); +#endif [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] @@ -569,11 +841,6 @@ internal unsafe static extern IntPtr internal unsafe static extern IntPtr PyImport_ExecCodeModule(string name, IntPtr code); - [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, - ExactSpelling=true, CharSet=CharSet.Ansi)] - internal unsafe static extern IntPtr - PyCFunction_New(IntPtr ml, IntPtr self); - [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern IntPtr @@ -709,10 +976,42 @@ internal unsafe static extern IntPtr internal unsafe static extern IntPtr PyObject_CallObject(IntPtr pointer, IntPtr args); +#if (PYTHON32 || PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern int + PyObject_RichCompareBool(IntPtr value1, IntPtr value2, int opid); + + internal static int PyObject_Compare(IntPtr value1, IntPtr value2) { + int res; + res = PyObject_RichCompareBool(value1, value2, Py_LT); + if (-1 == res) + return -1; + else if (1 == res) + return -1; + + res = PyObject_RichCompareBool(value1, value2, Py_EQ); + if (-1 == res) + return -1; + else if (1 == res) + return 0; + + res = PyObject_RichCompareBool(value1, value2, Py_GT); + if (-1 == res) + return -1; + else if (1 == res) + return 1; + + Exceptions.SetError(Exceptions.SystemError, "Error comparing objects"); + return -1; + } +#else [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern int PyObject_Compare(IntPtr value1, IntPtr value2); +#endif + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] @@ -729,11 +1028,16 @@ internal unsafe static extern int internal unsafe static extern int PyCallable_Check(IntPtr pointer); - [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, - ExactSpelling=true, CharSet=CharSet.Ansi)] + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] internal unsafe static extern int PyObject_IsTrue(IntPtr pointer); + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern int + PyObject_Not(IntPtr pointer); + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern int @@ -754,10 +1058,18 @@ internal unsafe static extern IntPtr internal unsafe static extern IntPtr PyObject_Str(IntPtr pointer); +#if (PYTHON32 || PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + EntryPoint="PyObject_Str", + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyObject_Unicode(IntPtr pointer); +#else [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, CharSet = CharSet.Ansi)] internal unsafe static extern IntPtr PyObject_Unicode(IntPtr pointer); +#endif [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] @@ -769,10 +1081,18 @@ internal unsafe static extern IntPtr // Python number API //==================================================================== +#if (PYTHON32 || PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + EntryPoint = "PyNumber_Long", + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Int(IntPtr ob); +#else // Python 2 [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern IntPtr PyNumber_Int(IntPtr ob); +#endif [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] @@ -799,14 +1119,8 @@ internal static bool PyBool_Check(IntPtr ob) { return PyObject_TypeCheck(ob, Runtime.PyBoolType); } - - - [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, - ExactSpelling=true, CharSet=CharSet.Ansi)] - private unsafe static extern IntPtr - PyInt_FromLong(IntPtr value); - - internal static IntPtr PyInt_FromInt32(int value) { + internal static IntPtr PyInt_FromInt32(int value) + { IntPtr v = new IntPtr(value); return PyInt_FromLong(v); } @@ -816,22 +1130,51 @@ internal static IntPtr PyInt_FromInt64(long value) { return PyInt_FromLong(v); } +#if (PYTHON32 || PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_FromLong", + ExactSpelling = true, CharSet = CharSet.Ansi)] + private unsafe static extern IntPtr + PyInt_FromLong(IntPtr value); [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + EntryPoint = "PyLong_AsLong", ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern int PyInt_AsLong(IntPtr value); [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + EntryPoint = "PyLong_FromString", ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern IntPtr PyInt_FromString(string value, IntPtr end, int radix); [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + EntryPoint = "PyLong_GetMax", ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern int PyInt_GetMax(); +#else // Python 2 + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + private unsafe static extern IntPtr + PyInt_FromLong(IntPtr value); + + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern int + PyInt_AsLong(IntPtr value); + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern IntPtr + PyInt_FromString(string value, IntPtr end, int radix); + + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern int + PyInt_GetMax(); +#endif internal static bool PyLong_Check(IntPtr ob) { return PyObject_TYPE(ob) == Runtime.PyLongType; @@ -907,6 +1250,130 @@ internal unsafe static extern IntPtr internal unsafe static extern double PyFloat_AsDouble(IntPtr ob); + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Add(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Subtract(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Multiply(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Divide(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_And(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Xor(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Or(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Lshift(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Rshift(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Power(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Remainder(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceAdd(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceSubtract(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceMultiply(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceDivide(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceAnd(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceXor(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceOr(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceLshift(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceRshift(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlacePower(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_InPlaceRemainder(IntPtr o1, IntPtr o2); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Negative(IntPtr o1); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Positive(IntPtr o1); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyNumber_Invert(IntPtr o1); //==================================================================== // Python sequence API @@ -1005,6 +1472,57 @@ internal static IntPtr PyString_FromString(string value) { return PyString_FromStringAndSize(value, value.Length); } +#if (PYTHON32 || PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern IntPtr + PyBytes_FromString(string op); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern int + PyBytes_Size(IntPtr op); + + internal static IntPtr PyBytes_AS_STRING(IntPtr ob) { + return ob + BytesOffset.ob_sval; + } + + internal static IntPtr PyString_FromStringAndSize(string value, int length) + { + // copy the string into an unmanaged UTF-8 buffer + int len = Encoding.UTF8.GetByteCount(value); + byte[] buffer = new byte[len + 1]; + Encoding.UTF8.GetBytes(value, 0, value.Length, buffer, 0); + IntPtr nativeUtf8 = Marshal.AllocHGlobal(buffer.Length); + try { + Marshal.Copy(buffer, 0, nativeUtf8, buffer.Length); + return PyUnicode_FromStringAndSize(nativeUtf8, length); + } + finally { + Marshal.FreeHGlobal(nativeUtf8); + } + } + +#if (PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_FromStringAndSize(IntPtr value, int size); +#elif (UCS2) + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyUnicodeUCS2_FromStringAndSize", + ExactSpelling = true, CharSet = CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_FromStringAndSize(IntPtr value, int size); +#else + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyUnicodeUCS4_FromStringAndSize", + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyUnicode_FromStringAndSize(IntPtr value, int size); +#endif + +#else // Python2x [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern IntPtr @@ -1020,12 +1538,57 @@ internal unsafe static extern IntPtr ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern int PyString_Size(IntPtr pointer); +#endif internal static bool PyUnicode_Check(IntPtr ob) { return PyObject_TYPE(ob) == Runtime.PyUnicodeType; } #if (UCS2) +#if (PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_FromObject(IntPtr ob); + + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_FromEncodedObject(IntPtr ob, IntPtr enc, IntPtr err); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + EntryPoint="PyUnicode_FromKindAndData", + ExactSpelling=true, + CharSet=CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_FromKindAndString(int kind, string s, int size); + + internal static IntPtr PyUnicode_FromUnicode(string s, int size) { + return PyUnicode_FromKindAndString(2, s, size); + } + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Unicode)] + internal unsafe static extern int + PyUnicode_GetSize(IntPtr ob); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Unicode)] + internal unsafe static extern char * + PyUnicode_AsUnicode(IntPtr ob); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyUnicode_AsUnicode", + ExactSpelling = true, CharSet = CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_AS_UNICODE(IntPtr op); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_FromOrdinal(int c); + +#else [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, EntryPoint="PyUnicodeUCS2_FromObject", ExactSpelling=true, CharSet=CharSet.Unicode)] @@ -1067,6 +1630,7 @@ internal unsafe static extern IntPtr ExactSpelling=true, CharSet=CharSet.Unicode)] internal unsafe static extern IntPtr PyUnicode_FromOrdinal(int c); +#endif internal static IntPtr PyUnicode_FromString(string s) { @@ -1077,6 +1641,8 @@ internal unsafe static string GetManagedString(IntPtr op) { IntPtr type = PyObject_TYPE(op); +// Python 3 strings are all unicode +#if !(PYTHON32 || PYTHON33 || PYTHON34) if (type == Runtime.PyStringType) { return Marshal.PtrToStringAnsi( @@ -1084,6 +1650,7 @@ internal unsafe static string GetManagedString(IntPtr op) Runtime.PyString_Size(op) ); } +#endif if (type == Runtime.PyUnicodeType) { @@ -1097,6 +1664,52 @@ internal unsafe static string GetManagedString(IntPtr op) #endif #if (UCS4) +#if (PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_FromObject(IntPtr ob); + + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_FromEncodedObject(IntPtr ob, IntPtr enc, IntPtr err); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyUnicode_FromKindAndData", + ExactSpelling = true)] + internal unsafe static extern IntPtr + PyUnicode_FromKindAndString(int kind, + [MarshalAs (UnmanagedType.CustomMarshaler, + MarshalTypeRef=typeof(Utf32Marshaler))] string s, + int size); + + internal static IntPtr PyUnicode_FromUnicode(string s, int size) { + return PyUnicode_FromKindAndString(4, s, size); + } + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern int + PyUnicode_GetSize(IntPtr ob); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true)] + internal unsafe static extern IntPtr + PyUnicode_AsUnicode(IntPtr ob); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyUnicode_AsUnicode", + ExactSpelling = true, CharSet = CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_AS_UNICODE(IntPtr op); + + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Unicode)] + internal unsafe static extern IntPtr + PyUnicode_FromOrdinal(int c); + +#else [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PyUnicodeUCS4_FromObject", ExactSpelling = true, CharSet = CharSet.Unicode)] @@ -1142,6 +1755,8 @@ internal unsafe static extern IntPtr internal unsafe static extern IntPtr PyUnicode_FromOrdinal(int c); +#endif + internal static IntPtr PyUnicode_FromString(string s) { return PyUnicode_FromUnicode(s, (s.Length)); @@ -1151,6 +1766,8 @@ internal unsafe static string GetManagedString(IntPtr op) { IntPtr type = PyObject_TYPE(op); +// Python 3 strings are all unicode +#if !(PYTHON32 || PYTHON33 || PYTHON34) if (type == Runtime.PyStringType) { return Marshal.PtrToStringAnsi( @@ -1158,11 +1775,16 @@ internal unsafe static string GetManagedString(IntPtr op) Runtime.PyString_Size(op) ); } +#endif if (type == Runtime.PyUnicodeType) { IntPtr p = Runtime.PyUnicode_AsUnicode(op); - return UnixMarshal.PtrToString(p, Encoding.UTF32); + int length = Runtime.PyUnicode_GetSize(op); + int size = length * 4; + byte[] buffer = new byte[size]; + Marshal.Copy(p, buffer, 0, size); + return Encoding.UTF32.GetString(buffer, 0, size); } return null; @@ -1212,6 +1834,11 @@ internal unsafe static extern int internal unsafe static extern int PyDict_DelItem(IntPtr pointer, IntPtr key); + [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern int + PyDict_DelItemString(IntPtr pointer, string key); + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern int @@ -1355,10 +1982,20 @@ internal unsafe static extern int // Python iterator API //==================================================================== +#if !(PYTHON32 || PYTHON33 || PYTHON34) [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, CharSet = CharSet.Ansi)] internal unsafe static extern bool PyIter_Check(IntPtr pointer); +#else + internal static bool + PyIter_Check(IntPtr pointer) + { + IntPtr ob_type = (IntPtr)Marshal.PtrToStructure(pointer + ObjectOffset.ob_type, typeof(IntPtr)); + IntPtr tp_iternext = ob_type + TypeOffset.tp_iternext; + return tp_iternext != null && tp_iternext != _PyObject_NextNotImplemented; + } +#endif [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, CharSet = CharSet.Ansi)] @@ -1369,6 +2006,11 @@ internal unsafe static extern IntPtr // Python module API //==================================================================== + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern IntPtr + PyModule_New(string name); + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern string @@ -1384,6 +2026,13 @@ internal unsafe static extern IntPtr internal unsafe static extern string PyModule_GetFilename(IntPtr module); +#if (PYTHON32 || PYTHON33 || PYTHON34) + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, + ExactSpelling=true, CharSet=CharSet.Ansi)] + internal unsafe static extern IntPtr + PyModule_Create2(IntPtr module, int apiver); +#endif + [DllImport(Runtime.dll, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true, CharSet=CharSet.Ansi)] internal unsafe static extern IntPtr diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 41b845737..866bbbb78 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -26,11 +26,9 @@ internal class TypeManager { static BindingFlags tbFlags; static Dictionary cache; - static int obSize; static TypeManager() { tbFlags = BindingFlags.Public | BindingFlags.Static; - obSize = 5 * IntPtr.Size; cache = new Dictionary(128); } @@ -86,11 +84,12 @@ internal static IntPtr GetTypeHandle(ManagedType obj, Type type) { internal static IntPtr CreateType(Type impl) { IntPtr type = AllocateTypeObject(impl.Name); + int ob_size = ObjectOffset.Size(type); // Set tp_basicsize to the size of our managed instance objects. - Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)obSize); + Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size); - IntPtr offset = (IntPtr)ObjectOffset.ob_dict; + IntPtr offset = (IntPtr)ObjectOffset.DictOffset(type); Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, offset); InitializeSlots(type, impl); @@ -124,11 +123,21 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) { } IntPtr base_ = IntPtr.Zero; + int ob_size = ObjectOffset.Size(Runtime.PyTypeType); + int tp_dictoffset = ObjectOffset.DictOffset(Runtime.PyTypeType); + // XXX Hack, use a different base class for System.Exception // Python 2.5+ allows new style class exceptions but they *must* // subclass BaseException (or better Exception). -#if (PYTHON25 || PYTHON26 || PYTHON27) - if (clrType == typeof(System.Exception)) { +#if (PYTHON25 || PYTHON26 || PYTHON27 || PYTHON32 || PYTHON33 || PYTHON34) + if (typeof(System.Exception).IsAssignableFrom(clrType)) + { + ob_size = ObjectOffset.Size(Exceptions.BaseException); + tp_dictoffset = ObjectOffset.DictOffset(Exceptions.BaseException); + } + + if (clrType == typeof(System.Exception)) + { base_ = Exceptions.Exception; Runtime.Incref(base_); } else @@ -143,11 +152,9 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) { Marshal.WriteIntPtr(type,TypeOffset.ob_type,Runtime.PyCLRMetaType); Runtime.Incref(Runtime.PyCLRMetaType); - Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)obSize); + Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size); Marshal.WriteIntPtr(type, TypeOffset.tp_itemsize, IntPtr.Zero); - - IntPtr offset = (IntPtr)ObjectOffset.ob_dict; - Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, offset); + Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, (IntPtr)tp_dictoffset); InitializeSlots(type, impl.GetType()); @@ -188,58 +195,78 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) { return type; } - internal static IntPtr CreateSubType(IntPtr args) { - - IntPtr py_name = Runtime.PyTuple_GetItem(args, 0); - IntPtr bases = Runtime.PyTuple_GetItem(args, 1); - IntPtr dict = Runtime.PyTuple_GetItem(args, 2); - IntPtr base_ = Runtime.PyTuple_GetItem(bases, 0); - + internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, IntPtr py_dict) + { + // Utility to create a subtype of a managed type with the ability for the + // a python subtype able to override the managed implementation string name = Runtime.GetManagedString(py_name); - IntPtr type = AllocateTypeObject(name); - - Marshal.WriteIntPtr(type,TypeOffset.ob_type,Runtime.PyCLRMetaType); - Runtime.Incref(Runtime.PyCLRMetaType); - - Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)obSize); - Marshal.WriteIntPtr(type, TypeOffset.tp_itemsize, IntPtr.Zero); - - IntPtr offset = (IntPtr)ObjectOffset.ob_dict; - Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, offset); - - IntPtr dc = Runtime.PyDict_Copy(dict); - Marshal.WriteIntPtr(type, TypeOffset.tp_dict, dc); - - Marshal.WriteIntPtr(type, TypeOffset.tp_base, base_); - Runtime.Incref(base_); - int flags = TypeFlags.Default; - flags |= TypeFlags.Managed; - flags |= TypeFlags.HeapType; - flags |= TypeFlags.BaseType; - flags |= TypeFlags.Subclass; - flags |= TypeFlags.HaveGC; - Marshal.WriteIntPtr(type, TypeOffset.tp_flags, (IntPtr)flags); + // the derived class can have class attributes __assembly__ and __module__ which + // control the name of the assembly and module the new type is created in. + object assembly = null; + object namespaceStr = null; + + List disposeList = new List(); + try + { + PyObject assemblyKey = new PyObject(Converter.ToPython("__assembly__", typeof(String))); + disposeList.Add(assemblyKey); + if (0 != Runtime.PyMapping_HasKey(py_dict, assemblyKey.Handle)) + { + PyObject pyAssembly = new PyObject(Runtime.PyDict_GetItem(py_dict, assemblyKey.Handle)); + disposeList.Add(pyAssembly); + if (!Converter.ToManagedValue(pyAssembly.Handle, typeof(String), out assembly, false)) + throw new InvalidCastException("Couldn't convert __assembly__ value to string"); + } - CopySlot(base_, type, TypeOffset.tp_traverse); - CopySlot(base_, type, TypeOffset.tp_clear); - CopySlot(base_, type, TypeOffset.tp_is_gc); + PyObject namespaceKey = new PyObject(Converter.ToPythonImplicit("__namespace__")); + disposeList.Add(namespaceKey); + if (0 != Runtime.PyMapping_HasKey(py_dict, namespaceKey.Handle)) + { + PyObject pyNamespace = new PyObject(Runtime.PyDict_GetItem(py_dict, namespaceKey.Handle)); + disposeList.Add(pyNamespace); + if (!Converter.ToManagedValue(pyNamespace.Handle, typeof(String), out namespaceStr, false)) + throw new InvalidCastException("Couldn't convert __namespace__ value to string"); + } + } + finally + { + foreach (PyObject o in disposeList) + o.Dispose(); + } - Runtime.PyType_Ready(type); + // create the new managed type subclassing the base managed type + ClassBase baseClass = ManagedType.GetManagedObject(py_base_type) as ClassBase; + if (null == baseClass) + { + return Exceptions.RaiseTypeError("invalid base class, expected CLR class type"); + } + try + { + Type subType = ClassDerivedObject.CreateDerivedType(name, + baseClass.type, + py_dict, + (string)namespaceStr, + (string)assembly); - IntPtr tp_dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict); - IntPtr mod = Runtime.PyString_FromString("CLR"); - Runtime.PyDict_SetItemString(tp_dict, "__module__", mod); + // create the new ManagedType and python type + ClassBase subClass = ClassManager.GetClass(subType); + IntPtr py_type = GetTypeHandle(subClass, subType); - // for now, move up hidden handle... - IntPtr gc = Marshal.ReadIntPtr(base_, TypeOffset.magic()); - Marshal.WriteIntPtr(type, TypeOffset.magic(), gc); + // by default the class dict will have all the C# methods in it, but as this is a + // derived class we want the python overrides in there instead if they exist. + IntPtr cls_dict = Marshal.ReadIntPtr(py_type, TypeOffset.tp_dict); + Runtime.PyDict_Update(cls_dict, py_dict); - return type; + return py_type; + } + catch (Exception e) + { + return Exceptions.RaiseTypeError(e.Message); + } } - internal static IntPtr CreateMetaType(Type impl) { // The managed metatype is functionally little different than the @@ -338,12 +365,24 @@ internal static IntPtr AllocateTypeObject(string name) { // Cheat a little: we'll set tp_name to the internal char * of // the Python version of the type name - otherwise we'd have to // allocate the tp_name and would have no way to free it. - +#if (PYTHON32 || PYTHON33 || PYTHON34) + // For python3 we leak two objects. One for the ascii representation + // required for tp_name, and another for the unicode representation + // for ht_name. + IntPtr temp = Runtime.PyBytes_FromString(name); + IntPtr raw = Runtime.PyBytes_AS_STRING(temp); + temp = Runtime.PyUnicode_FromString(name); +#else IntPtr temp = Runtime.PyString_FromString(name); IntPtr raw = Runtime.PyString_AS_STRING(temp); +#endif Marshal.WriteIntPtr(type, TypeOffset.tp_name, raw); Marshal.WriteIntPtr(type, TypeOffset.name, temp); +#if (PYTHON33 || PYTHON34) + Marshal.WriteIntPtr(type, TypeOffset.qualname, temp); +#endif + long ptr = type.ToInt64(); // 64-bit safe temp = new IntPtr(ptr + TypeOffset.nb_add); @@ -355,8 +394,13 @@ internal static IntPtr AllocateTypeObject(string name) { temp = new IntPtr(ptr + TypeOffset.mp_length); Marshal.WriteIntPtr(type, TypeOffset.tp_as_mapping, temp); +#if (PYTHON32 || PYTHON33 || PYTHON34) + temp = new IntPtr(ptr + TypeOffset.bf_getbuffer); + Marshal.WriteIntPtr(type, TypeOffset.tp_as_buffer, temp); +#else temp = new IntPtr(ptr + TypeOffset.bf_getreadbuffer); Marshal.WriteIntPtr(type, TypeOffset.tp_as_buffer, temp); +#endif return type; } @@ -416,19 +460,22 @@ private static void InitMethods(IntPtr pytype, Type type) { Type marker = typeof(PythonMethodAttribute); BindingFlags flags = BindingFlags.Public | BindingFlags.Static; + HashSet addedMethods = new HashSet(); while (type != null) { MethodInfo[] methods = type.GetMethods(flags); for (int i = 0; i < methods.Length; i++) { MethodInfo method = methods[i]; - object[] attrs = method.GetCustomAttributes(marker, false); - if (attrs.Length > 0) { - string method_name = method.Name; - MethodInfo[] mi = new MethodInfo[1]; - mi[0] = method; - MethodObject m = new TypeMethod(method_name, mi); - Runtime.PyDict_SetItemString(dict, method_name, - m.pyHandle); + if (!addedMethods.Contains(method.Name)) { + object[] attrs = method.GetCustomAttributes(marker, false); + if (attrs.Length > 0) { + string method_name = method.Name; + MethodInfo[] mi = new MethodInfo[1]; + mi[0] = method; + MethodObject m = new TypeMethod(type, method_name, mi); + Runtime.PyDict_SetItemString(dict, method_name, m.pyHandle); + addedMethods.Add(method_name); + } } } type = type.BaseType; diff --git a/src/runtime/typemethod.cs b/src/runtime/typemethod.cs index ab95f28ed..9170e5a4c 100644 --- a/src/runtime/typemethod.cs +++ b/src/runtime/typemethod.cs @@ -19,11 +19,11 @@ namespace Python.Runtime { internal class TypeMethod : MethodObject { - public TypeMethod(string name, MethodInfo[] info) : - base(name, info) {} + public TypeMethod(Type type, string name, MethodInfo[] info) : + base(type, name, info) {} - public TypeMethod(string name, MethodInfo[] info, bool allow_threads) : - base(name, info, allow_threads) { } + public TypeMethod(Type type, string name, MethodInfo[] info, bool allow_threads) : + base(type, name, info, allow_threads) { } public override IntPtr Invoke(IntPtr ob, IntPtr args, IntPtr kw) { MethodInfo mi = this.info[0]; diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index b0c41a866..46ca484bc 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -120,6 +120,7 @@ + @@ -140,8 +141,11 @@ $(SolutionDir) + $(TargetPath) + $(TargetDir)$(TargetName).pdb - + + diff --git a/src/testing/subclasstest.cs b/src/testing/subclasstest.cs new file mode 100644 index 000000000..64cea87c6 --- /dev/null +++ b/src/testing/subclasstest.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Python.Test +{ + public interface IInterfaceTest + { + // simple test with no arguments + string foo(); + + // test passing objects and boxing primitives + string bar(string s, int i); + } + + public class SubClassTest : IInterfaceTest + { + public SubClassTest() + { + } + + // simple test with no arguments + public virtual string foo() + { + return "foo"; + } + + // test passing objects and boxing primitives + public virtual string bar(string s, int i) + { + return s; + } + + // virtual methods that aren't overriden in python still work + public virtual string not_overriden() + { + return "not_overriden"; + } + + public virtual IList return_list() + { + return new List { "a", "b", "c" }; + } + + public static IList test_list(SubClassTest x) + { + // calls into python if return_list is overriden + return x.return_list(); + } + } + + public class TestFunctions + { + public static string test_foo(IInterfaceTest x) + { + // calls into python if foo is overriden + return x.foo(); + } + + public static string test_bar(IInterfaceTest x, string s, int i) + { + // calls into python if bar is overriden + return x.bar(s, i); + } + + // test instances can be constructed in managed code + public static IInterfaceTest create_instance(Type t) + { + return (IInterfaceTest)t.GetConstructor(new Type[] {}).Invoke(new Object[] {}); + } + + // test instances pass through managed code unchanged + public static IInterfaceTest pass_through(IInterfaceTest s) + { + return s; + } + } +} diff --git a/src/tests/runtests.py b/src/tests/runtests.py index 452b701f8..60bf075bf 100644 --- a/src/tests/runtests.py +++ b/src/tests/runtests.py @@ -18,7 +18,7 @@ try: import System except ImportError: - print "Load clr import hook" + print("Load clr import hook") import clr test_modules = ( @@ -69,6 +69,6 @@ def main(verbosity=1): if __name__ == '__main__': main(1) if '--pause' in sys.argv: - print "Press enter to continue" + print("Press enter to continue") raw_input() diff --git a/src/tests/test_array.py b/src/tests/test_array.py index 3a2259e45..a545c1b4c 100644 --- a/src/tests/test_array.py +++ b/src/tests/test_array.py @@ -10,6 +10,11 @@ import sys, os, string, unittest, types import Python.Test as Test import System +import six + +if six.PY3: + long = int + unichr = chr class ArrayTests(unittest.TestCase): @@ -422,8 +427,8 @@ def testInt64Array(self): self.assertTrue(items[0] == 0) self.assertTrue(items[4] == 4) - max = 9223372036854775807L - min = -9223372036854775808L + max = long(9223372036854775807) + min = long(-9223372036854775808) items[0] = max self.assertTrue(items[0] == max) @@ -522,7 +527,7 @@ def testUInt32Array(self): self.assertTrue(items[0] == 0) self.assertTrue(items[4] == 4) - max = 4294967295L + max = long(4294967295) min = 0 items[0] = max @@ -572,7 +577,7 @@ def testUInt64Array(self): self.assertTrue(items[0] == 0) self.assertTrue(items[4] == 4) - max = 18446744073709551615L + max = long(18446744073709551615) min = 0 items[0] = max @@ -1056,7 +1061,7 @@ def testArrayIteration(self): empty = Test.NullArrayTest().empty for i in empty: - raise TypeError, 'iteration over empty array' + raise TypeError('iteration over empty array') def testTupleArrayConversion(self): @@ -1131,7 +1136,10 @@ def testSequenceArrayConversion(self): """Test conversion of sequence-like objects to array arguments.""" from Python.Test import ArrayConversionTest from Python.Test import Spam - from UserList import UserList + if six.PY3: + from collections import UserList + else: + from UserList import UserList items = UserList() for i in range(10): @@ -1146,7 +1154,10 @@ def testSequenceNestedArrayConversion(self): """Test conversion of sequences to array-of-array arguments.""" from Python.Test import ArrayConversionTest from Python.Test import Spam - from UserList import UserList + if six.PY3: + from collections import UserList + else: + from UserList import UserList items = UserList() for i in range(10): @@ -1235,7 +1246,10 @@ def testSequenceArrayConversionTypeChecking(self): """Test error handling for sequence conversion to array arguments.""" from Python.Test import ArrayConversionTest from Python.Test import Spam - from UserList import UserList + if six.PY3: + from collections import UserList + else: + from UserList import UserList # This should work, because null / None is a valid value in an # array of reference types. @@ -1353,9 +1367,9 @@ def testSpecialArrayCreation(self): self.assertTrue(value[1] == 127) self.assertTrue(value.Length == 2) - value = Array[System.Char]([u'A', u'Z']) - self.assertTrue(value[0] == u'A') - self.assertTrue(value[1] == u'Z') + value = Array[System.Char]([six.u('A'), six.u('Z')]) + self.assertTrue(value[0] == six.u('A')) + self.assertTrue(value[1] == six.u('Z')) self.assertTrue(value.Length == 2) value = Array[System.Char]([0, 65535]) @@ -1378,29 +1392,31 @@ def testSpecialArrayCreation(self): self.assertTrue(value[1] == 2147483647) self.assertTrue(value.Length == 2) - value = Array[System.Int64]([0, 9223372036854775807L]) + value = Array[System.Int64]([0, long(9223372036854775807)]) self.assertTrue(value[0] == 0) - self.assertTrue(value[1] == 9223372036854775807L) + self.assertTrue(value[1] == long(9223372036854775807)) self.assertTrue(value.Length == 2) - value = Array[long]([0, 9223372036854775807L]) - self.assertTrue(value[0] == 0) - self.assertTrue(value[1] == 9223372036854775807L) - self.assertTrue(value.Length == 2) + # there's no explicit long type in python3, use System.Int64 instead + if not six.PY3: + value = Array[long]([0, long(9223372036854775807)]) + self.assertTrue(value[0] == 0) + self.assertTrue(value[1] == long(9223372036854775807)) + self.assertTrue(value.Length == 2) value = Array[System.UInt16]([0, 65000]) self.assertTrue(value[0] == 0) - self.assertTrue(value[1] == 65000) + self.assertTrue(value[1] == 65000) self.assertTrue(value.Length == 2) - value = Array[System.UInt32]([0, 4294967295L]) + value = Array[System.UInt32]([0, long(4294967295)]) self.assertTrue(value[0] == 0) - self.assertTrue(value[1] == 4294967295L) + self.assertTrue(value[1] == long(4294967295)) self.assertTrue(value.Length == 2) - value = Array[System.UInt64]([0, 18446744073709551615L]) + value = Array[System.UInt64]([0, long(18446744073709551615)]) self.assertTrue(value[0] == 0) - self.assertTrue(value[1] == 18446744073709551615L) + self.assertTrue(value[1] == long(18446744073709551615)) self.assertTrue(value.Length == 2) value = Array[System.Single]([0.0, 3.402823e38]) diff --git a/src/tests/test_class.py b/src/tests/test_class.py index b87587586..da50ea876 100644 --- a/src/tests/test_class.py +++ b/src/tests/test_class.py @@ -11,6 +11,12 @@ import sys, os, string, unittest, types import Python.Test as Test import System +import six + +if six.PY3: + DictProxyType = type(object.__dict__) +else: + DictProxyType = types.DictProxyType class ClassTests(unittest.TestCase): @@ -32,7 +38,7 @@ def testClassStandardAttrs(self): """Test standard class attributes.""" self.assertTrue(ClassTest.__name__ == 'ClassTest') self.assertTrue(ClassTest.__module__ == 'Python.Test') - self.assertTrue(type(ClassTest.__dict__) == types.DictProxyType) + self.assertTrue(type(ClassTest.__dict__) == DictProxyType) self.assertTrue(len(ClassTest.__doc__) > 0) diff --git a/src/tests/test_compat.py b/src/tests/test_compat.py index 7bb80d488..66c9cff16 100644 --- a/src/tests/test_compat.py +++ b/src/tests/test_compat.py @@ -8,6 +8,12 @@ # =========================================================================== import sys, os, string, unittest, types +import six + +if six.PY3: + ClassType = type +else: + ClassType = types.ClassType class CompatibilityTests(unittest.TestCase): @@ -19,8 +25,11 @@ def isCLRModule(self, object): return type(object).__name__ == 'ModuleObject' def isCLRRootModule(self, object): + if six.PY3: + # in Python 3 the clr module is a normal python module + return object.__name__ == "clr" return type(object).__name__ == 'CLRModule' - + def isCLRClass(self, object): return type(object).__name__ == 'CLR Metatype' # for now @@ -36,9 +45,15 @@ def testSimpleImport(self): self.assertTrue(type(sys) == types.ModuleType) self.assertTrue(sys.__name__ == 'sys') - import httplib - self.assertTrue(type(httplib) == types.ModuleType) - self.assertTrue(httplib.__name__ == 'httplib') + if six.PY3: + import http.client + self.assertTrue(type(http.client) == types.ModuleType) + self.assertTrue(http.client.__name__ == 'http.client') + + else: + import httplib + self.assertTrue(type(httplib) == types.ModuleType) + self.assertTrue(httplib.__name__ == 'httplib') def testSimpleImportWithAlias(self): @@ -51,9 +66,15 @@ def testSimpleImportWithAlias(self): self.assertTrue(type(mySys) == types.ModuleType) self.assertTrue(mySys.__name__ == 'sys') - import httplib as myHttplib - self.assertTrue(type(myHttplib) == types.ModuleType) - self.assertTrue(myHttplib.__name__ == 'httplib') + if six.PY3: + import http.client as myHttplib + self.assertTrue(type(myHttplib) == types.ModuleType) + self.assertTrue(myHttplib.__name__ == 'http.client') + + else: + import httplib as myHttplib + self.assertTrue(type(myHttplib) == types.ModuleType) + self.assertTrue(myHttplib.__name__ == 'httplib') def testDottedNameImport(self): @@ -127,7 +148,7 @@ def testDottedNameImportFrom(self): self.assertTrue(pulldom.__name__ == 'xml.dom.pulldom') from xml.dom.pulldom import PullDOM - self.assertTrue(type(PullDOM) == types.ClassType) + self.assertTrue(type(PullDOM) == ClassType) self.assertTrue(PullDOM.__name__ == 'PullDOM') @@ -146,7 +167,7 @@ def testDottedNameImportFromWithAlias(self): self.assertTrue(myPulldom.__name__ == 'xml.dom.pulldom') from xml.dom.pulldom import PullDOM as myPullDOM - self.assertTrue(type(myPullDOM) == types.ClassType) + self.assertTrue(type(myPullDOM) == ClassType) self.assertTrue(myPullDOM.__name__ == 'PullDOM') @@ -178,7 +199,7 @@ def testExplicitAssemblyLoad(self): self.assertTrue(assembly != None) import CLR.System.Data - self.assertTrue(sys.modules.has_key('System.Data')) + self.assertTrue('System.Data' in sys.modules) assembly = Assembly.LoadWithPartialName('SpamSpamSpamSpamEggsAndSpam') self.assertTrue(assembly == None) @@ -265,7 +286,7 @@ def main(): try: import System except ImportError: - print "Load clr import hook" + print("Load clr import hook") import clr main() diff --git a/src/tests/test_constructors.py b/src/tests/test_constructors.py index 4486e50bd..593b8afd8 100644 --- a/src/tests/test_constructors.py +++ b/src/tests/test_constructors.py @@ -55,7 +55,7 @@ class sub(System.Exception): instance = sub() ob = SubclassConstructorTest(instance) - print ob + print(ob) self.assertTrue(isinstance(ob.value, System.Exception)) diff --git a/src/tests/test_conversion.py b/src/tests/test_conversion.py index 8408f6fe3..961c7b9e8 100644 --- a/src/tests/test_conversion.py +++ b/src/tests/test_conversion.py @@ -10,6 +10,11 @@ import sys, os, string, unittest, types from Python.Test import ConversionTest import System +import six + +if six.PY3: + long = int + unichr = chr class ConversionTests(unittest.TestCase): @@ -176,16 +181,16 @@ def testCharConversion(self): self.assertTrue(System.Char.MinValue == unichr(0)) object = ConversionTest() - self.assertTrue(object.CharField == u'A') + self.assertTrue(object.CharField == six.u('A')) object.CharField = 'B' - self.assertTrue(object.CharField == u'B') + self.assertTrue(object.CharField == six.u('B')) - object.CharField = u'B' - self.assertTrue(object.CharField == u'B') + object.CharField = six.u('B') + self.assertTrue(object.CharField == six.u('B')) object.CharField = 67 - self.assertTrue(object.CharField == u'C') + self.assertTrue(object.CharField == six.u('C')) def test(): ConversionTest().CharField = 65536 @@ -307,23 +312,23 @@ def test(): def testInt64Conversion(self): """Test int64 conversion.""" - self.assertTrue(System.Int64.MaxValue == 9223372036854775807L) - self.assertTrue(System.Int64.MinValue == -9223372036854775808L) + self.assertTrue(System.Int64.MaxValue == long(9223372036854775807)) + self.assertTrue(System.Int64.MinValue == long(-9223372036854775808)) object = ConversionTest() self.assertTrue(object.Int64Field == 0) - object.Int64Field = 9223372036854775807L - self.assertTrue(object.Int64Field == 9223372036854775807L) + object.Int64Field = long(9223372036854775807) + self.assertTrue(object.Int64Field == long(9223372036854775807)) - object.Int64Field = -9223372036854775808L - self.assertTrue(object.Int64Field == -9223372036854775808L) + object.Int64Field = long(-9223372036854775808) + self.assertTrue(object.Int64Field == long(-9223372036854775808)) - object.Int64Field = System.Int64(9223372036854775807L) - self.assertTrue(object.Int64Field == 9223372036854775807L) + object.Int64Field = System.Int64(long(9223372036854775807)) + self.assertTrue(object.Int64Field == long(9223372036854775807)) - object.Int64Field = System.Int64(-9223372036854775808L) - self.assertTrue(object.Int64Field == -9223372036854775808L) + object.Int64Field = System.Int64(long(-9223372036854775808)) + self.assertTrue(object.Int64Field == long(-9223372036854775808)) def test(): ConversionTest().Int64Field = "spam" @@ -336,22 +341,22 @@ def test(): self.assertRaises(TypeError, test) def test(): - ConversionTest().Int64Field = 9223372036854775808L + ConversionTest().Int64Field = long(9223372036854775808) self.assertRaises(OverflowError, test) def test(): - ConversionTest().Int64Field = -9223372036854775809L + ConversionTest().Int64Field = long(-9223372036854775809) self.assertRaises(OverflowError, test) def test(): - value = System.Int64(9223372036854775808L) + value = System.Int64(long(9223372036854775808)) self.assertRaises(OverflowError, test) def test(): - value = System.Int64(-9223372036854775809L) + value = System.Int64(long(-9223372036854775809)) self.assertRaises(OverflowError, test) @@ -409,20 +414,20 @@ def test(): def testUInt32Conversion(self): """Test uint32 conversion.""" - self.assertTrue(System.UInt32.MaxValue == 4294967295L) + self.assertTrue(System.UInt32.MaxValue == long(4294967295)) self.assertTrue(System.UInt32.MinValue == 0) object = ConversionTest() self.assertTrue(object.UInt32Field == 0) - object.UInt32Field = 4294967295L - self.assertTrue(object.UInt32Field == 4294967295L) + object.UInt32Field = long(4294967295) + self.assertTrue(object.UInt32Field == long(4294967295)) object.UInt32Field = -0 self.assertTrue(object.UInt32Field == 0) - object.UInt32Field = System.UInt32(4294967295L) - self.assertTrue(object.UInt32Field == 4294967295L) + object.UInt32Field = System.UInt32(long(4294967295)) + self.assertTrue(object.UInt32Field == long(4294967295)) object.UInt32Field = System.UInt32(0) self.assertTrue(object.UInt32Field == 0) @@ -438,7 +443,7 @@ def test(): self.assertRaises(TypeError, test) def test(): - ConversionTest().UInt32Field = 4294967296L + ConversionTest().UInt32Field = long(4294967296) self.assertRaises(OverflowError, test) @@ -448,7 +453,7 @@ def test(): self.assertRaises(OverflowError, test) def test(): - value = System.UInt32(4294967296L) + value = System.UInt32(long(4294967296)) self.assertRaises(OverflowError, test) @@ -460,20 +465,20 @@ def test(): def testUInt64Conversion(self): """Test uint64 conversion.""" - self.assertTrue(System.UInt64.MaxValue == 18446744073709551615L) + self.assertTrue(System.UInt64.MaxValue == long(18446744073709551615)) self.assertTrue(System.UInt64.MinValue == 0) object = ConversionTest() self.assertTrue(object.UInt64Field == 0) - object.UInt64Field = 18446744073709551615L - self.assertTrue(object.UInt64Field == 18446744073709551615L) + object.UInt64Field = long(18446744073709551615) + self.assertTrue(object.UInt64Field == long(18446744073709551615)) object.UInt64Field = -0 self.assertTrue(object.UInt64Field == 0) - object.UInt64Field = System.UInt64(18446744073709551615L) - self.assertTrue(object.UInt64Field == 18446744073709551615L) + object.UInt64Field = System.UInt64(long(18446744073709551615)) + self.assertTrue(object.UInt64Field == long(18446744073709551615)) object.UInt64Field = System.UInt64(0) self.assertTrue(object.UInt64Field == 0) @@ -489,7 +494,7 @@ def test(): self.assertRaises(TypeError, test) def test(): - ConversionTest().UInt64Field = 18446744073709551616L + ConversionTest().UInt64Field = long(18446744073709551616) self.assertRaises(OverflowError, test) @@ -499,7 +504,7 @@ def test(): self.assertRaises(OverflowError, test) def test(): - value = System.UInt64(18446744073709551616L) + value = System.UInt64(long(18446744073709551616)) self.assertRaises(OverflowError, test) @@ -618,7 +623,7 @@ def testDecimalConversion(self): max_d = Decimal.Parse("79228162514264337593543950335") min_d = Decimal.Parse("-79228162514264337593543950335") - self.assertTrue(Decimal.ToInt64(Decimal(10)) == 10L) + self.assertTrue(Decimal.ToInt64(Decimal(10)) == long(10)) object = ConversionTest() self.assertTrue(object.DecimalField == Decimal(0)) @@ -659,25 +664,25 @@ def testStringConversion(self): object = ConversionTest() self.assertTrue(object.StringField == "spam") - self.assertTrue(object.StringField == u"spam") + self.assertTrue(object.StringField == six.u("spam")) object.StringField = "eggs" self.assertTrue(object.StringField == "eggs") - self.assertTrue(object.StringField == u"eggs") + self.assertTrue(object.StringField == six.u("eggs")) - object.StringField = u"spam" + object.StringField = six.u("spam") self.assertTrue(object.StringField == "spam") - self.assertTrue(object.StringField == u"spam") + self.assertTrue(object.StringField == six.u("spam")) - object.StringField = u'\uffff\uffff' - self.assertTrue(object.StringField == u'\uffff\uffff') + object.StringField = six.u('\uffff\uffff') + self.assertTrue(object.StringField == six.u('\uffff\uffff')) object.StringField = System.String("spam") self.assertTrue(object.StringField == "spam") - self.assertTrue(object.StringField == u"spam") + self.assertTrue(object.StringField == six.u("spam")) - object.StringField = System.String(u'\uffff\uffff') - self.assertTrue(object.StringField == u'\uffff\uffff') + object.StringField = System.String(six.u('\uffff\uffff')) + self.assertTrue(object.StringField == six.u('\uffff\uffff')) object.StringField = None self.assertTrue(object.StringField == None) @@ -829,11 +834,11 @@ def testByteArrayConversion(self): self.assertTrue(array[0] == 0) self.assertTrue(array[4] == 4) - value = "testing" + value = six.b("testing") object.ByteArrayField = value array = object.ByteArrayField for i in range(len(value)): - self.assertTrue(array[i] == ord(value[i])) + self.assertTrue(array[i] == six.indexbytes(value, i)) def testSByteArrayConversion(self): @@ -848,11 +853,11 @@ def testSByteArrayConversion(self): self.assertTrue(array[0] == 0) self.assertTrue(array[4] == 4) - value = "testing" + value = six.b("testing") object.SByteArrayField = value array = object.SByteArrayField for i in range(len(value)): - self.assertTrue(array[i] == ord(value[i])) + self.assertTrue(array[i] == six.indexbytes(value, i)) diff --git a/src/tests/test_delegate.py b/src/tests/test_delegate.py index 21c53ea3f..0d2315925 100644 --- a/src/tests/test_delegate.py +++ b/src/tests/test_delegate.py @@ -15,6 +15,12 @@ import sys, os, string, unittest, types import Python.Test as Test import System +import six + +if six.PY3: + DictProxyType = type(object.__dict__) +else: + DictProxyType = types.DictProxyType class DelegateTests(unittest.TestCase): @@ -24,7 +30,7 @@ def testDelegateStandardAttrs(self): """Test standard delegate attributes.""" self.assertTrue(PublicDelegate.__name__ == 'PublicDelegate') self.assertTrue(PublicDelegate.__module__ == 'Python.Test') - self.assertTrue(type(PublicDelegate.__dict__) == types.DictProxyType) + self.assertTrue(type(PublicDelegate.__dict__) == DictProxyType) self.assertTrue(PublicDelegate.__doc__ == None) diff --git a/src/tests/test_enum.py b/src/tests/test_enum.py index 98db3f3c6..26b14c274 100644 --- a/src/tests/test_enum.py +++ b/src/tests/test_enum.py @@ -10,6 +10,13 @@ import sys, os, string, unittest, types from System import DayOfWeek from Python import Test +import six + +if six.PY3: + DictProxyType = type(object.__dict__) + long = int +else: + DictProxyType = types.DictProxyType class EnumTests(unittest.TestCase): @@ -19,7 +26,7 @@ def testEnumStandardAttrs(self): """Test standard enum attributes.""" self.assertTrue(DayOfWeek.__name__ == 'DayOfWeek') self.assertTrue(DayOfWeek.__module__ == 'System') - self.assertTrue(type(DayOfWeek.__dict__) == types.DictProxyType) + self.assertTrue(type(DayOfWeek.__dict__) == DictProxyType) self.assertTrue(DayOfWeek.__doc__ == None) @@ -71,23 +78,23 @@ def testIntEnum(self): def testUIntEnum(self): """Test uint enum.""" - self.assertTrue(Test.UIntEnum.Zero == 0L) - self.assertTrue(Test.UIntEnum.One == 1L) - self.assertTrue(Test.UIntEnum.Two == 2L) + self.assertTrue(Test.UIntEnum.Zero == long(0)) + self.assertTrue(Test.UIntEnum.One == long(1)) + self.assertTrue(Test.UIntEnum.Two == long(2)) def testLongEnum(self): """Test long enum.""" - self.assertTrue(Test.LongEnum.Zero == 0L) - self.assertTrue(Test.LongEnum.One == 1L) - self.assertTrue(Test.LongEnum.Two == 2L) + self.assertTrue(Test.LongEnum.Zero == long(0)) + self.assertTrue(Test.LongEnum.One == long(1)) + self.assertTrue(Test.LongEnum.Two == long(2)) def testULongEnum(self): """Test ulong enum.""" - self.assertTrue(Test.ULongEnum.Zero == 0L) - self.assertTrue(Test.ULongEnum.One == 1L) - self.assertTrue(Test.ULongEnum.Two == 2L) + self.assertTrue(Test.ULongEnum.Zero == long(0)) + self.assertTrue(Test.ULongEnum.One == long(1)) + self.assertTrue(Test.ULongEnum.Two == long(2)) def testInstantiateEnumFails(self): diff --git a/src/tests/test_exceptions.py b/src/tests/test_exceptions.py index de6dd01e5..86a141403 100644 --- a/src/tests/test_exceptions.py +++ b/src/tests/test_exceptions.py @@ -9,6 +9,10 @@ import sys, os, string, unittest, types import System +import six + +if six.PY3: + unicode = str # Note: all of these tests are known to fail because Python currently # doesn't allow new-style classes to be used as exceptions. I'm leaving @@ -21,10 +25,11 @@ class ExceptionTests(unittest.TestCase): def testUnifiedExceptionSemantics(self): """Test unified exception semantics.""" from System import Exception, Object - import exceptions e = Exception('Something bad happened') - self.assertTrue(isinstance(e, exceptions.Exception)) + if not six.PY3: + import exceptions + self.assertTrue(isinstance(e, exceptions.Exception)) self.assertTrue(isinstance(e, Exception)) @@ -49,7 +54,6 @@ def testExtendedExceptionAttributes(self): """Test accessing extended exception attributes.""" from Python.Test import ExceptionTest, ExtendedException from System import Exception, OverflowException - import exceptions e = ExceptionTest.GetExtendedException() self.assertTrue(isinstance(e, ExtendedException)) @@ -93,7 +97,7 @@ def testRaiseClassExceptionWithValue(self): from System import NullReferenceException def test(): - raise NullReferenceException, 'Aiiieee!' + raise NullReferenceException('Aiiieee!') self.assertRaises(NullReferenceException, test) @@ -185,7 +189,8 @@ def testCatchExceptionFromManagedMethod(self): try: ExceptionTest().ThrowException() - except OverflowException, e: + except OverflowException: + e = sys.exc_info()[1] self.assertTrue(isinstance(e, OverflowException)) return @@ -199,13 +204,15 @@ def testCatchExceptionFromManagedProperty(self): try: v = ExceptionTest().ThrowProperty - except OverflowException, e: + except OverflowException: + e = sys.exc_info()[1] self.assertTrue(isinstance(e, OverflowException)) return try: ExceptionTest().ThrowProperty = 1 - except OverflowException, e: + except OverflowException: + e = sys.exc_info()[1] self.assertTrue(isinstance(e, OverflowException)) return @@ -227,7 +234,10 @@ def testCatchExceptionManagedClass(self): def testCatchExceptionPythonClass(self): """Test catching the python class of an exception.""" from System import OverflowException - from exceptions import Exception + if six.PY3: + from builtins import Exception + else: + from exceptions import Exception try: raise OverflowException('overflow') @@ -267,7 +277,8 @@ def testCatchExceptionWithAssignment(self): try: raise OverflowException('overflow') - except OverflowException, e: + except OverflowException: + e = sys.exc_info()[1] self.assertTrue(isinstance(e, OverflowException)) @@ -303,9 +314,10 @@ def testStrOfException(self): try: Convert.ToDateTime('this will fail') - except FormatException, e: + except FormatException: + e = sys.exc_info()[1] msg = unicode(e).encode("utf8") # fix for international installation - self.assertTrue(msg.find('System.Convert.ToDateTime') > -1, msg) + self.assertTrue(msg.find(unicode('System.Convert.ToDateTime').encode("utf8")) > -1, msg) def testPythonCompatOfManagedExceptions(self): diff --git a/src/tests/test_field.py b/src/tests/test_field.py index e266f65d1..1ec9c7744 100644 --- a/src/tests/test_field.py +++ b/src/tests/test_field.py @@ -11,6 +11,12 @@ from Python.Test import FieldTest from Python.Test import ShortEnum import System +import six + +if six.PY3: + IntType = int +else: + IntType = types.IntType class FieldTests(unittest.TestCase): @@ -212,15 +218,15 @@ def testFieldDescriptorGetSet(self): self.assertTrue(object.PublicStaticField == 0) descriptor = FieldTest.__dict__['PublicStaticField'] - self.assertTrue(type(descriptor) != types.IntType) + self.assertTrue(type(descriptor) != IntType) object.PublicStaticField = 0 descriptor = FieldTest.__dict__['PublicStaticField'] - self.assertTrue(type(descriptor) != types.IntType) + self.assertTrue(type(descriptor) != IntType) FieldTest.PublicStaticField = 0 descriptor = FieldTest.__dict__['PublicStaticField'] - self.assertTrue(type(descriptor) != types.IntType) + self.assertTrue(type(descriptor) != IntType) def testFieldDescriptorWrongType(self): @@ -286,15 +292,15 @@ def testByteField(self): def testCharField(self): """Test char fields.""" object = FieldTest() - self.assertTrue(object.CharField == u'A') + self.assertTrue(object.CharField == six.u('A')) self.assertTrue(object.CharField == 'A') object.CharField = 'B' - self.assertTrue(object.CharField == u'B') + self.assertTrue(object.CharField == six.u('B')) self.assertTrue(object.CharField == 'B') - object.CharField = u'C' - self.assertTrue(object.CharField == u'C') + object.CharField = six.u('C') + self.assertTrue(object.CharField == six.u('C')) self.assertTrue(object.CharField == 'C') diff --git a/src/tests/test_generic.py b/src/tests/test_generic.py index 256bca29a..d7ae2e26b 100644 --- a/src/tests/test_generic.py +++ b/src/tests/test_generic.py @@ -14,6 +14,13 @@ import sys, os, string, unittest, types import Python.Test as Test import System +import six + +if six.PY3: + long = int + unichr = chr + unicode = str + class GenericTests(unittest.TestCase): """Test CLR generics support.""" @@ -42,13 +49,13 @@ def testPythonTypeAliasing(self): dict = Dictionary[long, long]() self.assertEquals(dict.Count, 0) - dict.Add(1L, 1L) - self.assertTrue(dict[1L] == 1L) + dict.Add(long(1), long(1)) + self.assertTrue(dict[long(1)] == long(1)) dict = Dictionary[System.Int64, System.Int64]() self.assertEquals(dict.Count, 0) - dict.Add(1L, 1L) - self.assertTrue(dict[1L] == 1L) + dict.Add(long(1), long(1)) + self.assertTrue(dict[long(1)] == long(1)) dict = Dictionary[float, float]() self.assertEquals(dict.Count, 0) @@ -172,15 +179,17 @@ def testGenericTypeBinding(self): self._testGenericWrapperByType(bool, True) self._testGenericWrapperByType(System.Byte, 255) self._testGenericWrapperByType(System.SByte, 127) - self._testGenericWrapperByType(System.Char, u'A') + self._testGenericWrapperByType(System.Char, six.u('A')) self._testGenericWrapperByType(System.Int16, 32767) self._testGenericWrapperByType(System.Int32, 2147483647) self._testGenericWrapperByType(int, 2147483647) - self._testGenericWrapperByType(System.Int64, 9223372036854775807L) - self._testGenericWrapperByType(long, 9223372036854775807L) + self._testGenericWrapperByType(System.Int64, long(9223372036854775807)) + # Python 3 has no explicit long type, use System.Int64 instead + if not six.PY3: + self._testGenericWrapperByType(long, long(9223372036854775807)) self._testGenericWrapperByType(System.UInt16, 65000) - self._testGenericWrapperByType(System.UInt32, 4294967295L) - self._testGenericWrapperByType(System.UInt64, 18446744073709551615L) + self._testGenericWrapperByType(System.UInt32, long(4294967295)) + self._testGenericWrapperByType(System.UInt64, long(18446744073709551615)) self._testGenericWrapperByType(System.Single, 3.402823e38) self._testGenericWrapperByType(System.Double, 1.7976931348623157e308) self._testGenericWrapperByType(float, 1.7976931348623157e308) @@ -309,15 +318,17 @@ def testGenericMethodTypeHandling(self): self._testGenericMethodByType(bool, True) self._testGenericMethodByType(System.Byte, 255) self._testGenericMethodByType(System.SByte, 127) - self._testGenericMethodByType(System.Char, u'A') + self._testGenericMethodByType(System.Char, six.u('A')) self._testGenericMethodByType(System.Int16, 32767) self._testGenericMethodByType(System.Int32, 2147483647) self._testGenericMethodByType(int, 2147483647) - self._testGenericMethodByType(System.Int64, 9223372036854775807L) - self._testGenericMethodByType(long, 9223372036854775807L) + # Python 3 has no explicit long type, use System.Int64 instead + if not six.PY3: + self._testGenericMethodByType(System.Int64, long(9223372036854775807)) + self._testGenericMethodByType(long, long(9223372036854775807)) + self._testGenericMethodByType(System.UInt32, long(4294967295)) + self._testGenericMethodByType(System.Int64, long(1844674407370955161)) self._testGenericMethodByType(System.UInt16, 65000) - self._testGenericMethodByType(System.UInt32, 4294967295L) - self._testGenericMethodByType(System.Int64, 1844674407370955161L) self._testGenericMethodByType(System.Single, 3.402823e38) self._testGenericMethodByType(System.Double, 1.7976931348623157e308) self._testGenericMethodByType(float, 1.7976931348623157e308) @@ -439,9 +450,9 @@ def testMethodOverloadSelectionWithGenericTypes(self): self.assertTrue(value.value == 127) vtype = GenericWrapper[System.Char] - input = vtype(u'A') + input = vtype(six.u('A')) value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value.value == u'A') + self.assertTrue(value.value == six.u('A')) vtype = GenericWrapper[System.Char] input = vtype(65535) @@ -464,14 +475,16 @@ def testMethodOverloadSelectionWithGenericTypes(self): self.assertTrue(value.value == 2147483647) vtype = GenericWrapper[System.Int64] - input = vtype(9223372036854775807L) + input = vtype(long(9223372036854775807)) value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value.value == 9223372036854775807L) + self.assertTrue(value.value == long(9223372036854775807)) - vtype = GenericWrapper[long] - input = vtype(9223372036854775807L) - value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value.value == 9223372036854775807L) + # Python 3 has no explicit long type, use System.Int64 instead + if not six.PY3: + vtype = GenericWrapper[long] + input = vtype(long(9223372036854775807)) + value = MethodTest.Overloaded.__overloads__[vtype](input) + self.assertTrue(value.value == long(9223372036854775807)) vtype = GenericWrapper[System.UInt16] input = vtype(65000) @@ -479,14 +492,14 @@ def testMethodOverloadSelectionWithGenericTypes(self): self.assertTrue(value.value == 65000) vtype = GenericWrapper[System.UInt32] - input = vtype(4294967295L) + input = vtype(long(4294967295)) value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value.value == 4294967295L) + self.assertTrue(value.value == long(4294967295)) vtype = GenericWrapper[System.UInt64] - input = vtype(18446744073709551615L) + input = vtype(long(18446744073709551615)) value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value.value == 18446744073709551615L) + self.assertTrue(value.value == long(18446744073709551615)) vtype = GenericWrapper[System.Single] input = vtype(3.402823e38) @@ -580,9 +593,9 @@ def testOverloadSelectionWithArraysOfGenericTypes(self): gtype = GenericWrapper[System.Char] vtype = System.Array[gtype] - input = vtype([gtype(u'A'), gtype(u'A')]) + input = vtype([gtype(six.u('A')), gtype(six.u('A'))]) value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value[0].value == u'A') + self.assertTrue(value[0].value == six.u('A')) self.assertTrue(value.Length == 2) gtype = GenericWrapper[System.Char] @@ -615,19 +628,21 @@ def testOverloadSelectionWithArraysOfGenericTypes(self): gtype = GenericWrapper[System.Int64] vtype = System.Array[gtype] - input = vtype([gtype(9223372036854775807L), - gtype(9223372036854775807L)]) - value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value[0].value == 9223372036854775807L) - self.assertTrue(value.Length == 2) - - gtype = GenericWrapper[long] - vtype = System.Array[gtype] - input = vtype([gtype(9223372036854775807L), - gtype(9223372036854775807L)]) + input = vtype([gtype(long(9223372036854775807)), + gtype(long(9223372036854775807))]) value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value[0].value == 9223372036854775807L) + self.assertTrue(value[0].value == long(9223372036854775807)) self.assertTrue(value.Length == 2) + + # Python 3 has no explicit long type, use System.Int64 instead + if not six.PY3: + gtype = GenericWrapper[long] + vtype = System.Array[gtype] + input = vtype([gtype(long(9223372036854775807)), + gtype(long(9223372036854775807))]) + value = MethodTest.Overloaded.__overloads__[vtype](input) + self.assertTrue(value[0].value == long(9223372036854775807)) + self.assertTrue(value.Length == 2) gtype = GenericWrapper[System.UInt16] vtype = System.Array[gtype] @@ -638,17 +653,17 @@ def testOverloadSelectionWithArraysOfGenericTypes(self): gtype = GenericWrapper[System.UInt32] vtype = System.Array[gtype] - input = vtype([gtype(4294967295L), gtype(4294967295L)]) + input = vtype([gtype(long(4294967295)), gtype(long(4294967295))]) value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value[0].value == 4294967295L) + self.assertTrue(value[0].value == long(4294967295)) self.assertTrue(value.Length == 2) gtype = GenericWrapper[System.UInt64] vtype = System.Array[gtype] - input = vtype([gtype(18446744073709551615L), - gtype(18446744073709551615L)]) + input = vtype([gtype(long(18446744073709551615)), + gtype(long(18446744073709551615))]) value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value[0].value == 18446744073709551615L) + self.assertTrue(value[0].value == long(18446744073709551615)) self.assertTrue(value.Length == 2) gtype = GenericWrapper[System.Single] diff --git a/src/tests/test_indexer.py b/src/tests/test_indexer.py index 2b1d4e100..cb572e3a8 100644 --- a/src/tests/test_indexer.py +++ b/src/tests/test_indexer.py @@ -9,6 +9,11 @@ import sys, os, string, unittest, types import Python.Test as Test +import six + +if six.PY3: + long = int + unichr = chr class IndexerTests(unittest.TestCase): @@ -238,8 +243,8 @@ def test(): def testInt64Indexer(self): """Test Int64 indexers.""" object = Test.Int64IndexerTest() - max = 9223372036854775807L - min = -9223372036854775808L + max = long(9223372036854775807) + min = long(-9223372036854775808) self.assertTrue(object[max] == None) @@ -292,7 +297,7 @@ def test(): def testUInt32Indexer(self): """Test UInt32 indexers.""" object = Test.UInt32IndexerTest() - max = 4294967295L + max = long(4294967295) min = 0 self.assertTrue(object[max] == None) @@ -319,7 +324,7 @@ def test(): def testUInt64Indexer(self): """Test UInt64 indexers.""" object = Test.UInt64IndexerTest() - max = 18446744073709551615L + max = long(18446744073709551615) min = 0 self.assertTrue(object[max] == None) @@ -431,19 +436,19 @@ def testStringIndexer(self): object = Test.StringIndexerTest() self.assertTrue(object["spam"] == None) - self.assertTrue(object[u"spam"] == None) + self.assertTrue(object[six.u("spam")] == None) object["spam"] = "spam" self.assertTrue(object["spam"] == "spam") - self.assertTrue(object["spam"] == u"spam") - self.assertTrue(object[u"spam"] == "spam") - self.assertTrue(object[u"spam"] == u"spam") + self.assertTrue(object["spam"] == six.u("spam")) + self.assertTrue(object[six.u("spam")] == "spam") + self.assertTrue(object[six.u("spam")] == six.u("spam")) - object[u"eggs"] = u"eggs" + object[six.u("eggs")] = six.u("eggs") self.assertTrue(object["eggs"] == "eggs") - self.assertTrue(object["eggs"] == u"eggs") - self.assertTrue(object[u"eggs"] == "eggs") - self.assertTrue(object[u"eggs"] == u"eggs") + self.assertTrue(object["eggs"] == six.u("eggs")) + self.assertTrue(object[six.u("eggs")] == "eggs") + self.assertTrue(object[six.u("eggs")] == six.u("eggs")) def test(): object = Test.StringIndexerTest() @@ -509,8 +514,8 @@ def testObjectIndexer(self): object[1] = "one" self.assertTrue(object[1] == "one") - object[1L] = "long" - self.assertTrue(object[1L] == "long") + object[long(1)] = "long" + self.assertTrue(object[long(1)] == "long") def test(): class eggs: diff --git a/src/tests/test_interface.py b/src/tests/test_interface.py index 1e9c0ad96..4412aefb2 100644 --- a/src/tests/test_interface.py +++ b/src/tests/test_interface.py @@ -11,6 +11,13 @@ import sys, os, string, unittest, types import Python.Test as Test import System +import six + +if six.PY3: + DictProxyType = type(object.__dict__) +else: + DictProxyType = types.DictProxyType + class InterfaceTests(unittest.TestCase): """Test CLR interface support.""" @@ -20,7 +27,7 @@ def testInterfaceStandardAttrs(self): from Python.Test import IPublicInterface as ip self.assertTrue(ip.__name__ == 'IPublicInterface') self.assertTrue(ip.__module__ == 'Python.Test') - self.assertTrue(type(ip.__dict__) == types.DictProxyType) + self.assertTrue(type(ip.__dict__) == DictProxyType) def testGlobalInterfaceVisibility(self): diff --git a/src/tests/test_method.py b/src/tests/test_method.py index 03a23cf84..cdfc1e33b 100644 --- a/src/tests/test_method.py +++ b/src/tests/test_method.py @@ -13,6 +13,12 @@ from Python.Test import MethodTest, MethodTestSub import System +import six + +if six.PY3: + long = int + unichr = chr + class MethodTests(unittest.TestCase): """Test CLR method support.""" @@ -235,11 +241,11 @@ def testMethodCallStructConversion(self): def testSubclassInstanceConversion(self): """Test subclass instance conversion in method call.""" - class sub(System.Exception): + class TestSubException(System.Exception): pass object = MethodTest() - instance = sub() + instance = TestSubException() result = object.TestSubclassConversion(instance) self.assertTrue(isinstance(result, System.Exception)) @@ -501,8 +507,8 @@ def testExplicitOverloadSelection(self): value = MethodTest.Overloaded.__overloads__[System.SByte](127) self.assertTrue(value == 127) - value = MethodTest.Overloaded.__overloads__[System.Char](u'A') - self.assertTrue(value == u'A') + value = MethodTest.Overloaded.__overloads__[System.Char](six.u('A')) + self.assertTrue(value == six.u('A')) value = MethodTest.Overloaded.__overloads__[System.Char](65535) self.assertTrue(value == unichr(65535)) @@ -517,25 +523,27 @@ def testExplicitOverloadSelection(self): self.assertTrue(value == 2147483647) value = MethodTest.Overloaded.__overloads__[System.Int64]( - 9223372036854775807L + long(9223372036854775807) ) - self.assertTrue(value == 9223372036854775807L) + self.assertTrue(value == long(9223372036854775807)) - value = MethodTest.Overloaded.__overloads__[long]( - 9223372036854775807L - ) - self.assertTrue(value == 9223372036854775807L) + # Python 3 has no explicit long type, use System.Int64 instead + if not six.PY3: + value = MethodTest.Overloaded.__overloads__[long]( + long(9223372036854775807) + ) + self.assertTrue(value == long(9223372036854775807)) value = MethodTest.Overloaded.__overloads__[System.UInt16](65000) self.assertTrue(value == 65000) - value = MethodTest.Overloaded.__overloads__[System.UInt32](4294967295L) - self.assertTrue(value == 4294967295L) + value = MethodTest.Overloaded.__overloads__[System.UInt32](long(4294967295)) + self.assertTrue(value == long(4294967295)) value = MethodTest.Overloaded.__overloads__[System.UInt64]( - 18446744073709551615L + long(18446744073709551615) ) - self.assertTrue(value == 18446744073709551615L) + self.assertTrue(value == long(18446744073709551615)) value = MethodTest.Overloaded.__overloads__[System.Single](3.402823e38) self.assertTrue(value == 3.402823e38) @@ -617,10 +625,10 @@ def testOverloadSelectionWithArrayTypes(self): self.assertTrue(value[1] == 127) vtype = Array[System.Char] - input = vtype([u'A', u'Z']) + input = vtype([six.u('A'), six.u('Z')]) value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value[0] == u'A') - self.assertTrue(value[1] == u'Z') + self.assertTrue(value[0] == six.u('A')) + self.assertTrue(value[1] == six.u('Z')) vtype = Array[System.Char] input = vtype([0, 65535]) @@ -647,16 +655,18 @@ def testOverloadSelectionWithArrayTypes(self): self.assertTrue(value[1] == 2147483647) vtype = Array[System.Int64] - input = vtype([0, 9223372036854775807L]) + input = vtype([0, long(9223372036854775807)]) value = MethodTest.Overloaded.__overloads__[vtype](input) self.assertTrue(value[0] == 0) - self.assertTrue(value[1] == 9223372036854775807L) + self.assertTrue(value[1] == long(9223372036854775807)) - vtype = Array[long] - input = vtype([0, 9223372036854775807L]) - value = MethodTest.Overloaded.__overloads__[vtype](input) - self.assertTrue(value[0] == 0) - self.assertTrue(value[1] == 9223372036854775807L) + # Python 3 has no explicit long type, use System.Int64 instead + if not six.PY3: + vtype = Array[long] + input = vtype([0, long(9223372036854775807)]) + value = MethodTest.Overloaded.__overloads__[vtype](input) + self.assertTrue(value[0] == 0) + self.assertTrue(value[1] == long(9223372036854775807)) vtype = Array[System.UInt16] input = vtype([0, 65000]) @@ -665,16 +675,16 @@ def testOverloadSelectionWithArrayTypes(self): self.assertTrue(value[1] == 65000) vtype = Array[System.UInt32] - input = vtype([0, 4294967295L]) + input = vtype([0, long(4294967295)]) value = MethodTest.Overloaded.__overloads__[vtype](input) self.assertTrue(value[0] == 0) - self.assertTrue(value[1] == 4294967295L) + self.assertTrue(value[1] == long(4294967295)) vtype = Array[System.UInt64] - input = vtype([0, 18446744073709551615L]) + input = vtype([0, long(18446744073709551615)]) value = MethodTest.Overloaded.__overloads__[vtype](input) self.assertTrue(value[0] == 0) - self.assertTrue(value[1] == 18446744073709551615L) + self.assertTrue(value[1] == long(18446744073709551615)) vtype = Array[System.Single] input = vtype([0.0, 3.402823e38]) diff --git a/src/tests/test_module.py b/src/tests/test_module.py index 62ea78311..0d340652a 100644 --- a/src/tests/test_module.py +++ b/src/tests/test_module.py @@ -13,6 +13,13 @@ # testImplicitAssemblyLoad() passes on deprecation warning; perfect! # ##clr.AddReference('System.Windows.Forms') import sys, os, string, unittest, types, warnings +from fnmatch import fnmatch +import six + +if six.PY3: + ClassType = type +else: + ClassType = types.ClassType class ModuleTests(unittest.TestCase): @@ -22,6 +29,9 @@ def isCLRModule(self, object): return type(object).__name__ == 'ModuleObject' def isCLRRootModule(self, object): + if six.PY3: + # in Python 3 the clr module is a normal python module + return object.__name__ == "clr" return type(object).__name__ == 'CLRModule' def isCLRClass(self, object): @@ -62,8 +72,9 @@ def testModuleInterface(self): import System self.assertEquals(type(System.__dict__), type({})) self.assertEquals(System.__name__, 'System') - self.assertEquals(System.__file__, None) - self.assertEquals(System.__doc__, None) + # the filename can be any module from the System namespace (eg System.Data.dll or System.dll) + self.assertTrue(fnmatch(System.__file__, "*System*.dll")) + self.assertTrue(System.__doc__.startswith("Namespace containing types from the following assemblies:")) self.assertTrue(self.isCLRClass(System.String)) self.assertTrue(self.isCLRClass(System.Int32)) @@ -78,9 +89,14 @@ def testSimpleImport(self): self.assertTrue(type(sys) == types.ModuleType) self.assertTrue(sys.__name__ == 'sys') - import httplib - self.assertTrue(type(httplib) == types.ModuleType) - self.assertTrue(httplib.__name__ == 'httplib') + if six.PY3: + import http.client as httplib + self.assertTrue(type(httplib) == types.ModuleType) + self.assertTrue(httplib.__name__ == 'http.client') + else: + import httplib + self.assertTrue(type(httplib) == types.ModuleType) + self.assertTrue(httplib.__name__ == 'httplib') def testSimpleImportWithAlias(self): @@ -93,9 +109,14 @@ def testSimpleImportWithAlias(self): self.assertTrue(type(mySys) == types.ModuleType) self.assertTrue(mySys.__name__ == 'sys') - import httplib as myHttplib - self.assertTrue(type(myHttplib) == types.ModuleType) - self.assertTrue(myHttplib.__name__ == 'httplib') + if six.PY3: + import http.client as myHttplib + self.assertTrue(type(myHttplib) == types.ModuleType) + self.assertTrue(myHttplib.__name__ == 'http.client') + else: + import httplib as myHttplib + self.assertTrue(type(myHttplib) == types.ModuleType) + self.assertTrue(myHttplib.__name__ == 'httplib') def testDottedNameImport(self): @@ -169,7 +190,7 @@ def testDottedNameImportFrom(self): self.assertTrue(pulldom.__name__ == 'xml.dom.pulldom') from xml.dom.pulldom import PullDOM - self.assertTrue(type(PullDOM) == types.ClassType) + self.assertTrue(type(PullDOM) == ClassType) self.assertTrue(PullDOM.__name__ == 'PullDOM') @@ -188,7 +209,7 @@ def testDottedNameImportFromWithAlias(self): self.assertTrue(myPulldom.__name__ == 'xml.dom.pulldom') from xml.dom.pulldom import PullDOM as myPullDOM - self.assertTrue(type(myPullDOM) == types.ClassType) + self.assertTrue(type(myPullDOM) == ClassType) self.assertTrue(myPullDOM.__name__ == 'PullDOM') @@ -233,7 +254,7 @@ def testExplicitAssemblyLoad(self): self.assertTrue(assembly != None) import System.Data - self.assertTrue(sys.modules.has_key('System.Data')) + self.assertTrue('System.Data' in sys.modules) assembly = Assembly.LoadWithPartialName('SpamSpamSpamSpamEggsAndSpam') self.assertTrue(assembly == None) @@ -340,10 +361,10 @@ def test_ClrListAssemblies(self): from clr import ListAssemblies verbose = list(ListAssemblies(True)) short = list(ListAssemblies(False)) - self.assertTrue(u'mscorlib' in short) - self.assertTrue(u'System' in short) - self.assertTrue('Culture=' in verbose[0]) - self.assertTrue('Version=' in verbose[0]) + self.assertTrue(six.u('mscorlib') in short) + self.assertTrue(six.u('System') in short) + self.assertTrue(six.u('Culture=') in verbose[0]) + self.assertTrue(six.u('Version=') in verbose[0]) def test_ClrAddReference(self): from clr import AddReference diff --git a/src/tests/test_property.py b/src/tests/test_property.py index 851ff8af0..4b00040ef 100644 --- a/src/tests/test_property.py +++ b/src/tests/test_property.py @@ -9,6 +9,12 @@ import sys, os, string, unittest, types from Python.Test import PropertyTest +import six + +if six.PY3: + IntType = int +else: + IntType = types.IntType class PropertyTests(unittest.TestCase): @@ -139,15 +145,15 @@ def testPropertyDescriptorGetSet(self): self.assertTrue(object.PublicStaticProperty == 0) descriptor = PropertyTest.__dict__['PublicStaticProperty'] - self.assertTrue(type(descriptor) != types.IntType) + self.assertTrue(type(descriptor) != IntType) object.PublicStaticProperty = 0 descriptor = PropertyTest.__dict__['PublicStaticProperty'] - self.assertTrue(type(descriptor) != types.IntType) + self.assertTrue(type(descriptor) != IntType) PropertyTest.PublicStaticProperty = 0 descriptor = PropertyTest.__dict__['PublicStaticProperty'] - self.assertTrue(type(descriptor) != types.IntType) + self.assertTrue(type(descriptor) != IntType) def testPropertyDescriptorWrongType(self): diff --git a/src/tests/test_subclass.py b/src/tests/test_subclass.py new file mode 100644 index 000000000..113397dba --- /dev/null +++ b/src/tests/test_subclass.py @@ -0,0 +1,119 @@ +# =========================================================================== +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# =========================================================================== +import clr +clr.AddReference('Python.Test') +clr.AddReference('System') + +import sys, os, string, unittest, types +from Python.Test import TestFunctions, SubClassTest, IInterfaceTest +from System.Collections.Generic import List + +# class that implements the test interface +class InterfaceTestClass(IInterfaceTest): + __namespace__ = "Python.Test" + + def foo(self): + return "InterfaceTestClass" + + def bar(self, x, i): + return "/".join([x] * i) + +# class that derives from a class deriving from IInterfaceTest +class DerivedClass(SubClassTest): + __namespace__ = "Python.Test" + + def foo(self): + return "DerivedClass" + + def base_foo(self): + return SubClassTest.foo(self) + + def super_foo(self): + return super(DerivedClass, self).foo() + + def bar(self, x, i): + return "_".join([x] * i) + + def return_list(self): + l = List[str]() + l.Add("A") + l.Add("B") + l.Add("C") + return l + +class SubClassTests(unittest.TestCase): + """Test subclassing managed types""" + + def testBaseClass(self): + """Test base class managed type""" + object = SubClassTest() + self.assertEqual(object.foo(), "foo") + self.assertEqual(TestFunctions.test_foo(object), "foo") + self.assertEqual(object.bar("bar", 2), "bar") + self.assertEqual(TestFunctions.test_bar(object, "bar", 2), "bar") + self.assertEqual(object.not_overriden(), "not_overriden") + self.assertEqual(list(object.return_list()), ["a", "b", "c"]) + self.assertEqual(list(SubClassTest.test_list(object)), ["a", "b", "c"]) + + def testInterface(self): + """Test python classes can derive from C# interfaces""" + object = InterfaceTestClass() + self.assertEqual(object.foo(), "InterfaceTestClass") + self.assertEqual(TestFunctions.test_foo(object), "InterfaceTestClass") + self.assertEqual(object.bar("bar", 2), "bar/bar") + self.assertEqual(TestFunctions.test_bar(object, "bar", 2), "bar/bar") + + x = TestFunctions.pass_through(object) + self.assertEqual(id(x), id(object)) + + def testDerivedClass(self): + """Test python class derived from managed type""" + object = DerivedClass() + self.assertEqual(object.foo(), "DerivedClass") + self.assertEqual(object.base_foo(), "foo") + self.assertEqual(object.super_foo(), "foo") + self.assertEqual(TestFunctions.test_foo(object), "DerivedClass") + self.assertEqual(object.bar("bar", 2), "bar_bar") + self.assertEqual(TestFunctions.test_bar(object, "bar", 2), "bar_bar") + self.assertEqual(object.not_overriden(), "not_overriden") + self.assertEqual(list(object.return_list()), ["A", "B", "C"]) + self.assertEqual(list(SubClassTest.test_list(object)), ["A", "B", "C"]) + + x = TestFunctions.pass_through(object) + self.assertEqual(id(x), id(object)) + + def testCreateInstance(self): + """Test derived instances can be created from managed code""" + object = TestFunctions.create_instance(DerivedClass) + self.assertEqual(object.foo(), "DerivedClass") + self.assertEqual(TestFunctions.test_foo(object), "DerivedClass") + self.assertEqual(object.bar("bar", 2), "bar_bar") + self.assertEqual(TestFunctions.test_bar(object, "bar", 2), "bar_bar") + self.assertEqual(object.not_overriden(), "not_overriden") + + x = TestFunctions.pass_through(object) + self.assertEqual(id(x), id(object)) + + object2 = TestFunctions.create_instance(InterfaceTestClass) + self.assertEqual(object2.foo(), "InterfaceTestClass") + self.assertEqual(TestFunctions.test_foo(object2), "InterfaceTestClass") + self.assertEqual(object2.bar("bar", 2), "bar/bar") + self.assertEqual(TestFunctions.test_bar(object2, "bar", 2), "bar/bar") + + y = TestFunctions.pass_through(object2) + self.assertEqual(id(y), id(object2)) + +def test_suite(): + return unittest.makeSuite(SubClassTests) + +def main(): + unittest.TextTestRunner().run(test_suite()) + +if __name__ == '__main__': + main() diff --git a/src/tests/test_thread.py b/src/tests/test_thread.py index 171efa3bb..22d4c9538 100644 --- a/src/tests/test_thread.py +++ b/src/tests/test_thread.py @@ -7,13 +7,19 @@ # FOR A PARTICULAR PURPOSE. # =========================================================================== -import sys, os, string, unittest, types, thread +import sys, os, string, unittest, types from Python.Test import ThreadTest +import six + +if six.PY3: + import _thread as thread +else: + import thread def dprint(msg): # Debugging helper to trace thread-related tests. - if 0: print msg + if 0: print(msg) class ThreadTests(unittest.TestCase): @@ -39,7 +45,7 @@ def testDoubleCallbackToPython(self): def testPythonThreadCallsToCLR(self): """Test calls by Python-spawned threads into managed code.""" # This test is very likely to hang if something is wrong ;) - import threading, thread, time + import threading, time from System import String done = [] diff --git a/subclasstest.cs b/subclasstest.cs new file mode 100644 index 000000000..64cea87c6 --- /dev/null +++ b/subclasstest.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Python.Test +{ + public interface IInterfaceTest + { + // simple test with no arguments + string foo(); + + // test passing objects and boxing primitives + string bar(string s, int i); + } + + public class SubClassTest : IInterfaceTest + { + public SubClassTest() + { + } + + // simple test with no arguments + public virtual string foo() + { + return "foo"; + } + + // test passing objects and boxing primitives + public virtual string bar(string s, int i) + { + return s; + } + + // virtual methods that aren't overriden in python still work + public virtual string not_overriden() + { + return "not_overriden"; + } + + public virtual IList return_list() + { + return new List { "a", "b", "c" }; + } + + public static IList test_list(SubClassTest x) + { + // calls into python if return_list is overriden + return x.return_list(); + } + } + + public class TestFunctions + { + public static string test_foo(IInterfaceTest x) + { + // calls into python if foo is overriden + return x.foo(); + } + + public static string test_bar(IInterfaceTest x, string s, int i) + { + // calls into python if bar is overriden + return x.bar(s, i); + } + + // test instances can be constructed in managed code + public static IInterfaceTest create_instance(Type t) + { + return (IInterfaceTest)t.GetConstructor(new Type[] {}).Invoke(new Object[] {}); + } + + // test instances pass through managed code unchanged + public static IInterfaceTest pass_through(IInterfaceTest s) + { + return s; + } + } +} diff --git a/test_subclass.py b/test_subclass.py new file mode 100644 index 000000000..5a8e0fb8d --- /dev/null +++ b/test_subclass.py @@ -0,0 +1,116 @@ +# =========================================================================== +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# =========================================================================== +import clr +clr.AddReference('Python.Test') +clr.AddReference('System') + +import sys, os, string, unittest, types +from Python.Test import TestFunctions, SubClassTest, IInterfaceTest +from System.Collections.Generic import List + +# class that implements the test interface +class InterfaceTestClass(IInterfaceTest): + def foo(self): + return "InterfaceTestClass" + + def bar(self, x, i): + return "/".join([x] * i) + +# class that derives from a class deriving from IInterfaceTest +class DerivedClass(SubClassTest): + + def foo(self): + return "DerivedClass" + + def base_foo(self): + return SubClassTest.foo(self) + + def super_foo(self): + return super(DerivedClass, self).foo() + + def bar(self, x, i): + return "_".join([x] * i) + + def return_list(self): + l = List[str]() + l.Add("A") + l.Add("B") + l.Add("C") + return l + +class SubClassTests(unittest.TestCase): + """Test subclassing managed types""" + + def testBaseClass(self): + """Test base class managed type""" + object = SubClassTest() + self.assertEqual(object.foo(), "foo") + self.assertEqual(TestFunctions.test_foo(object), "foo") + self.assertEqual(object.bar("bar", 2), "bar") + self.assertEqual(TestFunctions.test_bar(object, "bar", 2), "bar") + self.assertEqual(object.not_overriden(), "not_overriden") + self.assertEqual(list(object.return_list()), ["a", "b", "c"]) + self.assertEqual(list(SubClassTest.test_list(object)), ["a", "b", "c"]) + + def testInterface(self): + """Test python classes can derive from C# interfaces""" + object = InterfaceTestClass() + self.assertEqual(object.foo(), "InterfaceTestClass") + self.assertEqual(TestFunctions.test_foo(object), "InterfaceTestClass") + self.assertEqual(object.bar("bar", 2), "bar/bar") + self.assertEqual(TestFunctions.test_bar(object, "bar", 2), "bar/bar") + + x = TestFunctions.pass_through(object) + self.assertEqual(id(x), id(object)) + + def testDerivedClass(self): + """Test python class derived from managed type""" + object = DerivedClass() + self.assertEqual(object.foo(), "DerivedClass") + self.assertEqual(object.base_foo(), "foo") + self.assertEqual(object.super_foo(), "foo") + self.assertEqual(TestFunctions.test_foo(object), "DerivedClass") + self.assertEqual(object.bar("bar", 2), "bar_bar") + self.assertEqual(TestFunctions.test_bar(object, "bar", 2), "bar_bar") + self.assertEqual(object.not_overriden(), "not_overriden") + self.assertEqual(list(object.return_list()), ["A", "B", "C"]) + self.assertEqual(list(SubClassTest.test_list(object)), ["A", "B", "C"]) + + x = TestFunctions.pass_through(object) + self.assertEqual(id(x), id(object)) + + def testCreateInstance(self): + """Test derived instances can be created from managed code""" + object = TestFunctions.create_instance(DerivedClass) + self.assertEqual(object.foo(), "DerivedClass") + self.assertEqual(TestFunctions.test_foo(object), "DerivedClass") + self.assertEqual(object.bar("bar", 2), "bar_bar") + self.assertEqual(TestFunctions.test_bar(object, "bar", 2), "bar_bar") + self.assertEqual(object.not_overriden(), "not_overriden") + + x = TestFunctions.pass_through(object) + self.assertEqual(id(x), id(object)) + + object2 = TestFunctions.create_instance(InterfaceTestClass) + self.assertEqual(object2.foo(), "InterfaceTestClass") + self.assertEqual(TestFunctions.test_foo(object2), "InterfaceTestClass") + self.assertEqual(object2.bar("bar", 2), "bar/bar") + self.assertEqual(TestFunctions.test_bar(object2, "bar", 2), "bar/bar") + + y = TestFunctions.pass_through(object2) + self.assertEqual(id(y), id(object2)) + +def test_suite(): + return unittest.makeSuite(SubClassTests) + +def main(): + unittest.TextTestRunner().run(test_suite()) + +if __name__ == '__main__': + main()