在 Windows 上编译用 Zig 编写的 CPython 扩展

compile CPython extension written in Zig on Windows

提问人:geoff22873 提问时间:11/12/2023 最后编辑:geoff22873 更新时间:11/22/2023 访问量:102

问:

Zig 能够导入 C 库,因此可用于编写 CPython 扩展并对其进行编译。这对我来说可能真的很有用。

这是我的 simple.zig Python 扩展

const py = @cImport({
    @cDefine("PY_SSIZE_T_CLEAN", {});
    @cInclude("Python.h");
});
const std = @import("std");
const print = std.debug.print;

const PyObject = py.PyObject;
const PyMethodDef = py.PyMethodDef;
const PyModuleDef = py.PyModuleDef;
const PyModuleDef_Base = py.PyModuleDef_Base;
const Py_BuildValue = py.Py_BuildValue;
const PyModule_Create = py.PyModule_Create;
const METH_NOARGS = py.METH_NOARGS;

fn hello(self: [*c]PyObject, args: [*c]PyObject) callconv(.C) [*]PyObject {
    _ = self;
    _ = args;
    print("welcom to ziglang\n", .{});
    return Py_BuildValue("");
}

var Methods = [_]PyMethodDef{
    PyMethodDef{
        .ml_name = "hello",
        .ml_meth = hello,
        .ml_flags = METH_NOARGS,
        .ml_doc = null,
    },
    PyMethodDef{
        .ml_name = null,
        .ml_meth = null,
        .ml_flags = 0,
        .ml_doc = null,
    },
};

var module = PyModuleDef{
    .m_base = PyModuleDef_Base{
        .ob_base = PyObject{
            .ob_refcnt = 1,
            .ob_type = null,
        },
        .m_init = null,
        .m_index = 0,
        .m_copy = null,
    },
    .m_name = "simple",
    .m_doc = null,
    .m_size = -1,
    .m_methods = &Methods,
    .m_slots = null,
    .m_traverse = null,
    .m_clear = null,
    .m_free = null,
};

pub export fn PyInit_simple() [*]PyObject {
    return PyModule_Create(&module);
}

当我使用 Zig 编译器编译用 Zig 编写的 CPython 扩展时,Python 无法导入 DLL。我使用的是 Windows 10。

zig build-lib -lc -dynamic -target x86_64-windows-msvc -I"C:\Users\me\Anaconda3\include" -L"C:\Users\me\Anaconda3\libs" -l"python39" simple.zig

当我尝试导入simple.dll时,Python说没有找到模块。我正在使用 os 模块将包含我的扩展 DLL 的目录添加到扩展搜索位置,因为我相信自 Python 3.8 以来在 Windows 上是必需的。

import os
os.add_dll_directory(r"C:\Users\me\my_zig_project")
import simple
ModuleNotFoundError: No module named 'simple'

我怀疑问题是 Windows 上的 CPython 是使用 MSVC 编译器编译的(MSVC 的版本取决于 Python 版本),并且即使我将目标环境指定为 Windows MSVC,Zig 编译器也不知何故不兼容。

有没有办法编译用 Zig 编写的 CPython 扩展以在 Windows 上运行?

如果可能的话,一个最小的例子将不胜感激。

Windows Visual-C++ cpython zig

评论

1赞 CristiFati 11/13/2023
[SO]:如何创建最小、可重现的示例 (reprex (mcve))。添加一些最少的代码、生成和运行命令以及输出。
2赞 Cubic 11/14/2023
“如果可能的话,一个最小的例子将不胜感激”,回到你身边:请向我们展示你正在尝试做什么——一个用 zig 编写的最小模块,你是如何编译它的,以及你是如何尝试从 python 使用它的。
0赞 geoff22873 11/15/2023
@CristiFati感谢您的指导。
0赞 geoff22873 11/15/2023
@Cubic希望我现在已经解决了你的观点。

答:

1赞 ad absurdum 11/19/2023 #1

我无法使用典型的方法使用 Zig 创建 Python 模块,但我已经能够使用 wheel 模块创建模块,这些模块链接使用 Zig 编译的 DLL 文件,然后可以使用 Pip 安装这些文件。这是一个最小的例子。setuptools

如果您的系统上尚未安装 setuptoolswheel 模块,则需要先安装它们。我从这个内容丰富的 SO 答案中获得了灵感。

创建如下所示的目录结构:

hello_zig_mod/
|
|-- setup.py
|
|-- hello/
    |
    |-- __init__.py
    |
    |-- hello.zig

