是什么导致我的 Python 紧缩 C 扩展崩溃?

What causes the crash of my crunch C extension for Python?

本文关键字:扩展 崩溃 紧缩 Python 我的 是什么      更新时间:2023-10-16

我正在尝试编写一个关于 crunch 的包装器作为C++。

内部代码似乎工作正常, 但是回报不起作用 - 所有这些都会导致崩溃。 代码一直运行到返回,然后崩溃。

我添加了一个虚拟函数来测试问题是否出在返回本身, 但情况似乎并非如此(请参阅虚拟函数(。 我还尝试了不同的返回值, 包括空回报,但所有这些都会导致崩溃。

static PyObject * dummy(PyObject * self, PyObject * args) {
return Py_BuildValue("{s:I,s:I,s:I,s:I,s:I,s:I,s:I,s:I}",
"width", 0,
"height", 0,
"levels", 0,
"faces", 0,
"bytes_per_block", 0,
"userdata0", 0,
"userdata1", 0,
"format", 0
);
}
// texture info
static PyObject * get_texture_info(PyObject * self, PyObject * args) {
unsigned char * buf;
UINT32 buf_length;
if (!PyArg_ParseTuple(args, "y*I", & buf, & buf_length)) {
return NULL;
}
crnd::crn_texture_info texture_info;
if (crnd::crnd_get_texture_info(buf, buf_length, & texture_info) == 0) {
PyErr_Format(PyExc_ZeroDivisionError, "Dividing %d by zero!", buf_length);
return NULL;
}
fprintf(stderr, "width: %dn", texture_info.m_width);
fprintf(stderr, "height: %dn", texture_info.m_height);
fprintf(stderr, "levels: %dn", texture_info.m_levels);
fprintf(stderr, "faces: %dn", texture_info.m_faces);
fprintf(stderr, "bytes_per_block: %dn", texture_info.m_bytes_per_block);
fprintf(stderr, "userdata0: %dn", texture_info.m_userdata0);
fprintf(stderr, "userdata1: %dn", texture_info.m_userdata1);
fprintf(stderr, "format: %dn", texture_info.m_format);
return Py_BuildValue("{s:I,s:I,s:I,s:I,s:I,s:I,s:I,s:I}",
"width", texture_info.m_width,
"height", texture_info.m_height,
"levels", texture_info.m_levels,
"faces", texture_info.m_faces,
"bytes_per_block", texture_info.m_bytes_per_block,
"userdata0", texture_info.m_userdata0,
"userdata1", texture_info.m_userdata1,
"format", texture_info.m_format
);
}
D:Projectspython_cdecrunch>python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import crunch
>>> d = open("tests\res\dxt1.crn",'rb').read()
>>> crunch.dummy()
{'width': 0, 'height': 0, 'levels': 0, 'faces': 0, 'bytes_per_block': 0, 'userdata0': 0, 'userdata1': 0, 'format': 0}
>>> crunch.get_texture_info(d,len(d))
width: 128
height: 128
levels: 8
faces: 1
bytes_per_block: 8
userdata0: 0
userdata1: 0
format: 0
D:Projectspython_cdecrunch>python

我想知道如何解决这个问题。

完整代码:

#include <Python.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
/* For System that are not Windows, we'll need to define these. */
#if SIZEOF_SHORT == 2
#define	INT16 short
#elif SIZEOF_INT == 2
#define	INT16 int
#else
#define	INT16 short /* most things works just fine anyway... */
#endif
#if SIZEOF_SHORT == 4
#define	INT32 short
#elif SIZEOF_INT == 4
#define	INT32 int
#elif SIZEOF_LONG == 4
#define	INT32 long
#else
#error Cannot find required 32-bit integer type
#endif
#if SIZEOF_LONG == 8
#define	INT64 long
#elif SIZEOF_LONG_LONG == 8
#define	INT64 long
#endif
#define	INT8  signed char
#define	UINT8 unsigned char
#define	UINT16 unsigned INT16
#define	UINT32 unsigned INT32
#endif
/* assume IEEE; tweak if necessary (patches are welcome) */
#define	FLOAT16 UINT16
#define	FLOAT32 float
#define	FLOAT64 double
#ifdef _MSC_VER
typedef signed __int64       int64_t;
#endif
#ifdef __GNUC__
#define GCC_VERSION (__GNUC__ * 10000 
+ __GNUC_MINOR__ * 100 
+ __GNUC_PATCHLEVEL__)
#endif
#include <string.h>
#if !defined(__APPLE__)
#if defined(__FreeBSD__)
#include <stdlib.h>
#else
#include <malloc.h>
#endif
#ifdef _WIN32
#define malloc_usable_size _msize
#endif
#endif
//#define CRND_HEADER_FILE_ONLY
#include "crunch/crn_decomp.h"
static PyObject * dummy(PyObject * self, PyObject * args) {
return Py_BuildValue("{s:I,s:I,s:I,s:I,s:I,s:I,s:I,s:I}",
"width", 0,
"height", 0,
"levels", 0,
"faces", 0,
"bytes_per_block", 0,
"userdata0", 0,
"userdata1", 0,
"format", 0
);
}
// texture info
static PyObject * get_texture_info(PyObject * self, PyObject * args) {
unsigned char * buf;
UINT32 buf_length;
if (!PyArg_ParseTuple(args, "y*I", & buf, & buf_length)) {
return NULL;
}
crnd::crn_texture_info texture_info;
if (crnd::crnd_get_texture_info(buf, buf_length, & texture_info) == 0) {
PyErr_Format(PyExc_ZeroDivisionError, "Dividing %d by zero!", buf_length);
return NULL;
}
fprintf(stderr, "width: %dn", texture_info.m_width);
fprintf(stderr, "height: %dn", texture_info.m_height);
fprintf(stderr, "levels: %dn", texture_info.m_levels);
fprintf(stderr, "faces: %dn", texture_info.m_faces);
fprintf(stderr, "bytes_per_block: %dn", texture_info.m_bytes_per_block);
fprintf(stderr, "userdata0: %dn", texture_info.m_userdata0);
fprintf(stderr, "userdata1: %dn", texture_info.m_userdata1);
fprintf(stderr, "format: %dn", texture_info.m_format);
return Py_BuildValue("{s:I,s:I,s:I,s:I,s:I,s:I,s:I,s:I}",
"width", texture_info.m_width,
"height", texture_info.m_height,
"levels", texture_info.m_levels,
"faces", texture_info.m_faces,
"bytes_per_block", texture_info.m_bytes_per_block,
"userdata0", texture_info.m_userdata0,
"userdata1", texture_info.m_userdata1,
"format", texture_info.m_format
);
}
// level info
static PyObject * get_level_info(PyObject * self, PyObject * args) {
unsigned char * buf;
UINT32 buf_length;
UINT32 level_index;
if (!PyArg_ParseTuple(args, "y*II", & buf, & buf_length, & level_index)) {
return NULL;
}
crnd::crn_level_info level_info;
if (crnd::crnd_get_level_info(buf, buf_length, level_index, & level_info) == 0) {
PyErr_Format(PyExc_ZeroDivisionError, "Dividing %d by zero!", level_index);
return NULL;
}
return Py_BuildValue("{s:I,s:I,s:I,s:I,s:I,s:I,s:I}",
"width", level_info.m_width,
"height", level_info.m_height,
"faces", level_info.m_faces,
"blocks_x", level_info.m_blocks_x,
"blocks_y", level_info.m_blocks_y,
"bytes_per_block", level_info.m_bytes_per_block,
"format", level_info.m_format
);
}
// unpack level
static PyObject * unpack_level(PyObject * self, PyObject * args) {
unsigned char * buf;
UINT32 buf_length;
UINT32 level_index;
if (!PyArg_ParseTuple(args, "y*II", & buf, & buf_length, & level_index)) {
return NULL;
}
crnd::crn_level_info level_info;
if (crnd::crnd_get_level_info(buf, buf_length, level_index, & level_info) == 0) {
PyErr_Format(PyExc_ZeroDivisionError, "Dividing %d by zero!", level_index);
return NULL;
}
UINT32 bpb = level_info.m_bytes_per_block;
crnd::crnd_unpack_context ctx;
unsigned char * dst;
void * ppDst[6];
ppDst[0] = dst;
UINT32 dst_length = bpb * level_info.m_blocks_x * level_info.m_blocks_y;
ctx = crnd::crnd_unpack_begin(buf, buf_length);
if (crnd::crnd_unpack_level(ctx, ppDst, dst_length, bpb, level_index) == 0) {
fprintf(stderr, "unpack failed");
PyErr_Format(PyExc_ZeroDivisionError, "Dividing %d by zero!", dst_length);
return NULL;
}
fprintf(stderr, "pre ret");
return Py_BuildValue("y*", dst);
}
// Exported methods are collected in a table
static struct PyMethodDef method_table[] = {
{
"dummy",
(PyCFunction) dummy,
METH_VARARGS,
"Method docstring"
},
{
"get_texture_info",
(PyCFunction) get_texture_info,
METH_VARARGS,
"Method docstring"
},
{
"get_level_info",
(PyCFunction) get_level_info,
METH_VARARGS,
"Method docstring"
},
{
"unpack_level",
(PyCFunction) unpack_level,
METH_VARARGS,
"Method docstring"
},
{
NULL,
NULL,
0,
NULL
} // Sentinel value ending the table
};
// A struct contains the definition of a module
static PyModuleDef crunch_module = {
PyModuleDef_HEAD_INIT,
"crunch", // Module name
"This is the module docstring",
-1, // Optional size of the module state memory
method_table,
NULL, // Optional slot definitions
NULL, // Optional traversal function
NULL, // Optional clear function
NULL // Optional module deallocation function
};
// The module init function
PyMODINIT_FUNC PyInit_crunch(void) {
return PyModule_Create( & crunch_module);
}


正如 https://docs.python.org/3/c-api/arg.html 似乎表明的那样,y*应该将数据放入Py_buffer中。

但那是什么?https://docs.python.org/3/c-api/buffer.html 似乎解释了它:它是一个有一堆有用字段的结构。它仍然与原始对象绑定。使用后必须释放。

对您来说,这意味着您必须进行以下更改:

取代

unsigned char* buf;

Py_buffer buf;

及以后

crnd::crnd_get_texture_info(buf, buf_length, &texture_info)

crnd::crnd_get_texture_info(buf.buf, buf_length, &texture_info)

或者,为了消除通过len()的需要,甚至更好

crnd::crnd_get_texture_info(buf.buf, buf.len, &texture_info)

(但请参阅文档以了解是否可以连续使用此buf;似乎有限制(

使用后,必须释放此缓冲区 (PyBuffer_Release()(。

如果这看起来太复杂,您应该使用y#格式,它为您提供两个值:包含数据的const char *和长度的int

在这里,你会做

unsigned char* buf;
int buf_length; // or Py_ssize_t? See below.or not?
if (!PyArg_ParseTuple(args, "y#", &buf, &buf_length)) {
return NULL;
}

并像您一样继续。

两个版本都从传递的对象派生长度,所以不要用crunch.get_texture_info(d,len(d))来调用它,而只用crunch.get_texture_info(d)来调用它。

是否必须将buf_length定义为intPy_ssize_t取决于您是否在包含Python.h之前定义PY_SSIZE_T_CLEAN(如文档中某处的"注释"框中所示(。它还说:"最好始终定义PY_SSIZE_T_CLEAN

免责声明:这些只是您可以继续的提示。我只有一个模糊的概念,这实际上是如何工作的,但在我看来,这可能指向正确的方向。它绝不是直接可用的解决方案。

编辑后编辑:此解决方案应应用于当前使用y*解析参数的所有位置。