Is it possible to build a cython module with some cdef functions and link the resulting shared library into a C++ program?
I tried a proof of concept:
cymod.pyx:
# distutils: language=c++
from libcpp.string cimport string
cdef public string simple_echo(string test_string):
return test_string
cpp_test.cpp:
#define PyMODINIT_FUNC void
#include <iostream>
#include "cymod.h"
int main(int argc, char const *argv[])
{
std::cout << simple_echo("test") << std::endl;
return 0;
}
setup.py:
from setuptools import setup, Extension
from Cython.Build import cythonize
setup(
name='cymod',
ext_modules=cythonize(
Extension(
"cymod", ["cymod.pyx"],
),
)
)
The cython module builds fine, but when I try to build the c++ code that will use the cython function I get:
$ g++ -L. -l:cymod.so cpp_test.cpp -o cpp_test
/tmp/cc48Vc2z.o: In function `main':
cpp_test.cpp:(.text+0x51): undefined reference to `simple_echo'
collect2: error: ld returned 1 exit status
Which is odd. The generated header file has it:
cymod.h:
/* Generated by Cython 0.29.1 */
#ifndef __PYX_HAVE__cymod
#define __PYX_HAVE__cymod
#ifndef __PYX_HAVE_API__cymod
#ifndef __PYX_EXTERN_C
#ifdef __cplusplus
#define __PYX_EXTERN_C extern "C"
#else
#define __PYX_EXTERN_C extern
#endif
#endif
#ifndef DL_IMPORT
#define DL_IMPORT(_T) _T
#endif
__PYX_EXTERN_C std::string simple_echo(std::string);
#endif /* !__PYX_HAVE_API__cymod */
/* WARNING: the interface of the module init function changed in CPython 3.5. */
/* It now returns a PyModuleDef instance instead of a PyModule instance. */
#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC initcymod(void);
#else
PyMODINIT_FUNC PyInit_cymod(void);
#endif
#endif /* !__PYX_HAVE__cymod */
and I see my function in cymod.so:
nm cymod.so| grep simple_echo
0000000000001e50 T simple_echo
NOTE: I realize that to actually get this working I'll need to link against the python libraries and initialize the interpreter etc. I left that out to make this a tad shorter and I get the same error either way.
The short answer is that I was putting the -l argument too early in the compilation command. It is also important to handle the library lookup path. The simplest way is to use rpath. I set the rpath to the directory that the executable is in, i.e., .
Additionally, it is necessary to link against the python libraries and set the include and library paths. These can be determined at compile time by using the output of the python-config utility. Here is the compilation command that ultimately did the trick:
g++ cpp_test.cpp -o cpp_test -L. -l:cymod.so $(python-config --libs) $(python-config --includes) $(python-config --cflags) -Wl,-rpath,"\$ORIGIN"
I also updated the c++ file to #include "Python.h" and added calls to Py_Initialize(), Py_Finalize(), and initcymod():
#include <iostream>
#include "Python.h"
#include "cymod.h"
int main(int argc, char *argv[])
{
Py_Initialize();
initcymod();
std::cout << simple_echo("test") << std::endl;
Py_Finalize();
return 0;
}
NOTE: the call to initcymod() is necessary, but python2 specific. On python3 you should call PyImport_AppendInittab("cymod", PyInit_cymod); prior to Py_Initialize(). The cymod part is the module name, substitute your module name.
Thanks to #ead for the informative link to the docs on this topic https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#using-cython-declarations-from-c and his answer to a related question https://stackoverflow.com/a/45424720/2069572
While reading the linked docs, I came across this:
Note On some operating systems like Linux, it is also possible to first build the Cython extension in the usual way and then link against the resulting .so file like a dynamic library. Beware that this is not portable, so it should be avoided.
So it turns out that you should not do what I was trying to do.
Instead, what I should have done was run:
cython --cplus cymod.pyx
And then compiled cpp_test.cpp with the generated cymod.cpp file.
No need to link the cython shared library, and it turns out that it is not a good idea to do so.
Related
I need to use an existing 3rd party API that comes with a *.h and a *.dll file to load data into R. The functions provided by the dll are not callable directly, so I need to wrap them to call them from R. In order to familiarize myself with this, I made little example dll (based on the HOWTO from the MINGW page here, I have put the source code of the files at the end of the post). There is just one function in it that doubles an integer input. I can compile the dll just fine and also use it in a exe file, so it is functional. This is on Windows 10.
I am not sure how to correctly use this in R. I have created a package (named testwithdll2 ), placed the header file and the dll in "src", together with the wrapper function. When I try to compile the package, I get the follwing error messages with the undefined reference:
C:/Rtools/mingw_64/bin/gcc -I"C:/PROGRA~1/R/R-35~1.1/include" -DNDEBUG
-O2 -Wall -std=gnu99 -mtune=generic -c mydouble_c.c -o mydouble_c.o
C:/Rtools/mingw_64/bin/gcc -shared -s -static-libgcc -o testwithdll2.dll
tmp.def mydouble_c.o -LC:/PROGRA~1/R/R-35~1.1/bin/x64 -lR
mydouble_c.o:mydouble_c.c:(.text+0xc): undefined reference to `__imp_timestwo'
collect2.exe: error: ld returned 1 exit status
Any pointers on what might have gone wrong are greatly appreciated.
example_dll.h:
#ifndef EXAMPLE_DLL_H
#define EXAMPLE_DLL_H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef BUILDING_EXAMPLE_DLL
#define EXAMPLE_DLL __declspec(dllexport)
#else
#define EXAMPLE_DLL __declspec(dllimport)
#endif
int EXAMPLE_DLL timestwo(int x);
#ifdef __cplusplus
}
#endif
#endif // EXAMPLE_DLL_H
example_dll.cpp:
#include <stdio.h>
#include "example_dll.h"
int timestwo(int x)
{
return 2 * x;
}
mydouble.c (in the src folder of the r package):
#include "example_dll.h"
void mydouble(int* a){
*a = timestwo(*a);
}
timestwo.R (wrapper function, in the R folder):
#' #useDynLib testwithdll2 mydouble
#' #export
timestwo <- function(n){
.C("mydouble",n )
n
}
I figured out what to do.
It was necessary to use a makevars file with the following lines:
MAKEVARS:
PKG_CPPFLAGS= -I.
PKG_LIBS= -L. -lexample_dll
It was also necessary to add the useDynlib call to the example_dll.dll in the namespace before the call to the testwithdll2.dll. This also meant that the .C call needed the PACKAGE parameter to be specified, so I had to change the r wrapper to:
timestwo.R
#' #useDynLib example_dll
#' #useDynLib testwithdll2
#' #export
timestwo <- function(n){
.C("mydouble",n, PACKAGE = "testwithdll2")[[1]]
}
Now everything works as expected.
I'm trying to get a simple c++ program to use a method in a dll. I've been receiving a variety of errors as I've adjusted the code and have been stuck mostly, as in the code posted below, with "undefined reference to" the method. The code below is being compiled as follows.
g++ -c testdll.cpp
g++ -shared -o testdll.dll testdll.o
g++ -o test test.cpp -L./ -ltestdll
error
g++ -o test test.cpp -L./ -ltestdll
C:\Users\ROGERF~1\AppData\Local\Temp\cca9YhFn.o:test.cpp:(.text+0x53): undefined
reference to `__imp__ZN7TestDLL9writeDataESs'
collect2.exe: error: ld returned 1 exit status
I have no idea why directory C:\Users\ROGERF~1\AppData\Local\Temp\ is involved in the process. That showed up after I started using code from the Microsoft website in the header file. Previously, I was just getting undefined reference to 'writeData'
testdll.cpp
#include <stdio.h>
#include <string>
using namespace std;
class TestDLL {
public:
string data1;
public: void writeData (string s) {
printf ("%s \n", s.c_str());
}
};
TestDLL.h
#ifndef TESTDLL_H
#define TESTDLL_H
#ifdef TRADITIONALDLL_EXPORTS
#define TRADITIONALDLL_API __declspec(dllexport)
#else
#define TRADITIONALDLL_API __declspec(dllimport)
#endif
#ifdef __cplusplus
extern "C"
{
#endif
class TestDLL {
public:
std::string data1;
public:
TRADITIONALDLL_API void writeData (std::string);
};
#ifdef __cplusplus
}
#endif
#endif // TESTDLL_H
test.cpp
#include <string>
#include "TestDLL.h"
using namespace std;
class TestDLL;
int
main () {
TestDLL testdll;
testdll.writeData ("success");
}
Extended explanation: I've focused this down to something easy to post and hopefully easy for someone to answer. I was a C programmer back at the dawn of the PC era but haven't done much with C++ ever or C since then. I've been a Java programmer for quite some time (along with web stuff). Right now, I'm dealing with an existing program that can be extended with dlls, and the dlls need to be connected to a system written in Java. I've done the first step in JNI, so I have Java connected to a single dll. But the architecture needs to be:
Existing C application - dll extensions - dll for JNI - Java system
with communication both ways.
What happens if you add the two following lines to testdll.cpp:
#define TRADITIONALDLL_EXPORTS 1
#include "TestDLL.h"
I suspect that what's happening is that you're not doing that, so GCC doesn't know to compile TestDLL::writeData() with DLL export linkage.
I have C static library that has a struct global variable defined within it. I wish to access that variable within C++ code that will link to that C library. At the moment, I am getting "ld: symbol(s) not found for architecture x86_64" errors.
I am trying the following (simplified struct for the sake of the question):
// library.h
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int simpleVariableA;
int simpleVariableB;
} GlobalStruct;
extern GlobalStruct gs;
#ifdef __cplusplus
}
#endif
// library.c
#include "library.h"
GlobalStruct gs;
Library.c and library.h are compiled into library.a, a statically-linked library. I then want to link a C++ file to the library, but get errors.
// main.cpp
#include "library.h"
int main(int argc, char** argv) {
gs.simpleVariableA = 1;
gs.simpleVariableB = 1;
}
What am I doing wrong? I have run "nm -g library.a" and get "0000000000000008 C _gs" back, so I think this means that the symbol is being exported, no? Is it an issue with C++ not finding C code?
I compile the files like this:
gcc -c library.c -o library.o
ar rcs liblibrary.a library.o
g++ main.cpp -L. -llibrary
BTW, I get an error with the ar command:
warning: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: warning for library: liblibrary.a the table of contents is empty (no object file members in the library define global symbols)
However, this error goes away if I define a function f() in library.h and library.c and compile this into the library.
Each of your mentions of gs is merely a declaration (that is it indicates that there is a thing by this name somewhere), but none of them is a definition (which is something which additionally sets aside space for the named thing).
If you initialise gs in library.c, your problem goes away:
GlobalStruct gs = { 0, 0 };
The function of the extern keyword in your library.h is specifically to stop this being a (tentative) definition. It's possibly redundant in this case, but probably best to include it, and so make sure that there's only one definition of gs, in library.c.
Including the initialiser in library.c also stops the warning message, because that initialiser is what creates the thing that goes in the library (that is, you were correct: the warning was significant for your problem).
I tried to link a static library (compiled with gcc) to a c++ program and I got 'undefined reference'. I used gcc and g++ version 4.6.3 on a ubuntu 12.04 server machine. For example, here is the simple library file for factorial method:
mylib.h
#ifndef __MYLIB_H_
#define __MYLIB_H_
int factorial(int n);
#endif
mylib.c
#include "mylib.h"
int factorial(int n)
{
return ((n>=1)?(n*factorial(n-1)):1);
}
I created object for this mylib.c using gcc:
gcc -o mylib.o -c mylib.c
Again the static library was created from the object file using AR utility:
ar -cvq libfact.a mylib.o
I tested this library with a C program (test.c) and C++ program (test.cpp)
Both C and C++ program have the same body:
#include "mylib.h"
int main()
{
int fact = factorial(5);
return 0;
}
Assuming static library libfact.a is available in /home/test directory, I compiled my C program without any issues:
gcc test.c -L/home/test -lfact
However while testing C++ program, it threw a link error:
g++ test.cpp -L/home/test -lfact
test.cpp:(.text+0x2f): undefined reference to `factorial(int)'
collect2: ld returned 1 exit status
I even tried adding extern command in test.cpp:
extern int factorial(int n) //added just before the main () function
Still the same error.
Can someone tell me what I am wrong here?
Is there anything I missed while creating the static library?
Do I have to add anything in my test.cpp to make it work?
The problem is that you haven't told your C++ program that factorial is written in C. You need to change your test.h header file. Like this
#ifndef __MYLIB_H_
#define __MYLIB_H_
#ifdef __cplusplus
extern "C" {
#endif
int factorial(int n);
#ifdef __cplusplus
}
#endif
#endif
Now your header file should work for both C and C++ programs. See here for details.
BTW names containing a double underscore are reserved for the compliler (so are names starting with an underscore and a capital letter) so #ifndef __MYLIB_H_ is illegal strictly speaking. I would change to #ifndef MYLIB_H #define MYLIB_H
While the accepted answer is absolutely correct, I thought I'd just add an observation. Some editors have trouble with the open / close brace, and will indent the entire extern "C" scope in the header. If mylib.h is a key header for a library, you might consider:
#if defined (__cplusplus)
#define _MYLIB_INIT_DECL extern "C" {
#define _MYLIB_FINI_DECL }
#else
#define _MYLIB_INIT_DECL
#define _MYLIB_FINI_DECL
#endif
All other headers in mylib library, e.g., mylib_aux.h, can be of the form:
#ifndef _MYLIB_AUX_H
#define _MYLIB_AUX_H
#include <mylib.h>
_MYLIB_INIT_DECL
... header content ...
_MYLIB_FINI_DECL
#endif /* _MYLIB_AUX_H */
Obviously, the names I'm using are arbitrary, but for multiple library headers, this approach has been useful to me.
To teach myself a little C++, I decided to write a little Program to write text to my Saitek X52 Pro joystick display.
I wanted to use Eduards C-library
http://plasma.hasenleithner.at/x52pro/
I know I have to place an "extern C" around the methods if I want to use them in my C++ program. But that means changing the Header file of the library - and then it wouldn't build anymore.
What would be the correct approach in this case?
EDIT: the suggested method worked partially.
Comm.cpp:
...
extern "C"{
#include <x52pro.h>
}
using namespace std;
int main ( int argc, char *argv[] ) {
cout<<"entered main"<<endl;
char *String;
strcpy(String,"testing");
struct x52 *hdl = x52_init();
x52_settext(hdl, 0,String , 7);
x52_close(hdl);
return EXIT_SUCCESS;
}
Error Message:
Comm.o: In function `main':
Comm.cpp|38| undefined reference to `x52_init'
Comm.cpp|39| undefined reference to `x52_settext'
Comm.cpp|40| undefined reference to `x52_close'
which are all methods defined in x52pro.h
To use extern "C" in C header files, wrap it so
#ifdef __cplusplus
extern "C" {
#endif
...
#ifdef __cplusplus
}
#endif
or you can wrap the #includes with extern "C"
extern "C" {
#include <chdr1.h>
#include <chdr2.h>
}
When linking your application, you must tell the linker what library to use and where the library is. From your link, you must add libusb as well. This looks roughly like this
g++ -o app_name Comm.o -L /path/to/library -lx52pro -lusb
When the library is installed in the system lib directory, you can omit the -L /path/... part. If you use a Makefile, you define this in some variables, usually
LDFLAGS = -L /path/to/library
LDLIBS = -lx52pro -lusb
See also Compiling and Linking and Wikipedia - Linker (computing)
In your C++ code, you can surround the included header file with extern "C" like this:
extern "C" {
#include "c_header_file.h"
}
Then, you would not need to modify the header file of the third party library.
#ifdef __cplusplus
extern C {
#endif
...
#ifdef __cplusplus
}
#endif