为啥我无法打开/读取从 Python 调用的 C 扩展名中的 txt 文件?

Posted

技术标签:

【中文标题】为啥我无法打开/读取从 Python 调用的 C 扩展名中的 txt 文件?【英文标题】:Why I'm unable to open/read txt file in C extension called from Python?为什么我无法打开/读取从 Python 调用的 C 扩展名中的 txt 文件? 【发布时间】:2020-06-18 11:38:57 【问题描述】:

我尝试将文件名从 python 传递给 C\C++ .dll 并让 C\C++ 代码打开/读取该文件。

这是我尝试使用 ctypes 实现的一个非常简单的示例。请原谅我的 C++ 代码的菜鸟。我在这里学习。

//C++ code in example.cpp compiled to example.dll
#include <stdio.h>


extern "C" 

char* _line;
char* test(char* txt)
FILE* f_name;
f_name = fopen(txt, "r");

//... read data from file and do something 

_line = txt;
return _line; // this is just my sanity check to see if "file.txt" got passed from python


我使用cygwin-64编译成.dll:

g++ -fPIC -shared example.cpp -o example.dll

Python 包装器:

import ctypes

print("works!")  #just sanity check...

lib = ctypes.CDLL("example.dll")
cpp_file_access = lib.test
cpp_file_access.argtypes = [ctypes.c_char_p]
cpp_file_access.restype = ctypes.c_char_p

print(cpp_file_access(b"file.txt")) #sanity check to see if file name got passed 

当从 python 调用 .dll 中的 test() 时,程序不会越过行:

f_name = fopen(txt, "r");

无论我使用哪种方法打开/访问所述 txt 文件。它要么“挂起”并且什么都不做,要么抛出 WindowsError 指示访问冲突 0x00000(...)。 当我在 C++ 代码中使用任何类型的“打印”函数时,也会发生同样的行为,例如:

printf("test print !");
std::cout << "another test print";

.dll 代码在其他情况下正常工作,除非我试图打开/读取 txt 文件或在从 python 调用的 C++ 代码中将某些内容“打印”到控制台。 实际上,当我将其编译为 .exe 文件时,它会打开/读取文件并将任何内容打印到控制台没有问题。

我找不到我的问题的任何解决方案/答案。 也许这里有人可以告诉我我做错了什么?

