I’m currently testing IronPython (I know a bit C# and a bit CPython). Now I want to use my custom class in a Python script.
I have the following project structure:
Solution IPTest
---Project IPTest
------Namespace IPTest
---------Program class (main)
---------Elem class
------Scripts
---------Worker.py
Where Elem is a simple:
public class Elem {
public string Name;
}
I can use the Python scripts in the .NET program without any problems like:
ScriptEngine engine = Python.CreateEngine();
ScriptSource source = engine.CreateScriptSourceFromFile("./Scripts/Worker.py");
ScriptScope scope = engine.CreateScope();
source.Execute(scope);
// or var worker = Python.CreateRuntime().UseFile("./Scripts/Worker.py");
dynamic Worker = scope.GetVariable("Worker");
dynamic worker = Worker();
var res1 = worker.add(4, 5);
However, I can’t figure out how to reference the hosting Assembly in the Python script. After some research I tried the following:
import sys
import System
sys.path.append(System.IO.Directory.GetCurrentDirectory()) #make sure assembly dir is in sys.path
import clr
clr.AddReference(“IPTest.exe”)
# or clr.AddReferenceToFile(r"IPTest.exe")
# or clr.AddReference(r"<fullpath>\IPTest\bin\Debug\IPTest.exe")
# or clr.AddReference(“../IPTest.exe”) #when not adding workingdir to sys.path
from IPTest import Elem
# or from IPTest.IPTest import Elem
# or import Elem
Neither works. I get two different error messages:
When adding workingdir to sys.path or using relative path or use
AddReferenceToFile: No module named IPTest
When using the absolute path: The given assembly name or codebase
was invalid. (Exception from HRESULT: 0x80131047)
I checked that the Assembly name is really IPTest and tried to use a dll instead of exe - though it should usually not make any difference and surprisingly doesn’t work either.
disclaimer: The solution described here:
engine.Runtime.LoadAssembly(Assembly.GetExecutingAssembly()); //in c#
from IPTest import Elem # in python script
works just fine. However I think it should also work by referencing in the script file (which would be nicer).
Probably I’m missing something obvious, but I just can’t see it so any hint is much appreciated.
Try clr.AddReferenceToFile(r'IPTest.exe') as mentioned in your error:
When adding workingdir to sys.path or using relative path or use AddReferenceToFile: No module named IPTest
Related
I have an embedded resource in my dll which is a python script. I'd like to make the classes and functions in that resource available to the python engine, so I can execute external .py files (as __main__) which would be able to do something like
import embedded_lib # where embedded_lib is an embedded python script
Is there a way to accomplish this? I was hoping there will be some sort of IronPython.ImportModule('module_name', source) so I've looked through IronPython docs and couldn't find anything, but I'm hoping I'm just bad at looking.. Maybe there is some way to intercept a call to import and load my script that way?
It is possible. You just need to add search paths to ScriptEngine object like this:
var paths = engine.GetSearchPaths();
paths.Add(yourLibsPath); // add directory to search
or
engine.SetSearchPaths(paths);
Then you could use any module in directories, which you add:
import pyFileName # without extension .py
Update
OK. If you want to use embedded resource strings like module, you may use this code:
var scope = engine.CreateScope(); // Create ScriptScope to use it like a module
engine.Execute("import clr\n" +
"clr.AddReference(\"System.Windows.Forms\")\n" +
"import System.Windows.Forms\n" +
"def Hello():\n" +
"\tSystem.Windows.Forms.MessageBox.Show(\"Hello World!\")", scope); // Execute code from string in scope.
Now you have a ScriptScope object (scope in code) containing all executed functions. And you may insert them into another scope like this:
foreach (var keyValuePair in scope.GetItems())
{
if(keyValuePair.Value != null)
anotherScope.SetVariable(keyValuePair.Key, keyValuePair.Value);
}
Or you can execute your scripts right in this ScriptScope:
dynamic executed = engine.ExecuteFile("Filename.py", scope);
executed.SomeFuncInFilename();
And in this script you may use all functions without import:
def SomeFuncInFilename():
Hello() # uses function from your scope
I am trying to load a .net assembly using LuaInterface. If I place the assembly in the same folder as my executable (and my LuaInterface.dll and LuaNet.dll) then everything works great. I would like to move the assembly into a different folder, but when I try that I get "A .NET exception occured in user-code". I have tried:
package.path = package.path .. "C:\\path\\to\\my\\assembly\\?.dll"
luanet.load_assembly("MyAssembly")
and
luanet.load_assembly("C:\\path\\to\\my\\assembly\\MyAssembly")
and
luanet.load_assembly("C:\\path\\to\\my\\assembly\\MyAssembly.dll")
All of these return the .NET exception error. Is there a way to define the path that LuaInterface uses?
Your assembly is loaded by your "hosting" executable, and not really loaded by the Lua environment itself. luanet.load_assembly("MyAssembly") simply makes the assembly accessible to the Lua environment. For example (C#):
using MyAssembly; //you can't compile unless MyAssembly is available
namespace LuaRunner
{
class LuaRunner
{
void DoLua()
{
using (LuaInterface.Lua lua = new LuaInterface.Lua())
{
lua.DoString("luanet.load_assembly('MyAssembly')");
//... do what you want within Lua with MyAssembly
}
}
}
}
Your running program is the "host" for Lua to run within, so it's your running program that actually loads MyAssembly. Your executable needs a reference to MyAssembly.dll, (and needs to be able to find it at runtime in the usual locations).
To search other assemblies, set the package.cpath variable. For example:
package.cpath = DATA_DIR .. "\\clibs\\?.dll;" .. package.cpath
From the Lua 5.1 documentation:
require (modname)
First require queries package.preload[modname]. If it has a value, this value (which should be a function) is the loader. Otherwise require searches for a Lua loader using the path stored in package.path. If that also fails, it searches for a C loader using the path stored in package.cpath.
package.cpath
The path used by require to search for a C loader.
Lua initializes the C path package.cpath in the same way it initializes the Lua path package.path, using the environment variable LUA_CPATH or a default path defined in luaconf.h.
I'm trying to extract the version number from a AssemblyInfo.cs file!
And I'm trying to use System.Reflection.Assembly.LoadFile(path); But while doing this I get a BadImageFormatException; "The module was expected to contain an assembly manifest. (Exception from HRESULT: 0x80131018)". So now I wounder, is that not a possible way to go about it? And should I use RegEx instead?
I have read many examples with GetExecutingAssembly() but I do want to get the version from an other project.
Clarification: I want to read the version info from the AssemblyInfo.cs file! And not from a compiled file. I'm trying to make a tool to update my version numbers before I make a new release.
You can get Assembly version without loading it as:
using System.Reflection;
using System.IO;
...
// Get assembly
AssemblyName currentAssembly = AssemblyName.GetAssemblyName(path);
Version assemblyVersion = currentAssembly.Version;
Edit:
If you want to read file then you can do it like this:
string path = #"d:\AssemblyInfo.cs";
if (File.Exists(path))
{
// Open the file to read from.
string[] readText = File.ReadAllLines(path);
var versionInfoLines = readText.Where(t => t.Contains("[assembly: AssemblyVersion"));
foreach (string item in versionInfoLines)
{
string version = item.Substring(item.IndexOf('(') + 2, item.LastIndexOf(')') - item.IndexOf('(') - 3);
//Console.WriteLine(Regex.Replace(version, #"\P{S}", string.Empty));
Console.WriteLine(version);
}
}
//Output
1.0.*
1.0.0.0
Hope this help...
You can specify the target assembly path in AssemblyName.GetAssemblyName
AssemblyName.GetAssemblyName("ProjectB.exe").Version
AssemblyInfo.cs file gets compiled to IL assembly.
If you load that assembly you can read the version with all the examples that you have already seen. Which is reading an embedded version information from a compiled assembly file, and it may be overwritten by compilation process to a value different from what is in AssemblyInfo.cs
However it sounds like what you want instead is to read a version number from AssemblyInfo.cs text file, without compiling it down.
If this is the case you really just have to use regex with a format appropriate for your project, or even come up with a convention that will keep it simple.
This could be as simple as
var versionMatch = Regex.Match(File.ReadAllText(filename), #"AssemblyVersion\s*\(\s*""([0-9\.\*]*?)""\s*\)");
if (versionMatch.Success)
{
Console.WriteLine(versionMatch.Groups[1].Value);
}
You would have to consider convention around what goes there, since 1.0.* is a valid version string that translates to timestamp values of form 1.0.nnn.mmm at compile time, and nnn and mmm part closely guessable but not precisely guessable.
It sounds like you're trying to load an assembly compiled for x86 in an x64 environment or vice-versa.
Ensure the assembly this code resides in is built for the same environment as the target and you can get it with the examples it sounds like you've read.
You can proceed with Assembly.GetName().Version where your assembly could be the type of your class
public class Test
{
public static void Main()
{
Console.WriteLine("Current assembly : " + typeof(Test).Assembly.GetName().Version);
}
}
For the test application I have working on, shows me below details using above code:
I'm looking to replicate the following in IronPython and searching has so far been fruitless and/or disappointing.
namespace Groceries
{
public class ChocolateMilk : Milk
{
// Other stuff here
}
}
The idea would be that the compiled Python DLL will be loaded into a C# program through System.Reflection.Assembly.Load and a GetType("Groceries.ChocolateMilk") on the loaded DLL would not return null.
The most recent answer I was able to find was in 2008 and said that it was impossible without using the Hosting API - http://lists.ironpython.com/pipermail/users-ironpython.com/2008-October/008684.html.
Any suggestions on how to accomplish this would be greatly appreciated. Any conclusions that this is currently impossible to do via IronPython will also be appreciated, but less so.
I'm a bit confused on what you're asking here. Are you trying to instantiate that C# code in your IronPython modules? Or do you have the equivalent classes written in IronPython and you want to instantiate them in your C# code?
Based on the link you posted, I suppose you're going for the latter and have IronPython classes that you want instantiated in your C# code. The answer is, you cannot directly instantiate them. When you compile IronPython code to an assembly, you cannot use the types defined there with your regular .NET code since there is not a one-to-one mapping between IronPython classes and .NET classes. You would have to host the assembly in your C# project and instantiate it that way.
Consider this module, Groceries.py compiled to Groceries.dll residing in the working directory:
class Milk(object):
def __repr__(self):
return 'Milk()'
class ChocolateMilk(Milk):
def __repr__(self):
return 'ChocolateMilk()'
To host the module in your C# code:
using System;
using IronPython.Hosting;
using System.IO;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var engine = Python.CreateEngine();
var groceriesPath = Path.GetFullPath(#"Groceries.dll");
var groceriesAsm = Assembly.LoadFile(groceriesPath);
engine.Runtime.LoadAssembly(groceriesAsm);
dynamic groceries = engine.ImportModule("Groceries");
dynamic milk = groceries.ChocolateMilk();
Console.WriteLine(milk.__repr__()); // "ChocolateMilk()"
}
}
Otherwise to go the other way and create an instance of your .NET type in your IronPython code (as your title suggests). You'd need to add the path to your assembly, reference it, then you could instantiate it as needed.
# add to path
import sys
sys.path.append(r'C:\path\to\assembly\dir')
# reference the assembly
import clr
clr.AddReferenceToFile(r'Groceries.dll')
from Groceries import *
chocolate = ChocolateMilk()
print(chocolate)
Can someone recommend a workaround for this ironpython bug?
I have a class contained within an external class library. I consume this class inside an embedded ironpython instance. When the class is retrieved from the scope by my c# app, the classes don't seem to match up!
My python script:
import sys
import clr
from ExternalAssembly import *
from IronPythonBug import *
internalClass = InternalClass("internal")
externalClass = ExternalClass("external")
My c# app:
internalClass = scope.GetVariable("internalClass");
externalClass = scope.GetVariable("externalClass");
if (internalClass is InternalClass)
Console.WriteLine("IternalClass matches");
else
Console.WriteLine("Error: InternalClass does not match");
if (externalClass is ExternalClass)
Console.WriteLine("ExternalClass matches");
else
Console.WriteLine("Error: ExternalClass does not match");
Console output:
IternalClass matches
Error: ExternalClass does not match
feel free to download a project that illustrates this bug:
http://www.virtual-chaos.net/zip/IronPythonBug.zip
This is caused by CLR loader contexts. The call to Assembly.LoadFile loads another copy of the assembly into a different context - giving you a duplicate set of types but with different identities. Instead of using Assembly.LoadFile to get the assembly object use typeof(ExternalClass).Assembly.
Do externalClass.GetType() and inspect the properties.
Seeing InternalClass is also from the same assembly, compare that type with the above one too.