python - ctypes.cdll.LoadLibrary on a dll in a zipped directory
问题描述
I call upon the might of StackOverflow, that I might deploy this software!
This software uses ctypes.cdll.LoadLibrary to load a dll. When deployed (mangled by py2exe) the dll is tucked away in a zip that contains a directory structure. The dll is a couple of levels deep in this structure. (I'm not sure if that's a relevant detail.) LoadLibrary fails because it cannot find the dll because the path to it is ...\site.zip\app\dll32\lfx.dll
.
I can't find anything for this on SO or Google. I'm considering doing a try-except on the LoadLibrary call and, in the except block, checking whether the given path refers to a zipped location, unzipping, and trying again.
Is there a more graceful way to load a dll from a zip?
解决方案
清单[Python 3.Docs]:ctypes - Python 的外部函数库。
为了加载.dll,CTypes使用:
- 尼克斯:[man7]:DLOPEN(3)
- Win : [MS.Docs]: LoadLibraryW 函数
两者都需要FS上存在的有效文件名(或NULL,但这在这里无关紧要)。您提供的文件名不符合条件,因为FS在路径中存在时不会自动处理.zip (或其他)文件。
所以.dll需要在加载之前解包。有很多方法可以做到这一点,这里有一种使用上下文管理器([Python 3.Docs]: Data model - With Statement Context Managers)或CM的方法。
代码00.py:
#!/usr/bin/env python
import sys
import ctypes as ct
import zipfile as zf
import os
if sys.platform[:3].lower() == "win":
from ctypes import wintypes as wt
unload_lib = ct.WinDLL("kernel32.dll").FreeLibrary
unload_lib.argtypes = [wt.HMODULE]
unload_lib.restype = wt.BOOL
else:
unload_lib = ct.CDLL(None).dlclose
unload_lib.argtypes = [ct.c_void_p]
unload_lib.restype = ct.c_int
class ZippedDll():
def __init__(self, zip_file_name, member_file_name, loader=ct.CDLL, extract_path=None, suppress_exceptions=False):
self.zip_file_name = zip_file_name
self.member_file_name = member_file_name
self.loader = loader
self.extract_path = extract_path
self.suppress_exceptions = suppress_exceptions
self.dll_path = None
self.dll = None
def __enter__(self):
self.dll_path = os.path.join(self.extract_path, self.member_file_name) if self.extract_path else self.member_file_name
if os.path.exists(self.dll_path):
self.dll_path = None
raise OSError("Target file already exists")
with zf.ZipFile(self.zip_file_name) as zip_file:
zip_file.extract(self.member_file_name, path=self.extract_path if self.extract_path else None)
try:
self.dll = self.loader(self.dll_path)
except OSError:
if not self.__exit__(*sys.exc_info()):
raise
return self.dll
def __exit__(self, exc_type, exc_val, exc_tb):
if self.dll:
unload_lib(self.dll._handle)
self.dll = None
if self.dll_path and os.path.isfile(self.dll_path):
os.unlink(self.dll_path)
self.dll_path = None
return self.suppress_exceptions
def main(*argv):
with ZippedDll("arch.zip", "dir00/dll00.dll", loader=ct.WinDLL) as dll:
print(dll._name, dll.dll00Func00)
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main(*sys.argv[1:])
print("\nDone.")
备注:
- 对于此示例,我使用了具有以下结构
的存档arch.zip
- 目录00/
- dll00.dll(来自另一个SO答案)导出函数(dll00Func00)
- 目录00/
- CM解压.dll并加载它,最后(使用 退出时)清理内容(中间目录除外)
- 可以添加更多错误处理
- 我没有测试Nix部分
输出:
e:\Work\Dev\StackOverflow\q060348430>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32 dir00/dll00.dll <_FuncPtr object at 0x000002A045422E18> Done.
推荐阅读
- python - macOS、Tkinter、Python (Macports) 给出错误“没有 $DISPLAY 环境变量”
- java - 如何在 Java 上使用 TFLite 处理 base64 字符串
- php - 文本中金额的正则表达式
- javascript - 如何在 Shopify 中构建真正的 React SPA 主题
- javascript - 如何仅在视频结束后滑动页面?
- python - How do you replace data for an existing line chart using python-pptx?
- python-3.x - 提高代码的执行时间,使其不会超时
- android - 如何在包中将数据类作为 Parcelable 传递?
- here-api - 在嵌入式 Web 浏览器中使用 HERE
- java - 对列表中的所有项目执行一个任务,然后在 RxJava 中执行另一个任务