[编辑#1] 我注意到了一些东西。 当我尝试在 Python3.8 中运行此代码时,我收到错误消息文件:

cygwin1.dll
cygstdc++-6.dll
cyggcc_s-seh-1.dll

即使它们位于 cygwin 的 bin 文件夹中并且 env 路径设置正确,也无法定位。我必须将这些文件复制粘贴到 example.dll 和 python 包装器(wrapper.py)所在的文件夹中。 我不知道这是否与我的问题有关,但我认为值得一提。我开始对那个 cygwin 编译器非常怀疑。挖掘继续。

[编辑#2] 我通过添加 void test_2() 修改了 C++ 代码,因此在 python 和 .dll 之间没有传递参数。

__declspec(dllexport) void test_2()
FILE* f_name;
f_name = fopen("file.txt", "r");

并从 python 调用它:

void_acc = lib.test_2
void_acc.restype = None
void_acc()
print ("program ended, go away !") // .. sanity check

问题依旧。程序执行到 fopen() 行时停止。

【问题讨论】:

【参考方案1】:

两件事一目了然:

您必须确保在函数声明之前使用 __declspec(dllexport) 或通过将其包含在 def 文件中来导出函数。 (如果您到达 fopen 行,这不是问题 - 只是为了完整性而提及。) 您在 Python 中指定了宽 c 字符串指针 (c_wchar_t),但在 c 中声明了窄 c 字符串 (char*),这些需要匹配。

【讨论】:

是的。谢谢你的建议。至于 fopen() 行。如果我将其注释掉,程序将正确执行 - 在 python 控制台中打印“file.txt”。这就是为什么我询问打开/读取文件的原因,因为这显然是我的问题(或其他问题)。 我仍然认为字符串可能是问题所在,因为它将指针返回到同一个字符串,所以它看起来没有变化,因此返回不会受到影响。试试把std::out &lt;&lt; txt;;在 fopen 之前或代替 fopen 你得到“file.txt”还是“f”? 我添加了内联 std::cout&lt;&lt;txt; 它导致:WindowsError: exception: access violation reading 0xFFFFFFFFFFFFF 指向 wrapper.py 中的行:print(cpp_file_access(b"file.txt")) @Rad226 两个错误就是一个正确。将 wchar* 传递给 char* 并将 char* 作为 wchar* 传回而不使用它似乎可以工作,但 fopen 不会喜欢它。 @Mark Tolonen 好的。但是我在我的 C++ 代码(上面的编辑#2)中添加了 void,我所做的只是fopen("file.txt", "r');,但它仍然不起作用。 “file.txt”作为 fopen() 的参数,在这种情况下不是从 python 传递的,不正确吗? std::cout&lt;&lt;"some text"; in void 也会中断。【参考方案2】:

.argtypes.restype 不正确。 C代码使用char*所以使用c_char_p

import ctypes

lib = ctypes.CDLL("example.dll")
cpp_file_access = lib.test
cpp_file_access.argtypes = ctypes.c_char_p,
cpp_file_access.restype = ctypes.c_char_p

print(cpp_file_access(b'file.txt')) # pass byte string

请注意,您不需要将参数包装在 ctypes 类型中。 ctypes 已经知道来自 .argtypes 的类型,如果您没有传递与声明的类型兼容的 Python 类型,则会抱怨。

【讨论】:

对于 python2(不是 3),只要 fopen() 行被注释掉,它就可以正常工作而没有抱怨。仍然非常感谢您的建议。 @Rad226 创建一个minimal reproducible example。对于 Python 2 和 3,这是调用带有签名 char* test(char* txt) 的函数的正确代码,因此问题出在您未显示的代码中。你得到什么错误? 没有错误并且程序执行在控制台中“挂起”,就像它正在等待输入一样,或者当从 C++ 代码执行任何类型的“打印”时,如 sdt::cout&lt;&lt;"thing"; 我收到此错误:@ 987654332@。我没有显示的其他代码无关紧要。我将问题的根源确定为从 C++ 代码打开/读取文件或打印到控制台。没有这些一切都可以完美运行。【参考方案3】:

已解决。

毕竟是编译器问题。 我应该使用 mingw-64 位而不是使用 cygwin。

https://www.msys2.org/

我按照这个 yt 视频中的说明进行操作: https://www.youtube.com/watch?v=aXF4A5UeSeM

工作正常。

【讨论】:

【参考方案4】:

如果由于某种原因您的链接已断开或您不想观看视频,我会发布该视频中的步骤以妥善保管。

转到: https://www.msys2.org/ 并单击下载安装程序的按钮。就我而言,它是(未来可能会有所不同):

msys2-x86_64-20200602.exe

安装它。记住你安装它的地方。这很快就会派上用场。 安装后,启动它(或选择安装完成后自动启动)。将出现命令行窗口。在该命令行窗口中写入/粘贴:

pacman -Syu --disable-download-timeout

等待它完成。之后关闭窗口。 转到您选择的安装文件夹。启动msys2.exe。将出现命令行窗口。再写或粘贴:

pacman -Syu --disable-download-timeout

...并等待它完成。保持命令行窗口打开。之后我们将使用它。 完成后,转到初始网页:https://www.msys2.org/ 并向下滚动,然后转到“浏览:包列表”。 在左侧转到“搜索”。 将"Search in" 设置为"base packages" 并在搜索框中输入"gcc"。选择:

mingw-w64-gcc

然后,从"binary packages" 列表中选择:

mingw-w64-x86_64-gcc

寻找"Installation" 标签(它应该在页面的顶部)。那里有一个命令。复制/粘贴到终端:

pacman -S mingw-w64-x86_64-gcc --disable-download-timeout

(添加了"--disable-download-timeout") 等待它安装。保持命令行窗口打开。有 2 件事要做。 在命令行窗口中,重新粘贴上一个命令,但将最后一个字母从 "gcc" 更改为 "gdb" 或使用:

pacman -S mingw-w64-x86_64-gdb --disable-download-timeout

等待它安装。保持命令行窗口打开。 1件事去。 在命令行窗口中,重新粘贴上一个命令,但将最后一个字母从 "gdb" 更改为 "make" 或使用:

pacman -S mingw-w64-x86_64-make --disable-download-timeout

等待它安装。我们完成了安装工作。然而,还有一个步骤 - 系统路径变量。 转到您安装msys2-x86_64-20200602.exe 的文件夹,应该有一个"msys2.exe" 文件和一个"mingw64" 文件夹。转到那个文件夹。查找"bin" 文件夹,复制其路径。 转到系统变量。 右键单击您的计算机图标。转到高级系统设置。转到环境变量(在底部)。查找名为“path”的变量,对其进行编辑。放 ”;”最后,如果它还没有准备好。在“;”后面粘贴&lt;rest of the path&gt;/mingw64/bin文件夹路径。

现在您可以在终端中使用 g++ 命令,例如:

g++ -fPIC -shared example.cpp -o example.dll

(用于制作 .dll 文件)。你已经完成了。

【讨论】:

以上是关于为啥我无法打开/读取从 Python 调用的 C 扩展名中的 txt 文件?的主要内容,如果未能解决你的问题,请参考以下文章

为啥打开Mastercam9.0 显示 无法读取 c:\mcam9\temp\db.ovf

为啥 c++ ifstream 不能从设备读取?

Python打开了一个文件并正在读取它的数据,为啥即使我删除了这个文件,python仍然可以读取它的数据?

如下:为啥C语言读取文件中的数据并输出时有乱码出现?本来输入的是数字,然后从文件中读取后就变汉字

为啥我收到“无法从传输连接读取数据”

为啥在 putw 在 C 中扩展文件后使用 fread?