I am trying to use the command find_library() from ctypes but I'm getting an error that I don't understand its reason. I am working on Windows
This is the code:
import ctypes
from ctypes.util import find_library
import numpy
from string import atoi
from time import sleep
# Class constants
#nidaq = ctypes.windll.nicaiu
nidaq = ctypes.cdll.LoadLibrary(find_library('NIDAQmx'))
And this is the error I'm getting:
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
nidaq = ctypes.cdll.LoadLibrary(find_library('NIDAQmx'))
File "C:\Python27\lib\ctypes\__init__.py", line 443, in LoadLibrary
return self._dlltype(name)
File "C:\Python27\lib\ctypes\__init__.py", line 365, in __init__
self._handle = _dlopen(self._name, mode)
TypeError: expected string or Unicode object, NoneType found
Should I place NIDAQmx in a specific place for example so that it can be found? Or this is unrelated?
Thanks!
ctypes is a foreign function library for Python. It provides C compatible data types, and allows calling functions in DLLs or shared libraries. It can be used to wrap these libraries in pure Python.
ctypes is the de facto standard library for interfacing with C/C++ from CPython, and it provides not only full access to the native C interface of most major operating systems (e.g., kernel32 on Windows, or libc on *nix), but also provides support for loading and interfacing with dynamic libraries, such as DLLs or ...
A cubit is an ancient unit based on the forearm length from the middle fingertip to the elbow bottom. Cubits of various lengths were employed in many parts of the world in antiquity, during the Middle Ages and as recently as Early Modern Times.
On Windows, find_library
searches the directories in the PATH
environment variable, which isn't the real search order for desktop applications that's used by the Windows loader. Notably find_library
doesn't include the application directory and the current directory.
Calling Windows SearchPath
would be closer, but not much closer given DLL activation contexts and other APIs such as SetDllDirectory
or the newer APIs SetDefaultDllDirectories
and AddDllDirectory
.
Given there's no simple way to replicate the search that's used by the Windows loader, just load the DLL by name using either CDLL
(cdecl) or WinDLL
(stdcall):
nidaq_cdecl = ctypes.CDLL('NIDAQmx')
nidaq_stdcall = ctypes.WinDLL('NIDAQmx')
You can add the DLL directory to PATH
dynamically at runtime (in contrast to the Linux loader's caching of LD_LIBRARY_PATH
at startup). For example, say your DLL dependencies are in the "dlls" subdirectory of your package. You can prepend this directory as follows:
import os
basepath = os.path.dirname(os.path.abspath(__file__))
dllspath = os.path.join(basepath, 'dlls')
os.environ['PATH'] = dllspath + os.pathsep + os.environ['PATH']
Alternatively, you can use a context manager that calls GetDllDirectory
and SetDllDirectory
to temporarily modify the search slot that's normally occupied by the current working directory. Bear in mind that, just like modifying PATH
, this modifies global process data, so care should be taken when using multiple threads. An advantage to this approach is that it doesn't modify the search path that CreateProcess
uses to find executables.
import os
import ctypes
from ctypes import wintypes
from contextlib import contextmanager
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
def check_dword(result, func, args):
if result == 0:
last_error = ctypes.get_last_error()
if last_error != 0:
raise ctypes.WinError(last_error)
return args
def check_bool(result, func, args):
if not result:
last_error = ctypes.get_last_error()
if last_error != 0:
raise ctypes.WinError(last_error)
else:
raise OSError
return args
kernel32.GetDllDirectoryW.errcheck = check_dword
kernel32.GetDllDirectoryW.argtypes = (wintypes.DWORD, # _In_ nBufferLength
wintypes.LPWSTR) # _Out_ lpBuffer
kernel32.SetDllDirectoryW.errcheck = check_bool
kernel32.SetDllDirectoryW.argtypes = (wintypes.LPCWSTR,) # _In_opt_ lpPathName
@contextmanager
def use_dll_dir(dll_dir):
size = newsize = 0
while newsize >= size:
size = newsize
prev = (ctypes.c_wchar * size)()
newsize = kernel32.GetDllDirectoryW(size, prev)
kernel32.SetDllDirectoryW(os.path.abspath(dll_dir))
try:
yield
finally:
kernel32.SetDllDirectoryW(prev)
For example:
if __name__ == '__main__':
basepath = os.path.dirname(os.path.abspath(__file__))
dllspath = os.path.join(basepath, 'dlls')
with use_dll_dir(dllspath):
nidaq = ctypes.CDLL('NIDAQmx')
Of course, if you're only interested in setting the DLL directory once at startup the problem is much simpler. Just call SetDllDirectoryW
directly.
Another approach is to call LoadLibraryEx
with the flag LOAD_WITH_ALTERED_SEARCH_PATH
, which temporarily adds the loaded DLL directory to the search path. You need to load the DLL using an absolute path, else the behavior is undefined. For convenience we can subclass ctypes.CDLL
and ctypes.WinDLL
to call LoadLibraryEx
instead of LoadLibrary
.
import ctypes
from ctypes import wintypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
def check_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
kernel32.LoadLibraryExW.errcheck = check_bool
kernel32.LoadLibraryExW.restype = wintypes.HMODULE
kernel32.LoadLibraryExW.argtypes = (wintypes.LPCWSTR,
wintypes.HANDLE,
wintypes.DWORD)
class CDLLEx(ctypes.CDLL):
def __init__(self, name, mode=0, handle=None,
use_errno=True, use_last_error=False):
if handle is None:
handle = kernel32.LoadLibraryExW(name, None, mode)
super(CDLLEx, self).__init__(name, mode, handle,
use_errno, use_last_error)
class WinDLLEx(ctypes.WinDLL):
def __init__(self, name, mode=0, handle=None,
use_errno=False, use_last_error=True):
if handle is None:
handle = kernel32.LoadLibraryExW(name, None, mode)
super(WinDLLEx, self).__init__(name, mode, handle,
use_errno, use_last_error)
Here are all of the available LoadLibraryEx
flags:
DONT_RESOLVE_DLL_REFERENCES = 0x00000001
LOAD_LIBRARY_AS_DATAFILE = 0x00000002
LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008
LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010 # NT 6.1
LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020 # NT 6.0
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040 # NT 6.0
# These cannot be combined with LOAD_WITH_ALTERED_SEARCH_PATH.
# Install update KB2533623 for NT 6.0 & 6.1.
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100
LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200
LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400
LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000
For example:
if __name__ == '__main__':
basepath = os.path.dirname(os.path.abspath(__file__))
dllpath = os.path.join(basepath, 'dlls', 'NIDAQmx.dll')
nidaq = CDLLEx(dllpath, LOAD_WITH_ALTERED_SEARCH_PATH)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With