2

I'm testing a scenario that when C++ set a function pointer to a python class variable, and then use PyObject_CallMethod to run another python method, which contain that class variable.

whole process would like this.

(1). PyCFunction_NewEx() make a py function -> (2). PyDict_SetItemString() assign to class variable under __dict__ -> (3). PyObject_CallMethod() call python method witch contain (1).

When I put all the code inside main() function (whitout void setCallback() and all code inside void setCallback() were placed in main()), It runs perfectly fine. However, after I put some code into a function, sometimes get seg fault, sometimes doesn't call function pointer in python and sometimes get correct answer.

How do I resolve this problem?

C++ Code: main.cpp

#include <python3.7/Python.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <python3.7/methodobject.h>

// func ptr.
PyObject *myCallFunc(PyObject *self,PyObject *args) {
    printf(" aaaaaaaaaaaaaaaaaaaaaaa\n");
    return NULL;
}

// def func ptr
typedef PyObject *(*PyCallFunc)(PyObject *self,PyObject *arg);

// set func ptr into python member var
void setCallback(PyObject *ClassObj){
    PyCallFunc pyCallFunc = myCallFunc;

    PyMethodDef methd = {"methd",pyCallFunc,METH_VARARGS,"py call func"};
    PyObject *fName = PyUnicode_FromString(methd.ml_name);
    if(fName == NULL) {
        printf(" fName\n");
        exit(0);
    }
    PyObject *pyRunFunc = PyCFunction_NewEx(&methd,NULL,fName);
    if(pyRunFunc == NULL){
        printf(" can not create py function. exit.");
        exit(0);
    }
    Py_DECREF(fName);

    PyObject* classAttrDict = PyObject_GetAttrString(ClassObj, "__dict__");     // extract instance Dictionary.
    if(classAttrDict == NULL) {
        printf(" classAttrDict\n");
        exit(0);
    }

    int pRetSetCurrPrice = PyDict_SetItemString(classAttrDict, "callFunc", pyRunFunc);
    if(pRetSetCurrPrice != 0){
        printf(" set error. exit.");
        exit(0);
    }
}

int main(int argc,char **argv){

    Py_SetProgramName((wchar_t *)argv[0]);
    void *pyMem = PyMem_Malloc(sizeof(wchar_t*)*argc);
    wchar_t** _argv = (wchar_t**)&pyMem;
    for (int i=0; i<argc; i++) {
        wchar_t* arg = Py_DecodeLocale(argv[i], NULL);
        _argv[i] = arg;
    }
    Py_Initialize();
    PySys_SetArgv(argc, _argv);


    PyObject* programName = PyUnicode_FromString("test");
    if(programName == NULL) {
        printf(" programName\n");
        exit(0);
    }

    PyObject* pCustomFunc = PyImport_Import(programName);   // import test
    Py_DECREF(programName);
    if(pCustomFunc == NULL) {
        printf(" pCustomFunc\n");
        exit(0);
    }
    PyObject* pClass = PyObject_GetAttrString(pCustomFunc, "Test");  // pClass = test.Test
    if(pClass == NULL) {
        printf(" pClass\n");
        exit(0);
    }    
    PyObject* pNewInstance = PyObject_CallObject(pClass,NULL);  // pNewInstance = test.Test()
    if(pNewInstance == NULL) {
        printf(" pNewInstance\n");
        exit(0);
    }

    setCallback(pNewInstance);

    PyObject* pCallRet = PyObject_CallMethod(pNewInstance, "runCustomFunc",NULL); // pCallRet = pNewInstance.callFunc()
    if(pCallRet == NULL) {
        printf(" pCallRet\n");
        //exit(0);
    }

    sleep(2);

    printf(" \n\nend\n\n");
    Py_Finalize();
    return 0;
}

Python code: test.py

import sys

def dummyFunc():
    pass

class Test:
    def __init__(self):
        self.aaa = 0
        self.callFunc = dummyFunc

    def runCustomFunc(self):
        print(" print from python.")
        print(" ref count of self.callFunc 1 is %d" %(sys.getrefcount(self.callFunc)))
        self.callFunc()
        print(" ref count of self.callFunc 2 is %d" %(sys.getrefcount(self.callFunc)))
        return 1

cmake for this test project: CMakeLists.txt

# set cmake and compiler.
cmake_minimum_required(VERSION 3.12...3.15)
set(CMAKE_CXX_FLAGS -std=c++17)

# set variable
set(CMAKE_POSITION_INDEPENDENT_CODE ON)    # test if this can resolve the problem
set(THREADS_PREFER_PTHREAD_FLAG ON)

if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

set(CMAKE_CXX_FLAGS "-Wall -Wextra")    # test if optimize cause the problem
set(CMAKE_CXX_FLAGS_DEBUG "-g")         # test if optimize cause the problem
set(CMAKE_CXX_FLAGS_RELEASE "-O0")      # test if optimize cause the problem

set(LINK_LIB "/usr/local/lib")

set(PYTHON3_LINKER "-lpython3.7")
#set(PTHREAD "-lpthread")
set(PYTHON3_HEADER "/usr/include/python3.7")
set(PYTHON3_LIB "/usr/lib/python3.7/config-3.7m-x86_64-linux-gnu")

set(CPP_FILE_LIST "main.cpp")



include_directories( ${PYTHON3_HEADER})
link_directories( ${PYTHON3_LIB} ${LINK_LIB})

add_executable(pyEmbedFunPtrTest ${CPP_FILE_LIST})

target_link_libraries(pyEmbedFunPtrTest ${PYTHON3_LINKER})

find_package(Threads REQUIRED)
target_link_libraries(pyEmbedFunPtrTest Threads::Threads)

#target_compile_options(pyEmbedFunPtrTest PUBLIC "-pthread")

1 Answer 1

1

It could be because the PyMethodDef is created on the stack of the setCallback

You can verify it in the source code of cpython here.

the PyMethodDef is not copied, it is referenced instead.

Sign up to request clarification or add additional context in comments.

1 Comment

I change it to PyMethodDef *methd = new PyMethodDef{"methd",pyCallFunc,METH_VARARGS,"py call func"}; and problem solved. Thank you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.