ctypes中的find_library()
我正在尝试使用ctypes中的find_library()命令,但遇到了一个我不明白的错误。我是在Windows系统上工作。
这是我的代码:
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'))
这是我遇到的错误:
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
我是否需要把NIDAQmx放在某个特定的位置,这样它才能被找到?还是这和这个问题无关?
谢谢!
2 个回答
你在找的库文件是不是在你电脑上常见的位置?find_library
这个功能不会随便在你的文件系统里搜索,它只会在一些特定的地方查找,这些地方在 ctypes/macholib/dyld.py
模块里有列出来(可以看看 dyld_find
这个函数)。
如果你的库文件在比如说 /usr/lib
这个地方,那它应该能被找到。但如果它在一个不常见的位置,你就需要把这个文件夹的路径加到一个环境变量里,比如 DYLD_LIBRARY_PATH
。
在Windows系统中,find_library
会在PATH
环境变量中查找目录,但这并不是Windows加载器实际使用的查找顺序。特别是,find_library
不会包括应用程序目录和当前目录。
如果调用Windows的SearchPath
,会更接近实际情况,但由于DLL的激活上下文和其他API,比如SetDllDirectory
、更新的SetDefaultDllDirectories
和AddDllDirectory
,这种接近程度也有限。
因为没有简单的方法来复制Windows加载器的查找方式,所以可以直接用名字加载DLL,使用CDLL
(cdecl)或WinDLL
(stdcall):
nidaq_cdecl = ctypes.CDLL('NIDAQmx')
nidaq_stdcall = ctypes.WinDLL('NIDAQmx')
你可以在运行时动态地将DLL目录添加到PATH
中(这和Linux加载器在启动时缓存LD_LIBRARY_PATH
不同)。比如说,你的DLL依赖项在包的“dlls”子目录中。你可以这样添加这个目录:
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']
另外,你可以使用一个上下文管理器,调用GetDllDirectory
和SetDllDirectory
,临时修改通常由当前工作目录占用的查找位置。要注意的是,像修改PATH
一样,这会修改全局进程数据,所以在使用多线程时要小心。这个方法的一个好处是,它不会修改CreateProcess
用来查找可执行文件的查找路径。
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)
例如:
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')
当然,如果你只想在启动时设置一次DLL目录,那问题就简单多了。只需直接调用SetDllDirectoryW
。
另一种方法是调用LoadLibraryEx
,并使用标志LOAD_WITH_ALTERED_SEARCH_PATH
,这会临时将加载的DLL目录添加到查找路径中。你需要使用绝对路径加载DLL,否则行为是未定义的。为了方便,我们可以对ctypes.CDLL
和ctypes.WinDLL
进行子类化,以调用LoadLibraryEx
而不是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)
以下是所有可用的LoadLibraryEx
标志:
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
例如:
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)