How would a Python script running on Linux call a routine in a Python script running under Wine?

Question:

I have a Python (3) script running on Linux, referred to as the main script, which has to call a routine from a proprietary DLL. So far, I have solved this with Wine using the following construct:

# Main script running on Linux
import subprocess
# [...]
subprocess.Popen('echo "python dll_call.py %s" | wine cmd &' % options, shell = True)
# [...]

The script dll_call.py is executed by a Windows Python (3) interpreter installed under Wine. It dumps the return values into a file which is then picked up by the waiting main script. It’s not exactly reliable and agonizingly slow if I have to do this a few times in a row.

I’d like to start the script dll_call.py once, offering some type of a simple server, which should expose the required routine in some sort of way. At the end of the day, I’d like to have a main script looking somewhat like this:

# Main script running on Linux
import subprocess
# [...]
subprocess.Popen('echo "python dll_call_server.py" | wine cmd &', shell = True)
# [...]
return_values = call_into_dll(options)

How can this be implemented best (if speed is required and security not a concern)?


Thank you @jsbueno and @AustinHastings for your answers and suggestions.

For those having similar problems: Inspired by the mentioned answers, I wrote a small Python module for calling into Windows DLLs from Python on Linux. It is based on IPC between a regular Linux/Unix Python process and a Wine-based Python process. Because I have needed it in too many different use-cases / scenarios, I designed it as a "generic" ctypes module drop-in replacement, which does most of the required plumbing automatically in the background.

Example: Assume you’re in Python on Linux, you have Wine installed, and you want to call into msvcrt.dll (the Microsoft C runtime library). You can do the following:

from zugbruecke import ctypes
dll_pow = ctypes.cdll.msvcrt.pow
dll_pow.argtypes = (ctypes.c_double, ctypes.c_double)
dll_pow.restype = ctypes.c_double
print('You should expect "1024.0" to show up here: "%.1f".' % dll_pow(2.0, 10.0))

Source code (LGPL), PyPI package & documentation. It’s still a bit rough around the edges (i.e. alpha and insecure), but it does handle most types of parameters (including pointers).

Asked By: s-m-e

||

Answers:

You want to communicate between two processes, where one of them is obscured by being under the control of the WINE engine.

My first thought here is to use a very decoupled form of IPC. There are just too many things that can go wrong with tight coupling and something like WINE involved.

And finally, how can this be made easy for someone new to this kind of stuff?

The obvious answer is to set up a web server. There are plenty of tutorials using plenty of packages in Python to respond to HTTP requests, and to generate HTTP requests.

So, set up a little HTTP responder in your WINE process, listen to some non-standard port (not 8080 or 80), and translate requests into calls to your DLL. If you’re clever, you’ll interpret web requests (http://localhost:108000/functionname?arg1=foo&arg2=bar) into possibly different DLL calls.

On the other side, create a HTTP client in your non-WINE code and make requests to your server.

Answered By: aghast

You can use the XMLRPC client and servers built-in Python’s stdlib to do what you want. Just make your Wine-Python expose the desired functions as XMLRPC methods, and make an inter-process call from any other Python program to that.

It also works for calling functions running in Jython or IronPython from CPython, and also across Python2 and Python3 – the examples included in the module documentation themselves should be enough.Just check the docs: https://docs.python.org/2/library/xmlrpclib.html

If you need the calls to be asynchronous on the client side, or the server site to respond to more than one process, you can find other frameworks over which to build the calls – Celery should also work across several different Pythons while preserving call compatibility, and it is certainly enough performance-wise.

Answered By: jsbueno