Segfault when import_array not in same translation unit Segfault when import_array not in same translation unit python-3.x python-3.x

Segfault when import_array not in same translation unit


After digging through the NumPy headers, I think I've found a solution:

in numpy/__multiarray_api.h, there's a section dealing with where an internal API buffer should be. For conciseness, here's the relevant snippet:

#if defined(PY_ARRAY_UNIQUE_SYMBOL)#define PyArray_API PY_ARRAY_UNIQUE_SYMBOL#endif#if defined(NO_IMPORT) || defined(NO_IMPORT_ARRAY)extern void **PyArray_API;#else#if defined(PY_ARRAY_UNIQUE_SYMBOL)void **PyArray_API;#elsestatic void **PyArray_API=NULL;#endif#endif

It looks like this is intended to allow multiple modules define their own internal API buffer, in which each module must call their own import_array define.

A consistent way to get several translation units to use the same internal API buffer is in every module, define PY_ARRAY_UNIQUE_SYMBOL to some library unique name, then every translation unit other than the one where the import_array wrapper is defined defines NO_IMPORT or NO_IMPORT_ARRAY. Incidentally, there are similar macros for the ufunc features: PY_UFUNC_UNIQUE_SYMBOL, and NO_IMPORT/NO_IMPORT_UFUNC.

The modified working example:

header1.hpp

#ifndef HEADER1_HPP#define HEADER1_HPP#ifndef MYLIBRARY_USE_IMPORT#define NO_IMPORT#endif#define PY_ARRAY_UNIQUE_SYMBOL MYLIBRARY_ARRAY_API#define PY_UFUNC_UNIQUE_SYMBOL MYLIBRARY_UFUNC_API#include <Python.h>#include <numpy/npy_3kcompat.h>#include <numpy/arrayobject.h>void initialize();#endif

file1.cpp

#define MYLIBRARY_USE_IMPORT#include "header1.hpp"void* wrap_import_array(){  import_array();  return (void*) 1;}void initialize(){  wrap_import_array();}

file2.cpp

#include "header1.hpp"#include <iostream>int main(){  Py_Initialize();  initialize();  npy_intp dims[] = {5};  std::cout << "creating descr" << std::endl;  PyArray_Descr* dtype = PyArray_DescrFromType(NPY_FLOAT64);  std::cout << "zeros" << std::endl;  PyArray_Zeros(1, dims, dtype, 0);  std::cout << "cleanup" << std::endl;  return 0;}

I don't know what pitfalls there are with this hack or if there are any better alternatives, but this appears to at least compile and run without any segfaults.