Ivo Silva
Ivo Silva

Reputation: 350

Expose a C++ global variable in Python

I'm trying to access to a C++ global variable in my Python code, using Cython.

Let's say I have the following array in my C++ code:

// Project.cpp
int myArr[2] = { 0, 1 };

So, in Cython to define a pointer to myArr:

cdef extern int * myArr_ptr

Does myArr_ptr really points to the C++ array? Or is just a random value?

Upvotes: 1

Views: 1632

Answers (1)

abarnert
abarnert

Reputation: 365895

OK, the problem is:

Project.cpp has a global int *myArr;, it's not listed in Project.h, and you want to access it from Cython without importing Project.cpp.

In the comments, you say:

I can't include Project.cpp because this file will try to include other files that were already included and will try to redefine many variables.

The way you described it, there might be an elementary error here. You probably know these basic things, so please don't be insulted that I bring them up, but I just want to be thorough:

First, if your header files don't have guards against multiple inclusion, fix that, and then you don't need to worry about "I can't include Project.cpp because this file will try to include other files that were already included and will try to redefine many variables." (And if you don't have header files, and you're just doing everything in .cpp files with explicit extern statements all over the place, don't do that—it's a bad idea in C and C++, well before you get to Cythonizing.)

Second, if your module is meant to interface to a .so/.dll/.dylib that Project.cpp is part of, you shouldn't be building against the source to that library, but to the installed interface. On the other hand, if your module is meant to directly include the C++ code, then you have to include Project.cpp. If you use extern declarations to reference things that you aren't going to link in, you're just going to get a linker error—or, if you're unlucky, everything will build but then fail at runtime.

Third, if it isn't actually global, you can't access from outside of Project.cpp—for reasons of scope, lifetime, or linkage, no other kind of variable is usable in a separate implementation file.

Again, I assume you know all those basic things, and I just misread your comment. There is a real problem case where you need to wrap something with a poorly-designed API that requires you to reach into the internals, and that can be tricky sometimes, and you probably did run into such a thing, and I just haven't figured out exactly how.

There are three basic solutions.

First, obviously, if you can build a proper native API, then wrapping that API in Cython is trivial. And this is useful for other reasons, at the same time. Sometimes, this would be too much time and effort—e.g., if the native library wasn't designed to be driven externally, and is a mass of 10 years of legacy maintenance, and the only reason you're ever going to have to wrap it is the current Cython project, you may not want to clean it up. Or, if it's a rapidly-changing library that you need to keep up to date with and don't have source control over, forking it and trying to keep in sync can be a nightmare. And so on. But if there's no such reason in your case, this is the right answer. If you can just cdef extern from "project.h", everything is easy.

You can do a simpler version of that by creating a "shim API" at the native level, creating separate .h files with the appropriate extern, function, and type declarations for the various, functions, and types you need to use from the internals. Then, you can just cdef extern from "project_extras.h".

Finally, you can always write explicit cdef extern statements for anything, without telling Cython where any of it comes from. Cython will turn this into the appropriate native externs in the generated code, and if you get everything right, it will work. There are some downsides here—the docs explain all the advantages of cdef extern from that you'll be giving up. The short version is, your Cython declarations have to exactly match the native declarations; otherwise, instead of getting automatically fixed up or raising a nice error at the Cython stage, you'll get an inscrutable error message from the C compiler referring to the unreadable Cython-generated C code instead of your actual code—or, worse, code that compiles but does the wrong thing.

For the case of a simple int * or int [] value, all of this scarcely matters, because that doesn't need any interpretation; a plain cdef extern should be just fine.

Upvotes: 4

Related Questions