1

Using Ironpython, I created a .dll from a .py file. It has classes and respective functions that I want to call to be used in c#. I created the .dll so that I can hide the source from the user.

Here is what I have tried:

    ScriptEngine engine = Python.CreateEngine();
    scope = engine.CreateScope();
    engine.Runtime.LoadAssembly(Assembly.LoadFile(fullPath2DLL));
    scope = engine.ImportModule("Simulation");

However, it cannot find "Simulation".

Also, I want to import the whole script at once so I can call whatever, whenever [Rather than the class 'Simulation'].

  • Do you have to load the .dll at run time? Can you not just add a reference from your C# project? – Colin Grealy Sep 17 '15 at 21:03
  • 1
    @ColinGrealy I do not believe it works that easily. Since this is a python script into a dll, I must use the ScriptEngine and ScriptScope to load these in to be used at run time. It is for a winform app. –  Sep 17 '15 at 21:05
  • Sorry, you're right. Looks like you can't actually do this. You'll have to load the .py scripts instead. – Colin Grealy Sep 17 '15 at 21:12
  • @ColinGrealy No worries. Yes the goal is to not have the .PY available to the user. I want to hide this. I have spent weeks on this and haven't found a working solution. Seems like no one has really done it? –  Sep 17 '15 at 21:15
  • From the IronPython documentation, it doesn't look like this is possible with a dll. http://ironpython.net/documentation/dotnet/dotnet.html#id86 If you really don't want the .py file available to the user, you could encrypt and rename it? Then just decrypt as you need it. – Colin Grealy Sep 17 '15 at 21:18
  • @ColinGrealy Thanks for the help. When you say encrypt, does that mean make the code consistently gibberish in a sense that only the computer can decode what is going on? I am newish to this process in Python and it seems odd that there is no way to have it interpret byte code on its own. –  Sep 17 '15 at 21:20
  • I meant encrypt the .py file. You can use something as simple as renaming the file and adding 1 to every byte or you could go the whole hog and use the .net framework encryption classes. You then load the file, decrypt it to a string and pass that string to the dlr hosting api – Colin Grealy Sep 17 '15 at 21:45

1 Answers1

2

Many things could go wrong, so I'll just show you complete example which works. Let's take this python code that I grabbed in some example:

MyGlobal = 5

class Customer(object):
"""A customer of ABC Bank with a checking account. Customers have the
following properties:

Attributes:
    name: A string representing the customer's name.
    balance: A float tracking the current balance of the customer's account.
"""

def __init__(self, name, balance=0.0):
    """Return a Customer object whose name is *name* and starting
    balance is *balance*."""
    self.name = name
    self.balance = balance

def withdraw(self, amount):
    """Return the balance remaining after withdrawing *amount*
    dollars."""
    if amount > self.balance:
        raise RuntimeError('Amount greater than available balance.')
    self.balance -= amount
    return self.balance

def deposit(self, amount):
    """Return the balance remaining after depositing *amount*
    dollars."""
    self.balance += amount
    return self.balance

Now let's open ipy and compile that into dll with:

>>> import clr
>>> clr.CompileModules("path_to.dll", "path_to.py");

Now we have dll. As you see python code contains class definition, and our goal is to create instance of that class in C# and call some methods.

 public class Program {
    private static void Main(string[] args) {
        ScriptEngine engine = Python.CreateEngine();            
        engine.Runtime.LoadAssembly(Assembly.LoadFile(@"path_to.dll"));
        // note how scope is created. 
        // "test" is just the name of python file from which dll was compiled. 
        // "test.py" > module named "test"
        var scope = engine.Runtime.ImportModule("test");
        // fetching global is as easy as this
        int g = scope.GetVariable("MyGlobal");
        // writes 5
        Console.WriteLine(g);
        // how class type is grabbed
        var customerType = scope.GetVariable("Customer");
        // how class is created using constructor with name (note dynamic keyword also)
        dynamic customer = engine.Operations.CreateInstance(customerType, "Customer Name");
        // calling method on dynamic object
        var balance = customer.deposit(10.0m);
        // this outputs 10, as it should
        Console.WriteLine(balance);
        Console.ReadKey();
    }
}
Evk
  • 98,527
  • 8
  • 141
  • 191
  • var scope = engine.Runtime.ImportModule("test"); what is test? Also I have a global variable that I would like to access. I was hoping to ImportModule ALL. Otherwise I am not too sure how to get that variable –  Sep 17 '15 at 21:56
  • Updated my answer. Note how code now grabs value of global, and explanation of what is "test" in code comments. – Evk Sep 17 '15 at 22:07
  • Ah OK, I didn't realize it would be able to extract that information. I will try this out now, thx! –  Sep 17 '15 at 22:11
  • It says 'No module named urllib2'. How do I attach ironpython to this? –  Sep 17 '15 at 22:15
  • Well I provided you with simple example how to do what you want to. First verify it works for you as is, and you understand how. Then, if your more complex example doesnt - provide its python code, otherwise I will be pointlessly guessing why it fails. As a wild guess of this type - try import urllib2 into the scope first – Evk Sep 17 '15 at 22:20
  • Sorry, I ordered the statements incorrectly. Works well, thank you very much –  Sep 17 '15 at 22:25
  • Great, thanks! How would I call a module-level function? Also through scope? For instance, how would I call os.getcwd() or os.path.join('a', 'b') ? – Sven Nov 17 '15 at 11:06
  • Never mind my previous question. I figured it out. At least if the scope is made dynamic, as in `dynamic os = engine.ImportModule("os");`, I can then do `dynamic p = os.getcwd()`. Very cool. – Sven Nov 17 '15 at 11:20