หมายเหตุ: คำตอบนี้มีส่วนยาวเกี่ยวกับวิธีแก้ปัญหา หากคุณเพียงต้องการใช้สิ่งนี้ให้ข้ามไปที่โซลูชัน 5 โดยตรง
ปัญหา
คุณพบว่าทุกอย่างใน Python นั้นเป็นวัตถุ ก่อนที่เราจะดูการแก้ไขสิ่งต่าง ๆ ก่อนอื่นเรามาทำความเข้าใจกับสิ่งที่เกิดขึ้นก่อน ฉันได้สร้างตัวอย่างที่สมบูรณ์เพื่อใช้งานด้วยไฟล์ส่วนหัว:
double f(double x) {
return x*x;
}
double myfun(double (*f)(double x)) {
fprintf(stdout, "%g\n", f(2.0));
return -1.0;
}
typedef double (*fptr_t)(double);
fptr_t make_fptr() {
return f;
}
การเปลี่ยนแปลงหลักที่ฉันทำจนถึงตอนนี้คือการเพิ่มคำจำกัดความให้กับการประกาศของคุณ เพื่อที่ฉันจะได้ทดสอบมันได้และฟังก์ชัน make_fptr()
ที่ส่งคืนบางสิ่งไปยัง Python ที่เรารู้ว่าจะถูกรวมไว้เป็นตัวชี้ฟังก์ชัน
ด้วยสิ่งนี้ โมดูล SWIG แรกอาจมีลักษณะดังนี้:
%module test
%{
#include "test.h"
%}
%include "test.h"
และเราสามารถคอมไพล์ด้วย:
swig2.0 -Wall -python test.i && gcc -Wall -Wextra -I/usr/include/python2.6 -std=gnu99 -shared -o _test.so test_wrap.c
ตอนนี้เราสามารถเรียกใช้สิ่งนี้และถาม Python เกี่ยวกับประเภทที่เรามี - ประเภท test.f
และประเภทของผลลัพธ์ของการเรียก test.make_fptr())
:
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> type(test.f)
<type 'builtin_function_or_method'>
>>> repr(test.f)
'<built-in function f>'
>>> type(test.make_fptr())
<type 'SwigPyObject'>
>>> repr(test.make_fptr())
"<Swig Object of type 'fptr_t' at 0xf7428530>"
ดังนั้นปัญหาที่เกิดขึ้นควรจะชัดเจน - ไม่มีการแปลงจากฟังก์ชันในตัวไปเป็นประเภท SWIG สำหรับตัวชี้ฟังก์ชัน ดังนั้นการเรียกของคุณไปที่ myfun(test.f)
จะไม่ทำงาน
การแก้ไขปัญหา
คำถามคือเราจะแก้ไขปัญหานี้ได้อย่างไร (และที่ไหน) อันที่จริง เราอาจเลือกวิธีแก้ปัญหาที่เป็นไปได้อย่างน้อยสี่วิธี ขึ้นอยู่กับจำนวนภาษาอื่นที่คุณกำหนดเป้าหมายและวิธี "Pythonic" ที่คุณต้องการให้เป็น
โซลูชันที่ 1:
วิธีแก้ปัญหาแรกนั้นไม่สำคัญ เราใช้ test.make_fptr()
เพื่อส่งคืนตัวจัดการ Python ไปยังตัวชี้ฟังก์ชันสำหรับ funciton f
แล้ว ดังนั้นเราจึงสามารถโทรได้จริง:
f=test.make_fptr()
test.myfun(f)
โดยส่วนตัวแล้วฉันไม่ชอบโซลูชันนี้มากนัก มันไม่ใช่สิ่งที่โปรแกรมเมอร์ Python คาดหวัง และไม่ใช่สิ่งที่โปรแกรมเมอร์ C คาดหวัง สิ่งเดียวที่จะเกิดขึ้นคือความเรียบง่ายของการนำไปปฏิบัติ
โซลูชันที่ 2:
SWIG ให้กลไกแก่เราในการแสดงตัวชี้ฟังก์ชันเป็นภาษาเป้าหมาย โดยใช้ %constant
(โดยปกติจะใช้สำหรับการเปิดเผยค่าคงที่เวลาคอมไพล์ แต่โดยพื้นฐานแล้วพอยน์เตอร์ฟังก์ชันทั้งหมดจะอยู่ในรูปแบบที่ง่ายที่สุดอยู่แล้ว)
ดังนั้นเราจึงสามารถแก้ไขไฟล์อินเทอร์เฟซ SWIG ของเราได้:
%module test
%{
#include "test.h"
%}
%constant double f(double);
%ignore f;
%include "test.h"
คำสั่ง %constant
บอกให้ SWIG ล้อม f
ไว้เป็นตัวชี้ฟังก์ชัน ไม่ใช่ฟังก์ชัน จำเป็นต้องใช้ %ignore
เพื่อหลีกเลี่ยงคำเตือนเกี่ยวกับการเห็นตัวระบุเดียวกันหลายเวอร์ชัน
(หมายเหตุ: ฉันได้ลบฟังก์ชัน typedef
และ make_fptr()
ออกจากไฟล์ส่วนหัว ณ จุดนี้ด้วย)
ซึ่งตอนนี้ให้เราวิ่ง:
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> type(test.f)
<type 'SwigPyObject'>
>>> repr(test.f)
"<Swig Object of type 'double (*)(double)' at 0xf7397650>"
เยี่ยมมาก - มีตัวชี้ฟังก์ชัน แต่มีอุปสรรค์กับสิ่งนี้:
>>> test.f(0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'SwigPyObject' object is not callable
ตอนนี้เราไม่สามารถเรียก test.f
จากฝั่ง Python ได้ ซึ่งนำไปสู่แนวทางแก้ไขถัดไป:
โซลูชันที่ 3:
เพื่อแก้ไขปัญหานี้ ขั้นแรกให้แสดง test.f
เป็น ทั้ง ตัวชี้ฟังก์ชันและฟังก์ชันในตัว เราสามารถทำได้โดยใช้ %rename
แทน %ignore
:
%การทดสอบโมดูล
%{
#include "test.h"
%}
%constant double f(double);
%rename(f_call) f;
%include "test.h"
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> repr(test.f)
"<Swig Object of type 'double (*)(double)' at 0xf73de650>"
>>> repr(test.f_call)
'<built-in function f_call>'
นั่นเป็นขั้นตอนหนึ่ง แต่ฉันก็ยังไม่ชอบความคิดที่ต้องจำว่าควรเขียน test.f_call
หรือแค่ test.f
ขึ้นอยู่กับบริบทของสิ่งที่ฉันต้องการจะทำกับ f
ในขณะนั้น เราสามารถทำได้โดยการเขียนโค้ด Python ลงในอินเทอร์เฟซ SWIG ของเรา:
%module test
%{
#include "test.h"
%}
%rename(_f_ptr) f;
%constant double f(double);
%rename(_f_call) f;
%feature("pythonprepend") myfun %{
args = f.modify(args)
%}
%include "test.h"
%pythoncode %{
class f_wrapper(object):
def __init__(self, fcall, fptr):
self.fptr = fptr
self.fcall = fcall
def __call__(self,*args):
return self.fcall(*args)
def modify(self, t):
return tuple([x.fptr if isinstance(x,self.__class__) else x for x in t])
f = f_wrapper(_f_call, _f_ptr)
%}
มีบิตการทำงานหลายอย่างที่นี่ ขั้นแรก เราสร้างคลาส Python บริสุทธิ์ใหม่เพื่อรวมฟังก์ชันที่เป็นทั้งตัวเรียกและตัวชี้ฟังก์ชัน มันถือเป็นสมาชิก SWIG จริงที่ห่อ (และเปลี่ยนชื่อ) ตัวชี้ฟังก์ชันและฟังก์ชัน ตอนนี้เปลี่ยนชื่อให้ขึ้นต้นด้วยขีดล่างเป็นแบบแผน Python ประการที่สอง เราตั้งค่า test.f
ให้เป็นอินสแตนซ์ของ wrapper นี้ เมื่อมันถูกเรียกว่าเป็นฟังก์ชัน มันจะส่งผ่านการโทร ในที่สุดเราก็ใส่โค้ดพิเศษลงใน wrapper myfun
เพื่อสลับในตัวชี้ฟังก์ชันจริงแทนที่จะเป็น wrapper ของเรา โดยระวังอย่าเปลี่ยนอาร์กิวเมนต์อื่น ๆ หากมี
สิ่งนี้ทำงานได้ตามที่คาดไว้ ตัวอย่างเช่น:
import test
print "As a callable"
test.f(2.0)
print "As a function pointer"
test.myfun(test.f)
เราอาจทำให้สิ่งนี้ดีขึ้นเล็กน้อย เช่น ด้วยมาโคร SWIG เพื่อหลีกเลี่ยงการสร้างอินสแตนซ์ %rename
, %constant
และ Wrapper ซ้ำ แต่เราไม่สามารถหลีกหนีจากความต้องการใช้ %feature("pythonprepend")
ในทุกที่ที่เราส่ง Wrapper เหล่านี้กลับไปยัง SWIG (หากเป็นไปได้ที่จะทำสิ่งนั้นอย่างโปร่งใส แสดงว่าเกินความรู้ Python ของฉัน)
โซลูชันที่ 4:
วิธีแก้ปัญหาก่อนหน้านี้ค่อนข้างเรียบร้อยกว่า มันทำงานโปร่งใสตามที่คุณคาดหวัง (ในฐานะทั้งผู้ใช้ C และ Python) และกลไกของมันถูกห่อหุ้มโดยไม่มีอะไรนอกจาก Python ที่นำไปใช้งาน
ยังมี gotcha นอกเหนือจากความจำเป็นในการใช้ pythonprepend สำหรับการใช้งานทุกครั้งของตัวชี้ฟังก์ชัน - หากคุณเรียกใช้ swig -python -builtin
มันก็จะไม่ทำงานเพราะไม่มีโค้ด Python ที่ต้องเติมไว้ตั้งแต่แรก! (คุณจะต้องเปลี่ยนโครงสร้างของ wrapper ให้เป็น: f = f_wrapper(_test._f_call, _test._f_ptr)
แต่นั่นจะไม่เพียงพอ)
ดังนั้นเราจึงสามารถแก้ไขได้โดยการเขียน Python C API บางส่วนในอินเทอร์เฟซ SWIG ของเรา:
%module test
%{
#include "test.h"
%}
%{
static __thread PyObject *callback;
static double dispatcher(double d) {
PyObject *result = PyObject_CallFunctionObjArgs(callback, PyFloat_FromDouble(d), NULL);
const double ret = PyFloat_AsDouble(result);
Py_DECREF(result);
return ret;
}
%}
%typemap(in) double(*)(double) {
if (!PyCallable_Check($input)) SWIG_fail;
$1 = dispatcher;
callback = $input;
}
%include "test.h"
นี่เป็นเรื่องน่าเกลียดเล็กน้อยด้วยเหตุผลสองประการ ประการแรก มันใช้ตัวแปรโกลบอล (เธรดโลคัล) เพื่อจัดเก็บ Python ที่เรียกได้ นั่นสามารถแก้ไขได้เล็กน้อยสำหรับการโทรกลับในโลกแห่งความเป็นจริงส่วนใหญ่ โดยมีอาร์กิวเมนต์ข้อมูลผู้ใช้ void*
รวมถึงอินพุตจริงของการโทรกลับ "userdata" สามารถเรียก Python ได้ในกรณีเหล่านั้น
ปัญหาที่สองนั้นยากกว่าเล็กน้อยในการแก้ไข - เนื่องจาก callable นั้นเป็นฟังก์ชัน C ที่ห่อหุ้มไว้ ลำดับการโทรตอนนี้จึงเกี่ยวข้องกับการห่อทุกอย่างให้เป็นประเภท Python และการเดินทางขึ้นและกลับจากล่าม Python เพียงเพื่อทำสิ่งที่ควรเป็นเรื่องเล็กน้อย นั่นเป็นค่าใช้จ่ายค่อนข้างน้อย
เราสามารถทำงานย้อนกลับจาก PyObject
ที่กำหนดได้ และลองหาว่าฟังก์ชันใด (ถ้ามี) เป็น wrapper สำหรับ:
%module test
%{
#include "test.h"
%}
%{
static __thread PyObject *callback;
static double dispatcher(double d) {
PyObject *result = PyObject_CallFunctionObjArgs(callback, PyFloat_FromDouble(d), NULL);
const double ret = PyFloat_AsDouble(result);
Py_DECREF(result);
return ret;
}
SWIGINTERN PyObject *_wrap_f(PyObject *self, PyObject *args);
double (*lookup_method(PyObject *m))(double) {
if (!PyCFunction_Check(m)) return NULL;
PyCFunctionObject *mo = (PyCFunctionObject*)m;
if (mo->m_ml->ml_meth == _wrap_f)
return f;
return NULL;
}
%}
%typemap(in) double(*)(double) {
if (!PyCallable_Check($input)) SWIG_fail;
$1 = lookup_method($input);
if (!$1) {
$1 = dispatcher;
callback = $input;
}
}
%include "test.h"
สิ่งนี้จำเป็นต้องมีโค้ดตัวชี้ต่อฟังก์ชัน แต่ตอนนี้มันเป็นการปรับให้เหมาะสมมากกว่าข้อกำหนดและอาจทำให้เป็นมาโคร SWIG แบบทั่วไปหรือสองแบบก็ได้
โซลูชันที่ 5:
ฉันกำลังทำงานกับโซลูชันที่ 5 ที่เรียบร้อยกว่าซึ่งจะใช้ %typemap(constcode)
เพื่ออนุญาตให้ %constant
ใช้เป็นทั้งวิธีการและตัวชี้ฟังก์ชัน ปรากฎว่า SWIG ได้รับการสนับสนุนอยู่แล้วในการทำเช่นนั้น ซึ่งฉันพบเมื่ออ่านแหล่งข้อมูล SWIG บางส่วน จริงๆ แล้วสิ่งที่เราต้องทำก็แค่:
%module test
%{
#include "test.h"
%}
%pythoncallback;
double f(double);
%nopythoncallback;
%ignore f;
%include "test.h"
%pythoncallback
เปิดใช้งานสถานะโกลบอลบางส่วนที่ทำให้ฟังก์ชันต่อมาถูกรวมเข้าด้วยกันเพื่อให้สามารถใช้งานได้ทั้งตัวชี้ฟังก์ชันและฟังก์ชัน! %nopythoncallback
ปิดการใช้งานนั้น
ซึ่งใช้งานได้ (มีหรือไม่มี -builtin
) กับ:
import test
test.f(2.0)
test.myfun(test.f)
ซึ่งแก้ปัญหาได้เกือบทั้งหมดในคราวเดียว นี่เป็นมีบันทึกไว้ในคู่มือด้วยซ้ำ แม้ว่าดูเหมือนจะไม่มีการกล่าวถึงใดๆ ก็ตาม %pythoncallback
. ดังนั้นโซลูชันทั้งสี่ก่อนหน้านี้จึงมีประโยชน์เป็นส่วนใหญ่เพียงเป็นตัวอย่างในการปรับแต่งอินเทอร์เฟซ SWIG
ยังคงมีกรณีหนึ่งที่โซลูชัน 4 จะมีประโยชน์ - หากคุณต้องการผสมผสานและจับคู่การโทรกลับที่ใช้ C และ Python คุณจะต้องใช้ไฮบริดของทั้งสอง (ตามหลักการแล้ว คุณควรลองทำการแปลงประเภทตัวชี้ฟังก์ชัน SWIG ในแผนผังพิมพ์ของคุณ จากนั้น iff นั้นล้มเหลวในการเลือกวิธี PyCallable แทน)
person
Flexo
schedule
09.04.2014