I wonder how to wrapping member function to global function in SWIG.
Example,
test.cpp
class test
{
public:
void foo1();
void foo2();
}
After Wrapping,
test.py
class test:
def foo1():
...
def foo2():
...
how to write *.i file?
First, you need a working set of files. A class definition with no implementation won't work:
test.hpp
class test {
public:
int foo1();
int foo2();
};
test.cpp
#include "test.hpp"
int test::foo1() { return 1; }
int test::foo2() { return 2; }
Then for the .i file:
// Name of the module. For Python this must be the name of the final
// _test.pyd and test.py files after swig processing and compilation.
%module test
%{
// Code to be inserted in the generated wrapper code.
// The wrapper must be able to call the wrapped code so include the header.
#include "test.hpp"
%}
// This tells swig to wrap everything found in "test.cpp".
// Note it *does not* process #include in this file by default, just
// declaration found directly in the file.
%include "test.hpp"
Run SWIG to generate (in this case) test.py and test_wrap.cxx:
swig -python -c++ test.i
Compile the wrapper. I'm doing this directly with MSVC. The Python include and libs directory must be provided to build the Python extension, and the name of the final Python extension should be _.pyd.
cl /EHsc /LD /W3 /Ic:\python310\include /Fe_test.pyd test_wrap.cxx test.cpp /link /libpath:c:\python310\libs
Demo:
C:\>swig -python -c++ test.i
C:\>cl /nologo /EHsc /LD /W3 /Id:\dev\python310\include /Fe_test.pyd test_wrap.cxx test.cpp /link /libpath:d:\dev\python310\libs
test_wrap.cxx
test.cpp
Generating Code...
Creating library _test.lib and object _test.exp
C:\>py
Python 3.10.2 (tags/v3.10.2:a58ebcc, Jan 17 2022, 14:12:15) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> t = test.test()
>>> t.foo1()
1
>>> t.foo2()
2
Related
I just created a DLL in C with Python in it.
When I export my function with python functions in it, I can't call it in my Python code
But when I export a classic C function without Python code inside, it works perfectly. I don't understand
C DLL
#include <stdio.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
__declspec(dllexport) PyObject* getList()
{
PyObject *PList = PyList_New(0);
PyList_Append(PList, Py_BuildValue("i", 1));
return PList;
}
Python Code
import ctypes
lib = ctypes.cdll.LoadLibrary("EasyProtect.dll")
getList = lib.getList
getList.argtypes = None
getList.restype = ctypes.py_object
print(getList())
My Error
print(getList())
OSError: exception: access violation reading 0x0000000000000010
According to [Python.Docs]: class ctypes.PyDLL(name, mode=DEFAULT_MODE, handle=None) (emphasis is mine):
Instances of this class behave like CDLL instances, except that the Python GIL is not released during the function call, and after the function execution the Python error flag is checked. If the error flag is set, a Python exception is raised.
Thus, this is only useful to call Python C api functions directly.
So, you should replace cdll (CDLL) by pydll (PyDLL). I enhanced your example a bit.
dll00.c:
#include <stdio.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#if defined(_WIN32)
# define DLL00_EXPORT_API __declspec(dllexport)
#else
# define DLL00_EXPORT_API
#endif
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT_API PyObject* createList(unsigned int size, int *pValues);
#if defined(__cplusplus)
}
#endif
PyObject* createList(unsigned int size, int *pValues)
{
PyObject *pList = PyList_New(size);
for (unsigned int i = 0; i < size; ++i) {
PyList_SetItem(pList, i, Py_BuildValue("i", pValues[i]));
}
return pList;
}
code00.py:
#!/usr/bin/env python
import ctypes as ct
import sys
DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
IntPtr = ct.POINTER(ct.c_int)
def main(*argv):
dll00 = ct.PyDLL(DLL_NAME)
create_list = dll00.createList
create_list.argtypes = (ct.c_uint, IntPtr)
create_list.restype = ct.py_object
dim = 5
int_arr = (ct.c_int * dim)(*range(dim, 0, -1)) # Intermediary step: create an array
res = create_list(dim, ct.cast(int_arr, IntPtr))
print("\n{:s} returned: {:}".format(create_list.__name__, res))
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
Output:
[cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q072231434]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2019\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul
[prompt]> dir /b
code00.py
dll00.c
[prompt]>
[prompt]> cl /nologo /MD /DDLL /I"c:\Install\pc064\Python\Python\03.09\include" dll00.c /link /NOLOGO /DLL /OUT:dll00.dll /LIBPATH:"c:\Install\pc064\Python\Python\03.09\libs"
dll00.c
Creating library dll00.lib and object dll00.exp
[prompt]>
[prompt]> dir /b
code00.py
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj
[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
createList returned: [5, 4, 3, 2, 1]
Done.
When you use the Python C API, the GIL (global interpreter lock) must be held. Use PyDLL for that. Load your DLL with:
lib = ctypes.PyDLL("EasyProtect.dll")
As an aside, your DLL has a reference leak. Py_BuildValue returns a new object, and PyList_Append increments the reference when adding it to the list. Py_DECREF should be called on the object returned by Py_BuildValue.
In this case, though, create the list with the size you want and use PyList_SET_ITEM which steals the new object's reference to initialize the list and is optimized for initializing new list items:
#include <stdio.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
__declspec(dllexport) PyObject* getList()
{
PyObject *PList = PyList_New(1); # size 1 list
PyList_SET_ITEM(PList, 0, PyLong_FromLong(1)); # assign offset 0
return PList;
}
I'm working on a program that supports Python extensions and noticed that it does not open if the user does not have Python in their machines or uses the x64 version instead of the x32 one. (I cannot change this last part as it does not depend on me).
So I've been reading about delay loading to later check if the library is available and did this:
// linker: /DELAYLOAD:python3.dll
#include <delayimp.h>
#include <Python.h>
#pragma comment(lib, "delayimp")
#pragma comment(lib, "python3")
...and everything worked fine until studio gave me this problem:
LNK1194 cannot delay-load 'python3.dll' due to import of data symbol
'__imp__PyType_Type'; link without /DELAYLOAD:python3.dll
So my question is: Is there a workaround for this issue?
I've been thinking of editing the includes and directly defining PyType_Type in my program from their GitHub page but I'm afraid of breaking something...
Thank you.
Here's a small demo (as I thought in one of my comments)
dll00.h:
#pragma once
#if defined(_WIN32)
# if defined DLL00_EXPORTS
# define DLL00_EXPORT_API __declspec(dllexport)
# else
# define DLL00_EXPORT_API __declspec(dllexport)
# endif
#else
# define DLL00_EXPORT_API
#endif
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT_API int dllPyFunc(const char *pyStr);
#if defined(__cplusplus)
}
#endif
dll00.c:
#define DLL00_EXPORTS
#include "dll00.h"
#include <Python.h>
#include <stdio.h>
int dllPyFunc(const char *pyStr)
{
if (!pyStr) {
printf("NULL PY test!\n");
return -1;
}
int res = 0;
if (!Py_IsInitialized())
Py_InitializeEx(0);
res = PyRun_SimpleString(pyStr);
res |= Py_FinalizeEx();
return res;
}
main00.c:
#if defined(DELAYLOAD)
# include "dll00.h"
# pragma comment(lib, "delayimp")
# pragma comment(lib, "dll00")
#endif
#include <Windows.h>
#include <stdio.h>
#if !defined(DELAYLOAD)
typedef int (*DllPyFuncFuncPtr)(const char *pyStr);
DllPyFuncFuncPtr dllPyFunc = NULL;
#endif
int main(int argc, char *argv[])
{
int ret = 0;
printf("Arg count: %d\n", argc);
if (argc == 1) {
printf("NO PYTHON WHATSOEVER!!!\n");
ret = 0;
} else {
printf("Attempt to run [%s] from Python\n", argv[1]);
#if !defined(DELAYLOAD)
HMODULE hDll00 = LoadLibrary("dll00.dll");
if (hDll00 == NULL) {
printf("Error loading dll: %d\n", GetLastError());
return -1;
}
dllPyFunc = (DllPyFuncFuncPtr)GetProcAddress(hDll00, "dllPyFunc");
if (dllPyFunc == NULL) {
printf("Error getting function: %d\n", GetLastError());
FreeLibrary(hDll00);
hDll00 = NULL;
return -2;
}
#endif
ret = dllPyFunc(argv[1]);
#if !defined(DELAYLOAD)
FreeLibrary(hDll00);
hDll00 = NULL;
#endif
}
printf("\nDone.\n");
return ret;
}
Output:
[cfati#CFATI-W10PC064:e:\Work\Dev\StackOverflow\q069418904]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
[prompt]> "c:\Install\pc032\MS\VisualStudioCommunity\2019\VC\Auxiliary\Build\vcvarsall.bat" x64 >nul
[prompt]> dir /b
dll00.c
dll00.h
main00.c
[prompt]> :: Build .dll
[prompt]> cl /nologo /MD /DDLL /I"c:\Install\pc064\Python\Python\03.08\include" dll00.c /link /NOLOGO /DLL /OUT:dll00.dll /LIBPATH:"c:\Install\pc064\Python\Python\03.08\libs"
dll00.c
Creating library dll00.lib and object dll00.exp
[prompt]> :: Build .exe (dynamic .dll load)
[prompt]> cl /nologo /MD /W0 main00.c /link /NOLOGO /OUT:main00_pc064.exe
main00.c
[prompt]> :: Build .exe (delayed .dll load)
[prompt]> cl /nologo /MD /W0 /DDELAYLOAD main00.c /link /NOLOGO /OUT:main00_dl_pc064.exe /DELAYLOAD:dll00.dll
main00.c
[prompt]> dir /b
dll00.c
dll00.dll
dll00.exp
dll00.h
dll00.lib
dll00.obj
main00.c
main00.obj
main00_dl_pc064.exe
main00_pc064.exe
[prompt]> :: Save current path (which doesn't have python38.dll's parent)
[prompt]> set _PATH=%PATH%
[prompt]> :: Add python38.dll's parent to PATH
[prompt]> set PATH=%_PATH%;c:\Install\pc064\Python\Python\03.08
[prompt]>
[prompt]> main00_pc064.exe
Arg count: 1
NO PYTHON WHATSOEVER!!!
Done.
[prompt]> main00_pc064.exe "import os;print(os.getcwd())"
Arg count: 2
Attempt to run [import os;print(os.getcwd())] from Python
e:\Work\Dev\StackOverflow\q069418904
Done.
[prompt]> main00_dl_pc064.exe
Arg count: 1
NO PYTHON WHATSOEVER!!!
Done.
[prompt]> main00_dl_pc064.exe "import os;print(os.getcwd())"
Arg count: 2
Attempt to run [import os;print(os.getcwd())] from Python
e:\Work\Dev\StackOverflow\q069418904
Done.
[prompt]> :: NO python38.dll
[prompt]> set PATH=%_PATH%
[prompt]> main00_pc064.exe
Arg count: 1
NO PYTHON WHATSOEVER!!!
Done.
[prompt]> main00_pc064.exe "import os;print(os.getcwd())"
Arg count: 2
Attempt to run [import os;print(os.getcwd())] from Python
Error loading dll: 126
[prompt]> main00_dl_pc064.exe
Arg count: 1
NO PYTHON WHATSOEVER!!!
Done.
[prompt]> main00_dl_pc064.exe "import os;print(os.getcwd())"
Arg count: 2
Attempt to run [import os;print(os.getcwd())] from Python
<<<<<<<< CRASH HERE >>>>>>>>
Notes:
Although this example is too trivial to evidentiate it, dynamic loading the library requires more code (on the .exe side)
On the other hand, delay load approach crashes if Python is required but not present in %PATH%
So I found a post by Raymond Chen about dll forwarding. It may be a solution so I will post it here for now and update later if it works.
Raymond Chen post: https://devblogs.microsoft.com/oldnewthing/20080204-00/?p=23593
Edit: It seems like a good option for other libs but not for Python. They are already doing this (the python3 dll is a forwarding to python3X) and the __imp__PyType_Type is still a problem.
In the end I just created a copy of the lib and put it inside a folder called PyLibrary that compiles with Python.
Edit2: In the end I may just take python off and into another module and just delayload that module.
I'm attempting to learn SWIG (http://www.swig.org/) in order to extend/expose a C++ library that I have to Python & Java languages.
Before starting this endeavor, I started with a simple SWIG example (http://www.swig.org/tutorial.html) & got that working.
See below (you can see swig woks on my system & that I can load "example" module in Python shell):
linux{me}% swig -python example.i
linux{me}% gcc -c -fPIC example.c example_wrap.c / -I/usr/include/python2.7
gcc: warning: /: linker input file unused because linking not done
linux{me}% gcc -c -fPIC example.c example_wrap.c -I/usr/include/python2.7
linux{me}% ld -shared example.o example_wrap.o -o _example.so
linux{me}% python
Python 2.7.5 (default, Aug 2 2016, 04:20:16)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
import example
example.fact(5)
120
example.my_mod(5)
Traceback (most recent call last):
File "", line 1, in
TypeError: my_mod() takes exactly 2 arguments (1 given)
example.my_mod(7,3)
1
example.get_time()
'Fri Feb 28 14:54:08 2020\n'
All is good, now the next step is to expose a C++ class, so I followed this tutorial initially (https://realmike.org/blog/2010/07/18/python-extensions-in-cpp-using-swig/). When compared to official SWIG docs this tutorial had a few steps missing, like load the .so shared libray so that Python can call "import" on the library, we will get to that later...
I made a few changes to the SomeClass.h to add constants "#define TypeBool 0" etc...
Here is the relevant dummy C++ header:
#ifndef _SOME_CLASS_H_
#define _SOME_CLASS_H_
class SomeClass {
public:
SomeClass();
SomeClass(int a, int b);
virtual ~SomeClass();
void MethodA(int a = -1);
void MethodB(int b = -1);
void setType(int type);
int GetValA();
int GetValB();
int GetType();
private:
int mValA;
int mValB;
int type;
};
#endif // _SOME_CLASS_H_
Here is the relevant Class file that implements the SomeClass.h:
#include "SomeClass.h"
//refactor to use enum
#define TypeBool 0
#define TypeChar 1
#define TypeByte 2
#define TypeInt 3
#define TypeShort 4
#define TypeFloat 5
#define TypeDouble 6
#define TypeString 7
#define TypeComposite 8
SomeClass::SomeClass() {
mValA = -1;
mValB = -1;
}
SomeClass::~SomeClass() {
}
void SomeClass::MethodA(int a)
{
mValA = mValA * -1;
}
void setType(int type)
{
switch (type)
{
case 0:
type = TypeBool;
break;
case 1:
type = TypeChar;
break;
case 2:
type = TypeByte;
break;
case 3:
type = TypeInt;
break;
case 4:
type = TypeShort;
break;
case 5:
type = TypeFloat;
break;
case 6:
type = TypeDouble;
break;
case 7:
type = TypeString;
break;
case 8:
type = TypeComposite;
break;
}
}
void SomeClass::MethodB(int b)
{
mValB = b;
}
int SomeClass::GetValA()
{
return mValA;
}
int SomeClass::GetValB()
{
return mValB;
}
int SomeClass::GetType()
{
return type;
}
Attempt #1 Here is the SWIG required "interface" file, based on (https://realmike.org/blog/2010/07/18/python-extensions-in-cpp-using-swig/) or http://www.swig.org/Doc4.0/SWIGDocumentation.pdf pages 25-32.
%module mymodule
%{
#include "SomeClass.h"
%}
%include "SomeClass.h"
1) Then I issue the command:
swig -c++ -python -I/home/me/NetBeansProjects/example mymodule.i
This commnand runs and seems to create python & cpp autogenerated wrappers:
-rw-r--r-- 1 me linuxlusers 350 Mar 3 15:53 mymodule.i
-rw-r--r-- 1 me linuxlusers 2864 Mar 3 15:55 mymodule.py
-rw-r--r-- 1 me linuxlusers 121327 Mar 3 15:55 mymodule_wrap.cxx
2) Then I compile:
g++ -c -fPIC SomeClass.cpp mymodule_wrap.cxx -I/usr/include/python2.7
Which creates object files:
-rw-r--r-- 1 me linuxlusers 60136 Mar 3 15:56 mymodule_wrap.o
-rw-r--r-- 1 me linuxlusers 4384 Mar 3 15:56 SomeClass.o
3)Then, I bundle my object files into shared library file ".so" & make that shared library available system-wide so I can load it from Python:
linux{me}% ld -shared mymodule_wrap.o -o _mymodule.so
4) Finally, I call python shell from my command line & attempt to import mymodule:
linux{me}% python
Python 2.7.5 (default, Aug 2 2016, 04:20:16)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import mymodule
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "mymodule.py", line 15, in <module>
import _mymodule
ImportError: ./_mymodule.so: undefined symbol: __gxx_personality_v0
>>>
You can see the error: ImportError: ./_mymodule.so: undefined symbol: __gxx_personality_v0.
Attempt $2 (I rewrote the interface file with .cpp implememntation in the %inline tag):
%module mymodule
%{
#include "SomeClass.h"
%}
%include "SomeClass.h"
%constant TypeBool 0
%constant TypeChar 1
%constant TypeByte 2
%constant TypeInt 3
%constant TypeShort 4
%constant TypeFloat 5
%constant TypeDouble 6
%constant TypeString 7
%constant TypeComposite 8
%inline %{
SomeClass::SomeClass() {
mValA = -1;
mValB = -1;
}
SomeClass::~SomeClass() {
}
void SomeClass::MethodA(int a)
{
mValA = mValA * -1;
}
void setType(int type)
{
switch (type)
{
case 0:
type = TypeBool;
break;
case 1:
type = TypeChar;
break;
case 2:
type = TypeByte;
break;
case 3:
type = TypeInt;
break;
case 4:
type = TypeShort;
break;
case 5:
type = TypeFloat;
break;
case 6:
type = TypeDouble;
break;
case 7:
type = TypeString;
break;
case 8:
type = TypeComposite;
break;
}
}
void SomeClass::MethodB(int b)
{
mValB = b;
}
int SomeClass::GetValA()
{
return mValA;
}
int SomeClass::GetValB()
{
return mValB;
}
int SomeClass::GetType()
{
return type;
}
%}
I ran the swig commands & compilation again, however I got the same error:
linux{me}% swig -c++ -python -I/home/me/NetBeansProjects/example mymodule.i
linux{me}% g++ -c -fPIC SomeClass.cpp mymodule_wrap.cxx -I/usr/include/python2.7
linux{me}% ld -shared mymodule_wrap.o -o _mymodule.so
linux{me}% python
Python 2.7.5 (default, Aug 2 2016, 04:20:16)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import mymodule
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "mymodule.py", line 15, in <module>
import _mymodule
ImportError: ./_mymodule.so: undefined symbol: __gxx_personality_v0
>>>
KeyboardInterrupt
>>>
Attempt 3 (I rewrote the interface file with header .h in the %inline tag):
%module mymodule
%{
#include "SomeClass.h"
%}
%include "SomeClass.h"
%constant TypeBool 0
%constant TypeChar 1
%constant TypeByte 2
%constant TypeInt 3
%constant TypeShort 4
%constant TypeFloat 5
%constant TypeDouble 6
%constant TypeString 7
%constant TypeComposite 8
%inline %{
class SomeClass {
public:
SomeClass();
SomeClass(int a, int b);
virtual ~SomeClass();
void MethodA(int a = -1);
void MethodB(int b = -1);
void setType(int type);
int GetValA();
int GetValB();
int GetType();
private:
int mValA;
int mValB;
int type;
};
%}
Again, I got the same error (ImportError: ./_mymodule.so: undefined symbol: __gxx_personality_v0.) as the other 2 attempts above.
I attempted compiler flags as recommended in "undefined symbol: __cxa_pure_virtual" error when loading library from java
However, I got the same error.
1) How can I resolve this error?
Attempt #4
Somewhat of a solution:
I wrote whole new simpler class based on another online guide, but without overloaded constructors & functions.
I imported SWIG wrapped C++ class (Word.cpp), by removing multiple constructors & only have single default const.
See below:
#ifndef WORD_H
#define WORD_H
#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std;
class Word {
public:
Word();
// REMOVED Word(std::string the_word);
//REMOVED Word(const Word& orig);
virtual ~Word();
virtual void updateWord(std::string word);
virtual std::string getWord();
private:
std::string _the_word;
};
#endif /* WORD_H */
SWIG interface for Word.h:
%{
/* Put header files here or function declarations like below */
/*#include "example.h"*/
#include "Word.h"
%}
%include "std_string.i"
/* %include "example.h"*/
%include "Word.h"
And used these compile options:
swig -python example.i
swig -python -c++ example.i
python setup.py build_ext --inplace
Python:
python
Python 2.7.5 (default, Aug 2 2016, 04:20:16)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
import _example
_example.new_Word()
<Swig Object of type 'Word *' at 0x7f4debcceb70>
_example.new_Word()
swig/python detected a memory leak of type 'Word *', no destructor found.
<Swig Object of type 'Word *' at 0x7f4debcced50>
w = _example.new_Word()
_example.Word_updateWord(w,"beef")
_example.Word_getWord(w)
swig/python detected a memory leak of type 'Word *', no destructor found.
'beef'
_example.Word_updateWord(w,"chicken")
_example.Word_getWord(w)
'chicken'
So without the same function, or constructor signature we are able to export C++ Class as a Python module.
However, this is very limiting, since Object Orientation design patterns use overloading/overriding significantly.
Thanks,
A million!
I have a little problem with the compilation of multiple C++ on Windows.
I implemented four classes in C++ for cryptography with gmp. I want to call them from Python with ctypes.
I wrote a cpp file with the extern keyword:
#include "integer.h"
#include "modular_number.h"
#include "padic_number.h"
#include "rational_number.h"
extern "C" {
__declspec(dllexport) ModNum* newModNum(const char * n, const char * p) { return new ModNum(Integer(n), Integer(p)); }
__declspec(dllexport) const char* getModValue(const ModNum& mod){ return mod.getValue().getValue(); }
__declspec(dllexport) RationalNum* newRationalNum(const char* mpq) { return new RationalNum(mpq); }
__declspec(dllexport) const char* getRationalValue(const RationalNum& rat){ return rat.getValue(); }
__declspec(dllexport) PadicNum* newPadicNum(const char* n, const char* base) { return new PadicNum(Integer(n), Integer(base)); }
__declspec(dllexport) const char* getPadicValue(const PadicNum& padic){ return padic.getValue().getValue(); }
}
I compiled my files with:
mingw32-g++ -fexceptions -g -fexpensive-optimizations -flto -O3 -Weffc++ -Wextra -Wall -std=c++14 -fPIC -Og -IC:\MinGW\include -flto -s -lgmp -lmpfr -lpthread -c -fPIC *.cpp -I"C:\Program Files\Python38-32\include" -I"C:\Program Files\Python38-32\libs"
mingw32-g++.exe -shared -Wl,-dll -o numeric.dll *.o -lgmp -lmpfr -lgmpxx -static
But when I use these commands in Python:
import ctypes;
x = ctypes.DLL("./numeric.dll");
The variable x does not have the functions: newModNum, getModValue, etc...
Could anyone tell me what I'm doing wrong? I get no error and I do not understand.
My other files are common C ++ files with header and implementation.
Thanks in advance and have a nice day!
ctypes functions are imported on first use. Using libc as an example:
>>> import ctypes
>>> libc = ctypes.CDLL("libc.so.06")
>>> "printf" in dir(libc)
False
>>> libc.printf
<_FuncPtr object at 0x7f6512c23430>
>>> "printf" in dir(libc)
True
ctypes assumes all parameters and the return value are int. You should give type hints which also conveniently import the functions.
import ctypes
x = ctypes.DLL("./numeric.dll")
x.newModNum.argtypes = [ctypes.c_char_p, ctypes.c_char_p] # <-- also imports
x.newModNum.rettype = ctypes.c_void_p
And remove the semicolons from the end of lines. It causes dangerous blood pressure spikes in python programmers.
I've a DLL that embeds Python interpreter using the C/Python API. The DLL works fine if called one time, but if the DLL is called twice, the code cracks and my program catch memory error.
The C code calling DLL is simple and the call to DLL function(Which calls Python interpreter) is done one time, if the second call(In the code) is not commented the code cracks and this happens only if "Numpy" is called in the Python code.
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <dll_simples.h>
int main() {
double in[] = { 4,2,5,4,2 };
double out[5] = {};
double a = 0;
double b = 0;
simuser(a,b,in,out);
//simuser(a, b, in, out); IF NOT COMMENTED -> ERROR
return 0;
}
I've commented Py_Finalize() in the DLL as suggested here and this post here affirms that from Py_Finalize() docs"Some extensions may not work properly if their initialization routine is called more than once; this can happen if an application calls Py_Initialize() and Py_Finalize() more than once." So i'd like to know why this happens and if any other thing could be done except calls "Py_Finalize()" only one time in the last call to DLL.
Listing [Python 3.Docs]: ctypes - A foreign function library for Python.
dll00.h:
#pragma once
#if defined(_WIN32)
# if defined DLL0_EXPORTS
# define DLL00_EXPORT_API __declspec(dllexport)
# else
# define DLL00_EXPORT_API __declspec(dllimport)
# endif
#else
# define DLL00_EXPORT_API
#endif
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT_API int dll00Func00(double t, double delta, unsigned int size, const double *pIn, double *pOut);
#if defined(__cplusplus)
}
#endif
dll00.c:
#include <stdio.h>
#include "Python.h"
#define DLL0_EXPORTS
#include "dll00.h"
#define C_TAG "From C .dll"
int dll00Func00(double t, double delta, unsigned int size, const double *pIn, double *pOut) {
int res = 0;
printf("%s: in function\n", C_TAG);
const int isInit = Py_IsInitialized();
// Modify array calling Python functions
if (!isInit) {
printf("%s: initializing Python interpreter\n", C_TAG);
Py_Initialize();
}
res = PyRun_SimpleString("print(\"From Python (within C .dll): test\")");
for (unsigned int i = 0; i < size; i++) {
pOut[i] = pIn[i] * t + delta;
}
if (!isInit) {
printf("%s: uninitializing Python interpreter\n", C_TAG);
Py_Finalize();
}
return 0;
}
main00.c:
#include <stdio.h>
#include "dll00.h"
#define SIZE 4
int main() {
int res = 0;
double in[SIZE] = { 10.0, 11.0, 12.0, 13.0 };
double out[SIZE] = { 0 };
res = dll00Func00(2, 0.5, SIZE, in, out);
printf("Output array:\n");
for (unsigned int i = 0; i < SIZE; i++) {
printf("%.03f ", out[i]);
}
printf("\n");
return 0;
}
code00.py:
#!/usr/bin/env python
import sys
import ctypes as ct
DLL_NAME = "./dll00.dll"
def main(*argv):
DblPtr = ct.POINTER(ct.c_double)
size = 5
DblArr = ct.c_double * size
dll00 = ct.PyDLL(DLL_NAME)
dll00Func00 = dll00.dll00Func00
dll00Func00.argtypes = (ct.c_double, ct.c_double, ct.c_uint, DblPtr, DblPtr)
dll00Func00.restype = ct.c_int
in_arr = DblArr(*range(size))
out_arr = DblArr()
print("Output array:")
for i in range(size):
print("{:.3f}".format(out_arr[i]), end=" ")
print("\n")
res = dll00Func00(2, 2.5, size, in_arr, out_arr)
print("Output array:")
for i in range(size):
print("{:.3f}".format(out_arr[i]), end=" ")
print()
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main(*sys.argv[1:])
print("\nDone.")
Notes:
In Python, use ctypes.PyDLL as you're (indirectly) calling Python API functions
In the .dll, use [Python 3.Docs]: Initialization, Finalization, and Threads - int Py_IsInitialized()
As a side note, the if test is not needed in Py_Initialize's case as Py_Initialize simply returns if the interpreter is already initialized (so I left it there just for consistency), but it is needed for Py_Finalize as one wouldn't want to uninitialize the interpreter while still in Python. So Py_Initialize / Py_Finalize pair doesn't work on "reference count" (every Py_Initialize call requires an Py_Finalize one)
Calling Py_Initialize / Py_Finalize in the function, seems like an overkill (if the function is being called multiple times). I'd do 2 wrapper functions in the .dll and call:
one at the beginning
the other at the end
of the (C) program
Output:
e:\Work\Dev\StackOverflow\q059937552>sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2017\VC\Auxiliary\Build\vcvarsall.bat" x64
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.19
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'
[prompt]> dir /b
code00.py
dll00.c
dll00.h
main00.c
[prompt]> cl /nologo /MD /DDLL /I"c:\Install\pc064\Python\Python\03.07.06\include" dll00.c /link /NOLOGO /DLL /OUT:dll00.dll /LIBPATH:"c:\Install\pc064\Python\Python\03.07.06\libs"
dll00.c
Creating library dll00.lib and object dll00.exp
[prompt]> cl /nologo /MD /W0 main00.c /link /NOLOGO /OUT:main00_064.exe dll00.lib
main00.c
[prompt]> dir /b
code00.py
dll00.c
dll00.dll
dll00.exp
dll00.h
dll00.lib
dll00.obj
main00.c
main00.obj
main00_064.exe
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
Output array:
0.000 0.000 0.000 0.000 0.000
From C .dll: in function
From Python (within C .dll): test
Output array:
2.500 4.500 6.500 8.500 10.500
Done.
[prompt]> set PATH=%PATH%;c:\Install\pc064\Python\Python\03.07.06
[prompt]> main00_064.exe
From C .dll: in function
From C .dll: initializing Python interpreter
From Python (within C .dll): test
From C .dll: uninitializing Python interpreter
Output array:
20.500 22.500 24.500 26.500