该文件是将编译为 DLL 的 Zig 代码:hello.zig

const std = @import("std");

pub export fn message(msg: [*c]const u8) void {
    std.debug.print("Hello, {s}!\n", .{std.mem.span(msg)});
}

该文件是用于初始化模块的 Python 文件。它链接到 DLL 并将其中的函数包装在 Python 函数中:__init__.pymessage

from ctypes import *
import os
lib_path = os.path.join(os.path.dirname(__file__), 'hello.dll')
lib = CDLL(lib_path)

def message(msg):
    lib.message(bytes(msg, 'utf-8'))

该文件用于构建可由 Pip 安装的轮子。您可以在我上面链接的答案中阅读有关此文件详细信息的更多信息。setup.py

from setuptools import setup, Distribution

class BinaryDistribution(Distribution):
    def has_ext_modules(foo):
        return True

setup(
    name="hello",
    packages=['hello'],
    package_data={
        'hello':['hello.dll'],
    },
    distclass=BinaryDistribution
)

进入目录并使用 Zig 编译器构建一个动态库。hello/

> zig build-lib .\hello.zig -dynamic

这将生成 DLL 以及其他一些项目。现在回到目录并构建轮子:hello_zig_mod

> python setup.py bdist_wheel

这会将轮子与其他工件一起构建为文件。您现在终于可以使用 Pip 安装轮子了:dist\.whl

> pip install .\dist\hello-0.0.0-cp312-cp312-win_amd64.whl

签入 Python REPL:

>>> import hello
>>> hello.message("Zig Module")
Hello, Zig Module!

评论

0赞 geoff22873 11/22/2023
谢谢。我希望在不使用任何其他 Python 工具(如 setuptools)的情况下构建扩展,但感谢您的示例 - 这是一种有效的方法,并指导我找到拼图中缺失的部分,我需要将 simple.dll 重命名为 simple.pyd。我需要在zig build-lib命令中包含-lc标志,以便它包含libc,否则我找不到io.h。
1赞 geoff22873 11/22/2023 #2

看过这个视频 https://www.youtube.com/watch?v=9q-LHP7cMfg 我意识到我所需要的只是将我的 simple.dll重命名为 simple.pyd

这是一个最小的工作示例。创建 simple.zig,如下所示

const py = @cImport({
    @cDefine("PY_SSIZE_T_CLEAN", {});
    @cInclude("Python.h");
});
const std = @import("std");
const print = std.debug.print;

const PyObject = py.PyObject;
const PyMethodDef = py.PyMethodDef;
const PyModuleDef = py.PyModuleDef;
const PyModuleDef_Base = py.PyModuleDef_Base;
const Py_BuildValue = py.Py_BuildValue;
const PyModule_Create = py.PyModule_Create;
const METH_NOARGS = py.METH_NOARGS;

fn hello(self: [*c]PyObject, args: [*c]PyObject) callconv(.C) [*]PyObject {
    _ = self;
    _ = args;
    print("welcome to ziglang\n", .{});
    return Py_BuildValue("");
}

var Methods = [_]PyMethodDef{
    PyMethodDef{
        .ml_name = "hello",
        .ml_meth = hello,
        .ml_flags = METH_NOARGS,
        .ml_doc = null,
    },
    PyMethodDef{
        .ml_name = null,
        .ml_meth = null,
        .ml_flags = 0,
        .ml_doc = null,
    },
};

var module = PyModuleDef{
    .m_base = PyModuleDef_Base{
        .ob_base = PyObject{
            .ob_refcnt = 1,
            .ob_type = null,
        },
        .m_init = null,
        .m_index = 0,
        .m_copy = null,
    },
    .m_name = "simple",
    .m_doc = null,
    .m_size = -1,
    .m_methods = &Methods,
    .m_slots = null,
    .m_traverse = null,
    .m_clear = null,
    .m_free = null,
};

pub export fn PyInit_simple() [*]PyObject {
    return PyModule_Create(&module);
}

构建 simple.zig 以获取 simple.dll

zig build-lib -lc -dynamic -I"<directory containing Python.h>" -L"<directory containing python3.lib>" -l"python3" simple.zig

将 simple.dll 重命名为 simple.pyd

在包含 simple.pyd 的目录中运行 python.exe(python.exe 的 3.X 版本必须与您链接的 python3.lib 匹配)

>>> import simple
>>> simple.hello()
welcome to ziglang