30

I have a Python module, wrapper.py, that wraps a C DLL. The DLL lies in the same folder as the module. Therefore, I use the following code to load it:

myDll = ctypes.CDLL("MyCDLL.dll")

This works if I execute wrapper.py from its own folder. If, however, I run it from elsewhere, it fails. That's because ctypes computes the path relative to the current working directory.

My question is, is there a way by which I can specify the DLL's path relative to the wrapper instead of the current working directory? That will enable me to ship the two together and allow the user to run/import the wrapper from anywhere.

G S
  • 35,511
  • 22
  • 84
  • 118
  • 1
    I use the approach in the [selected answer](http://stackoverflow.com/a/2980501/1020470) but in one case I had a dll that imported an export from another dll, and it wouldn't load. The solution was to load the other dll first, EG: `_DIRNAME = os.path.dirname(__file__); required_dll = ctypes.cdll.LoadLibrary(os.path.join(_DIRNAME, required.dll)); main_dll = ctypes.cdll.LoadLibrary(os.path.join(_DIRNAME, main.dll))` voila that works! – Mark Mikofski Aug 20 '13 at 21:24

4 Answers4

27

You can use os.path.dirname(__file__) to get the directory where the Python source file is located.

Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
  • 2
    Relative path like this works just fine, no need for `os.path.abspath()` or adding this path to `$PATH` (`%PATH%`). One caveat, if your dll requires another dll, then you will have to load that too, before you load this one, see my comment under OP's question. – Mark Mikofski Aug 20 '13 at 21:33
14

Expanding on Matthew's answer:

import os.path
dll_name = "MyCDLL.dll"
dllabspath = os.path.dirname(os.path.abspath(__file__)) + os.path.sep + dll_name
myDll = ctypes.CDLL(dllabspath)

This will only work from a script, not the console nor from py2exe.

fmark
  • 57,259
  • 27
  • 100
  • 107
  • I like the use of os.path.sep in general, but are there any Windows installations that don't use ';' for a path separator? – Chris B. Jun 05 '10 at 13:36
  • 1
    This path separator is a directory separator, i.e. '\' on Windows. This takes a different approach to yours, loading the dll through its absolute file path rather than modifying the PATH environmental variable. I think I like yours more, as it has a fallback to loading the default system dll if one is not supplied. – fmark Jun 05 '10 at 13:43
  • 10
    [`os.path.join`](http://docs.python.org/2/library/os.path.html#os.path.join) is a lot easier on the fingers and the eyes. EG: `os.path.abspath(os.path.join(os.path.dirname(__file__), dll_name))` – Mark Mikofski Aug 20 '13 at 21:36
  • 1
    I think [Chris B.](http://stackoverflow.com/users/9161/chris-b) was confusing [`os.pathsep`](http://docs.python.org/2/library/os.html#os.pathsep) with [`os.sep`](http://docs.python.org/2/library/os.html#os.sep). FYI: `os.sep` and `os.path.sep` are equivalent. – Mark Mikofski Aug 20 '13 at 21:48
12

I always add the directory where my DLL is to the path. That works:

os.environ['PATH'] = os.path.dirname(__file__) + ';' + os.environ['PATH']
windll.LoadLibrary('mydll.dll')

Note that if you use py2exe, this doesn't work (because __file__ isn't set). In that case, you need to rely on the sys.executable attribute (full instructions at http://www.py2exe.org/index.cgi/WhereAmI)

Chris B.
  • 85,731
  • 25
  • 98
  • 139
  • 1
    [`os.pathsep`](http://docs.python.org/2/library/os.html#os.pathsep) is platform safe way to indicate the separator used between path entries in the system `$PATH` or `%PATH%` environmental variable. However I would **not** advocate this answer because IMO it's unpythonic to edit environmental variables at runtime; IMO they should be set during installation by `setup.py`. Instead I think the [selected answer](http://stackoverflow.com/a/2980501/1020470) does the trick! – Mark Mikofski Aug 20 '13 at 21:45
  • 4
    @MarkMikofski Thats silly. Any application that edits environment variables is the only application that sees the change. They do not persist. It would be a security nightmare if they did. setup.py cannot change the environment variables for the running application anyways. Edit your environment to your heart's content. – Tritium21 Jun 14 '15 at 15:16
  • @tritium21 that wasn't the reason for my comment, although I think I was confusing setting `PATH` and `sys.path`. In general I think whatever is robust, simple and widely practised is best. IMO adding the DLL's location to `PATH` is not as robust as just using the DLL full path. – Mark Mikofski Jun 14 '15 at 15:28
  • @tritium21 BTW: love your handle – Mark Mikofski Jun 14 '15 at 15:29
  • @tritium21 just realized that I have done exactly what Chris B.'s answer says numerous times so guess I have to eat my words – Mark Mikofski Jun 14 '15 at 15:35
  • Another reason this answer is helpful is because it works when the DLL you are loading in turn loads other DLLs whose directory is not in the PATH (e.g. they are sitting in the same dir as the DLL you're loading). – Dave Brueck Mar 01 '19 at 23:14
  • 2
    Be warned that adding the DLL directory to the PATH environment variable no longer works in Python 3.8+. You need to call the new `os.add_dll_directory()` instead (you may want to call it conditionally - `hasattr(os, "add_dll_directory"): ...` - and otherwise modify PATH for Python 3.7 and below. Also see https://docs.python.org/3.8/whatsnew/3.8.html#changes-in-the-python-api – CodeManX Sep 21 '20 at 11:19
0

Another version:

dll_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'MyCDLL.dll')
myDll = ctypes.CDLL(dll_file)
Shtefan
  • 742
  • 12
  • 14