Skip to content

Commit 83867fb

Browse files
committed
API: Default to hidden visibility for API tables
This adds a new ``NPY_API_SYMBOL_ATTRIBUTE`` but defaults to using hidden visibility (always the case on windows!). That actually makes the situation "worse" for ``eigenpy`` in some degree, since it forces them to adapt, but it also allows them to decide to just roll with it (by actually also exporting it on windows which). Since it aligns windows and linux it seems like a good idea? OTOH, who knows how many projects get away with just not caring about windows... I tried to reorganize the docs a bit on how to import the array API...
1 parent 6bac54e commit 83867fb

File tree

5 files changed

+181
-56
lines changed

5 files changed

+181
-56
lines changed

doc/source/numpy_2_0_migration_guide.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,21 @@ using the NumPy types. You can still write cython code using the ``c.real`` and
221221
``c.imag`` attributes (using the native typedefs), but you can no longer use
222222
in-place operators ``c.imag += 1`` in Cython's c++ mode.
223223

224+
.. _api-table-visibility-change:
225+
226+
Changes in table visibility (linking error)
227+
-------------------------------------------
228+
If you are experiencing linking errors related to ``PyArray_API`` or
229+
``PyArray_RUNTIME_VERSION``, NumPy changed the default visibility to hidden
230+
(which was always the case on windows).
231+
You can use the :c:macro:`NPY_API_SYMBOL_ATTRIBUTE` to opt-out of this change.
232+
However, we generally discourage linking across project boundaries because
233+
it breaks NumPy compatibility checks.
234+
235+
If you are experiencing problems due to an upstream header including NumPy,
236+
the solution is to make sure you ``#include "numpy/ndarrayobject.h"`` before
237+
their header and import NumPy yourself based on :ref:`_including-the-c-api`.
238+
224239

225240
Changes to namespaces
226241
=====================

doc/source/reference/c-api/array.rst

