2014-12-01 107 views
1

我有一个用C语言编写的库。库中的一个例程需要一个函数指针,将其保存到内存中供以后使用,然后返回。我正在尝试传递一个Python回调函数。CFUNCTYPE的增量引用计数器

我看到的问题是稍后调用此函数有时会导致分段错误。这是我正在玩的一些玩具代码。的Python代码:

import ctypes 
import sys 

class problem(ctypes.Structure): 
    _fields_ = [("val",ctypes.c_int),("f",ctypes.POINTER(ctypes.c_void_p))] 

def change_struct_data(s): 
    s.contents.val = 99 

class UsefulInfo: 
    def __init__(self,library_name): 
     self.lib = ctypes.pydll.LoadLibrary(library_name) 
     self.lib.create_data.restype = ctypes.POINTER(problem) 
     self.data = self.lib.create_data(12) 

    def setcallback(self,f): 
     cback = ctypes.CFUNCTYPE(None,ctypes.POINTER(problem)) 
     funcy = cback(f) 
     self.lib.pass_func(self.data,funcy) 

    def makecall(self): 
     self.data.contents.val = 0 
     self.lib.use_callback(self.data) 
     print 'val is now',self.data.contents.val 


test = UsefulInfo('./libfoo.so') 
test.setcallback(change_struct_data) 
test.makecall() 

C代码:

//gcc test.c -I/usr/include/python2.7/ -L/usr/lib/python2.7/ -lpython2.7 -lm -fPIC -c -g 
//gcc -shared -Wl,-soname,libfoo.so -o libfoo.so test.o 
#include <Python.h> 
#include <stdio.h> 
#include <stdlib.h> 

typedef struct problem 
{ 
    int val; 
     void (*f)(struct problem*); 
} problem; 


int C_inc_ref(PyObject* obj) 
{ 
    Py_INCREF(obj); 
    return 0; 
} 

void pass_func(problem* prob,void (*funcy)(problem*)) 
{ 
    prob->f = funcy; 
} 

void use_callback(problem* prob) 
{ 
    prob->f(prob); 
} 

problem* create_data(int n) 
{ 
    problem* prob = (problem*) malloc(sizeof(problem)); 
    prob->val = n; 
    prob->f = NULL; 
    return prob; 
} 

左思右想,我怀疑函数转换在日常setcallback到CFUNCTYPE被垃圾收集。我修改了程序略有print语句:

def setcallback(self,f): 
    cback = ctypes.CFUNCTYPE(None,ctypes.POINTER(problem)) 
    funcy = cback(f) 
    self.lib.pass_func(self.data,funcy) 
    print 'ref count is',sys.getrefcount(funcy) 

不足为奇的是,我得到funcy 2个引用。我试着将它设置为一个全局变量,以防止垃圾收集,这似乎工作(它确实增加了引用计数),但肯定不是很漂亮。

我认为更好的解决方案是手动增加引用计数。但我根本无法让这个工作正常。使用要么Py_IncRef呼叫或C代码的呼叫,其递增基准计数器

def setcallback(self,f): 
    cback = ctypes.CFUNCTYPE(None,ctypes.POINTER(problem)) 
    funcy = cback(f) 
    self.lib.pass_func(self.data,funcy) 
    #self.lib.C_inc_ref(funcy) 
    ctypes.pythonapi.Py_IncRef(funcy) 
    print 'ref count is',sys.getrefcount(funcy) 

:我再次改变例程。但是,无论使用哪种方法增加计数器,我要么出现分段错误,要么计数器不增加(保持2)。

有什么理由不能增加回调函数的引用计数器?也许我犯了一个错误?任何帮助表示赞赏。

+0

这篇文章对你有帮助吗? http://stackoverflow.com/a/7261524/1470749 – 101 2014-12-01 22:24:10

+0

你为什么使用'pydll'?在调用过程中,'PyDLL'类被标记为保存GIL,如果C库使用解释器,则只需要该类。 – eryksun 2014-12-06 00:16:51

回答

2

cback是一个返回封装函数的函数。那还有什么作用?装饰:

# global scope, the callback type 
cback = ctypes.CFUNCTYPE(None,ctypes.POINTER(problem)) 

# decorate the callback 
@cback 
def change_struct_data(s): 
    s.contents.val = 99 

现在change_struct_data实际上是一个实例化对象CFUNCTYPE不会超出范围。你不能直接从Python调用它,因为它已经被装饰成从C调用,但这不应该是一个问题。

+0

谢谢马克。装饰者的想法似乎可以做到。比制作​​人造引用更清洁。我仍然不明白为什么我不能在回调函数中递增引用计数... – 2014-12-03 21:15:46

+0

@ Neither_8,如果您设置了'Py_IncRef.argtypes = [ctypes.py_object]',它将起作用。你不会得到'ArgumentError',因为'funcy'是一个ctypes对象,所以你实际上将它的thunk指针传递给'Py_IncRef'。这不会立即发生segfault,因为thunk代码被映射为可写,但当然这是由此操作损坏的。 – eryksun 2014-12-06 00:26:11