Lines changed: 139 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3814,13 +3814,118 @@ Other conversions
38143814
in the *vals* array. The sequence can be smaller then *maxvals* as
38153815
the number of converted objects is returned.
38163816
3817+
.. _including-the-c-api:
38173818
3818-
Miscellaneous
3819-
-------------
3819+
Including and importing the C API
3820+
---------------------------------
38203821
3822+
To use the NumPy C-API you typically need to include the
3823+
``numpy/ndarrayobject.h`` header and ``numpy/ufuncobject.h`` for some ufunc
3824+
related functionality (``arrayobject.h`` is an alias for ``ndarrayobject.h``).
38213825
3822-
Importing the API
3823-
~~~~~~~~~~~~~~~~~
3826+
These two headers export most relevant functionality. In general any project
3827+
which uses the NumPy API must import NumPy using one of the functions
3828+
``PyArray_ImportNumPyAPI()`` or ``import_array()``.
3829+
In some places, functionality which requires ``import_array()`` is not
3830+
needed, because you only need type definitions. In this case, it is
3831+
sufficient to include ``numpy/ndarratypes.h``.
3832+
3833+
For the typical Python project, multiple C or C++ files will be compiled into
3834+
a single shared object (the Python C-module) and ``PyArray_ImportNumPyAPI()``
3835+
should be called inside it's module initialization.
3836+
3837+
When you have a single C-file, this will consist of::
3838+
3839+
.. code-block:: c
3840+
3841+
#include "numpy/ndarrayobject.h"
3842+
3843+
PyMODINIT_FUNC PyInit_my_module(void)
3844+
{
3845+
if (PyArray_ImportNumPyAPI() < 0) {
3846+
return NULL;
3847+
}
3848+
/* Other initialization code. */
3849+
}
3850+
3851+
However, most projects will have additional C files which are all
3852+
linked together into a single Python module.
3853+
In this case, the helper C files typically do not have a canonical place
3854+
where ``PyArray_ImportNumPyAPI`` should be called (although it is OK and
3855+
fast to call it often).
3856+
3857+
To solve this, NumPy provides the following pattern that the the main
3858+
file is modified to define ``PY_ARRAY_UNIQUE_SYMBOL`` before the include:
3859+
3860+
.. code-block:: c
3861+
/* Main module file */
3862+
#define PY_ARRAY_UNIQUE_SYMBOL MyModule
3863+
#include "numpy/ndarrayobject.h"
3864+
3865+
PyMODINIT_FUNC PyInit_my_module(void)
3866+
{
3867+
if (PyArray_ImportNumPyAPI() < 0) {
3868+
return NULL;
3869+
}
3870+
/* Other initialization code. */
3871+
}
3872+
3873+
while the other files use:
3874+
3875+
.. code-block:: C
3876+
3877+
/* Second file without any import */
3878+
#define NO_IMPORT_ARRAY
3879+
#define PY_ARRAY_UNIQUE_SYMBOL MyModule
3880+
#include "numpy/ndarrayobject.h"
3881+
3882+
You can of course add the defines to a local header used throughout.
3883+
You just have to make sure that the main file does _not_ define
3884+
``NO_IMPORT_ARRAY``.
3885+
3886+
For ``numpy/ufuncobject.h`` the same logic applies, but the unique symbol
3887+
mechanism is ``#define PY_UFUNC_UNIQUE_SYMBOL`` (both can match).
3888+
3889+
Additionally, you will probably wish to add a
3890+
``#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION``
3891+
to avoid warnings about possible use of old API.
3892+
3893+
.. note::
3894+
If you are experiencing access violations make sure that the NumPy API
3895+
was properly imported and the symbol ``PyArray_API`` is not ``NULL``.
3896+
When in a debugger, this symbols actual name will be
3897+
``PY_ARRAY_UNIQUE_SYMBOL``+``PyArray_API``, so for example
3898+
``MyModulePyArray_API`` in the above.
3899+
(E.g. even a ``printf("%p\n", PyArray_API);`` just before the crash.)
3900+
3901+
3902+
Mechanism details and dynamic linking
3903+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3904+
3905+
The main part of the mechanism is that without NumPy needs to define
3906+
a ``void **PyArray_API`` table for you to look up all functions.
3907+
Depending on your macro setup, this takes different routes depending on
3908+
whether :c:macro:`NO_IMPORT_ARRAY` and :c:macro:`PY_ARRAY_UNIQUE_SYMBOL`
3909+
are defined:
3910+
3911+
* If neither is defined, the C-API is declared to
3912+
``static void **PyArray_API``, so it is only visible within the
3913+
compilation unit/file using ``#includes numpy/arrayobject.h``.
3914+
* If only ``PY_ARRAY_UNIQUE_SYMBOL`` is defined (it could be empty) then
3915+
the it is declared to a non-static ``void **`` allowing it to be used
3916+
by other files which are linked.
3917+
* If ``NO_IMPORT_ARRAY`` is defined, the table is declared as
3918+
``extern void **``, meaning that it must be linked to a file which does not
3919+
use ``NO_IMPORT_ARRAY`.
3920+
3921+
The ``PY_ARRAY_UNIQUE_SYMBOL`` mechanism additionally mangles the names to
3922+
avoid conflicts.
3923+
3924+
3925+
.. versionchanged::
3926+
NumPy 2.0 exports the headers to avoid sharing the table outside of a
3927+
single shared object/dll (this was always the case on Windows).
3928+
Please see :c:macro:`NPY_API_SYMBOL_ATTRIBUTE` for details.
38243929
38253930
In order to make use of the C-API from another extension module, the
38263931
:c:func:`import_array` function must be called. If the extension module is
@@ -3844,61 +3949,45 @@ the C-API is needed then some additional steps must be taken.
38443949
module that will make use of the C-API. It imports the module
38453950
where the function-pointer table is stored and points the correct
38463951
variable to it.
3952+
This macro includes a ``return NULL;`` on error, so that
3953+
``PyArray_ImportNumPyAPI()`` is preferable for custom error checking.
3954+
You may also see use of ``_import_array()`` (a function, not
3955+
a macro, but you may want to raise a better error if it fails) and
3956+
the variations ``import_array1(ret)`` which customizes the return value.
38473957
38483958
.. c:macro:: PY_ARRAY_UNIQUE_SYMBOL
38493959
3850-
.. c:macro:: NO_IMPORT_ARRAY
3960+
.. c:macro:: NPY_API_SYMBOL_ATTRIBUTE
38513961
3852-
Using these #defines you can use the C-API in multiple files for a
3853-
single extension module. In each file you must define
3854-
:c:macro:`PY_ARRAY_UNIQUE_SYMBOL` to some name that will hold the
3855-
C-API (*e.g.* myextension_ARRAY_API). This must be done **before**
3856-
including the numpy/arrayobject.h file. In the module
3857-
initialization routine you call :c:func:`import_array`. In addition,
3858-
in the files that do not have the module initialization
3859-
sub_routine define :c:macro:`NO_IMPORT_ARRAY` prior to including
3860-
numpy/arrayobject.h.
3861-
3862-
Suppose I have two files coolmodule.c and coolhelper.c which need
3863-
to be compiled and linked into a single extension module. Suppose
3864-
coolmodule.c contains the required initcool module initialization
3865-
function (with the import_array() function called). Then,
3866-
coolmodule.c would have at the top:
3962+
.. versionadded:: 2.0
38673963
3868-
.. code-block:: c
3964+
An additional symbol which can be used to share e.g. visibility beyond
3965+
shared object boundaries.
3966+
By default, NumPy adds the C visibility hidden attribute (if available):
3967+
``void __attribute__((visibility("hidden"))) **PyArray_API;``.
3968+
You can change this by defining ``NPY_API_SYMBOL_ATTRIBUTE``, which will
3969+
make this:
3970+
``void NPY_API_SYMBOL_ATTRIBUTE **PyArray_API;`` (with additional
3971+
name mangling via the unique symbol).
38693972
3870-
#define PY_ARRAY_UNIQUE_SYMBOL cool_ARRAY_API
3871-
#include numpy/arrayobject.h
3973+
Adding an empty ``#define NPY_API_SYMBOL_ATTRIBUTE`` will have the same
3974+
behavior as NumPy 1.x.
38723975
3873-
On the other hand, coolhelper.c would contain at the top:
3976+
.. note::
3977+
Windows never had shared visbility although you can use this macro
3978+
to achieve it. We generally discourage sharing beyond shared boundary
3979+
lines since importing the array API includes NumPy version checks.
3980+
3981+
.. c:macro:: NO_IMPORT_ARRAY
3982+
3983+
Defining ``NO_IMPORT_ARRAY`` before the ``ndarrayobject.h`` include
3984+
indicates that the NumPy C API import is handled in a different file
3985+
and the include mechanism will not be added here.
3986+
You must have one file without ``NO_IMPORT_ARRAY`` defined.
38743987
3875-
.. code-block:: c
38763988
3877-
#define NO_IMPORT_ARRAY
3878-
#define PY_ARRAY_UNIQUE_SYMBOL cool_ARRAY_API
3879-
#include numpy/arrayobject.h
3880-
3881-
You can also put the common two last lines into an extension-local
3882-
header file as long as you make sure that NO_IMPORT_ARRAY is
3883-
#defined before #including that file.
3884-
3885-
Internally, these #defines work as follows:
3886-
3887-
* If neither is defined, the C-API is declared to be
3888-
``static void**``, so it is only visible within the
3889-
compilation unit that #includes numpy/arrayobject.h.
3890-
* If :c:macro:`PY_ARRAY_UNIQUE_SYMBOL` is #defined, but
3891-
:c:macro:`NO_IMPORT_ARRAY` is not, the C-API is declared to
3892-
be ``void**``, so that it will also be visible to other
3893-
compilation units.
3894-
* If :c:macro:`NO_IMPORT_ARRAY` is #defined, regardless of
3895-
whether :c:macro:`PY_ARRAY_UNIQUE_SYMBOL` is, the C-API is
3896-
declared to be ``extern void**``, so it is expected to
3897-
be defined in another compilation unit.
3898-
* Whenever :c:macro:`PY_ARRAY_UNIQUE_SYMBOL` is #defined, it
3899-
also changes the name of the variable holding the C-API, which
3900-
defaults to ``PyArray_API``, to whatever the macro is
3901-
#defined to.
3989+
Miscellaneous
3990+
-------------
39023991
39033992
Checking the API Version
39043993
~~~~~~~~~~~~~~~~~~~~~~~~

doc/source/release/2.0.0-notes.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,17 @@ Please see :ref:`migration_c_descr` for more information.
655655

656656
(`gh-25943 <https://github.com/numpy/numpy/pull/25943>`__)
657657

658+
API symbols now hidden but customizable
659+
---------------------------------------
660+
NumPy now defaults to hide the API symbols it adds to allow all NumPy API
661+
usage.
662+
This means that by default you cannot dynamically fetch the NumPy API from
663+
another library (this was never possible on windows).
664+
Please see :ref:`api-table-visibility-change` and the new
665+
:c:macro:`NPY_API_SYMBOL_ATTRIBUTE` which allows to customize/revert this.
666+
667+
(`gh-26103 <https://github.com/numpy/numpy/pull/26103>`__)
668+
658669

659670
NumPy 2.0 C API removals
660671
========================

numpy/_core/code_generators/generate_numpy_api.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,18 @@
3333
_NPY_VERSION_CONCAT_HELPER(PY_ARRAY_UNIQUE_SYMBOL)
3434
#endif
3535
36+
/* By default do not export API in an .so (was never the case on windows) */
37+
#ifndef NPY_API_SYMBOL_ATTRIBUTE
38+
#define NPY_API_SYMBOL_ATTRIBUTE NPY_VISIBILITY_HIDDEN
39+
#endif
40+
3641
#if defined(NO_IMPORT) || defined(NO_IMPORT_ARRAY)
37-
extern void **PyArray_API;
38-
extern int PyArray_RUNTIME_VERSION;
42+
extern NPY_API_SYMBOL_ATTRIBUTE void **PyArray_API;
43+
extern NPY_API_SYMBOL_ATTRIBUTE int PyArray_RUNTIME_VERSION;
3944
#else
4045
#if defined(PY_ARRAY_UNIQUE_SYMBOL)
41-
void **PyArray_API;
42-
int PyArray_RUNTIME_VERSION;
46+
NPY_API_SYMBOL_ATTRIBUTE void **PyArray_API;
47+
NPY_API_SYMBOL_ATTRIBUTE int PyArray_RUNTIME_VERSION;
4348
#else
4449
static void **PyArray_API = NULL;
4550
static int PyArray_RUNTIME_VERSION = 0;

numpy/_core/code_generators/generate_ufunc_api.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@
1818
#define PyUFunc_API PY_UFUNC_UNIQUE_SYMBOL
1919
#endif
2020
21+
/* By default do not export API in an .so (was never the case on windows) */
22+
#ifndef NPY_API_SYMBOL_ATTRIBUTE
23+
#define NPY_API_SYMBOL_ATTRIBUTE NPY_VISIBILITY_HIDDEN
24+
#endif
25+
2126
#if defined(NO_IMPORT) || defined(NO_IMPORT_UFUNC)
22-
extern void **PyUFunc_API;
27+
extern NPY_API_SYMBOL_ATTRIBUTE void **PyUFunc_API;
2328
#else
2429
#if defined(PY_UFUNC_UNIQUE_SYMBOL)
25-
void **PyUFunc_API;
30+
NPY_API_SYMBOL_ATTRIBUTE void **PyUFunc_API;
2631
#else
2732
static void **PyUFunc_API=NULL;
2833
#endif

0 commit comments

Comments
 (0)