Skip to content

Commit c70bee8

Browse files
committed
add support for ctypes' function pointers
1 parent deffd23 commit c70bee8

File tree

4 files changed

+91
-38
lines changed

4 files changed

+91
-38
lines changed

src/Converters.cxx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,15 @@ struct CPyCppyy_tagPyCArgObject { // not public (but stable; note that olde
123123
#define ct_c_fcomplex 21
124124
#define ct_c_complex 22
125125
#define ct_c_pointer 23
126-
#define NTYPES 24
126+
#define ct_c_funcptr 24
127+
#define NTYPES 25
127128

128129
static std::array<const char*, NTYPES> gCTypesNames = {
129130
"c_bool", "c_char", "c_wchar", "c_byte", "c_ubyte", "c_short", "c_ushort", "c_uint16",
130131
"c_int", "c_uint", "c_uint32", "c_long", "c_ulong", "c_longlong", "c_ulonglong",
131132
"c_float", "c_double", "c_longdouble",
132-
"c_char_p", "c_wchar_p", "c_void_p", "c_fcomplex", "c_complex", "_Pointer" };
133+
"c_char_p", "c_wchar_p", "c_void_p", "c_fcomplex", "c_complex",
134+
"_Pointer", "_CFuncPtr" };
133135
static std::array<PyTypeObject*, NTYPES> gCTypesTypes;
134136
static std::array<PyTypeObject*, NTYPES> gCTypesPtrTypes;
135137

@@ -2645,6 +2647,12 @@ static void* PyFunction_AsCPointer(PyObject* pyobject,
26452647
// fall-through, with calling through Python
26462648
}
26472649

2650+
if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_funcptr))) {
2651+
// ctypes function pointer
2652+
void* fptr = *(void**)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;
2653+
return fptr;
2654+
}
2655+
26482656
if (PyCallable_Check(pyobject)) {
26492657
// generic python callable: create a C++ wrapper function
26502658
void* wpraddress = nullptr;

src/TemplateProxy.cxx

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,6 @@
1616

1717
namespace CPyCppyy {
1818

19-
//- helper for ctypes conversions --------------------------------------------
20-
static PyObject* TC2CppName(PyObject* pytc, const char* cpd, bool allow_voidp)
21-
{
22-
const char* name = nullptr;
23-
if (CPyCppyy_PyText_Check(pytc)) {
24-
char tc = ((char*)CPyCppyy_PyText_AsString(pytc))[0];
25-
switch (tc) {
26-
case '?': name = "bool"; break;
27-
case 'c': name = "char"; break;
28-
case 'b': name = "char"; break;
29-
case 'B': name = "unsigned char"; break;
30-
case 'h': name = "short"; break;
31-
case 'H': name = "unsigned short"; break;
32-
case 'i': name = "int"; break;
33-
case 'I': name = "unsigned int"; break;
34-
case 'l': name = "long"; break;
35-
case 'L': name = "unsigned long"; break;
36-
case 'q': name = "long long"; break;
37-
case 'Q': name = "unsigned long long"; break;
38-
case 'f': name = "float"; break;
39-
case 'd': name = "double"; break;
40-
case 'g': name = "long double"; break;
41-
case 'z': // special case for C strings, ignore cpd
42-
return CPyCppyy_PyText_FromString(std::string{"const char*"}.c_str());
43-
default: name = (allow_voidp ? "void*" : nullptr); break;
44-
}
45-
}
46-
47-
if (name)
48-
return CPyCppyy_PyText_FromString((std::string{name}+cpd).c_str());
49-
return nullptr;
50-
}
51-
5219
//----------------------------------------------------------------------------
5320
TemplateInfo::TemplateInfo() : fPyClass(nullptr), fNonTemplated(nullptr),
5421
fTemplated(nullptr), fLowPriority(nullptr), fDoc(nullptr)
@@ -109,6 +76,7 @@ PyObject* TemplateProxy::Instantiate(const std::string& fname,
10976
std::string proto = "";
11077

11178
#if PY_VERSION_HEX >= 0x03080000
79+
// adjust arguments for self if this is a rebound global function
11280
bool isNS = (((CPPScope*)fTI->fPyClass)->fFlags & CPPScope::kIsNamespace);
11381
if (!isNS && !fSelf && CPyCppyy_PyArgs_GET_SIZE(args, nargsf)) {
11482
args += 1;
@@ -138,7 +106,7 @@ PyObject* TemplateProxy::Instantiate(const std::string& fname,
138106
PyErr_Clear();
139107
}
140108

141-
PyObject* pyptrname = TC2CppName(pytc, ptrdef.c_str(), true);
109+
PyObject* pyptrname = Utility::CT2CppName(pytc, ptrdef.c_str(), true);
142110
if (pyptrname) {
143111
PyTuple_SET_ITEM(tpArgs, i, pyptrname);
144112
bArgSet = true;
@@ -152,14 +120,14 @@ PyObject* TemplateProxy::Instantiate(const std::string& fname,
152120
if (!bArgSet) pytc = PyObject_GetAttr(itemi, PyStrings::gCTypesType);
153121

154122
if (!bArgSet && pytc) {
155-
PyObject* pyactname = TC2CppName(pytc, "&", false);
123+
PyObject* pyactname = Utility::CT2CppName(pytc, "&", false);
156124
if (!pyactname) {
157125
// _type_ of a pointer to c_type is that type, which will have a type
158126
PyObject* newpytc = PyObject_GetAttr(pytc, PyStrings::gCTypesType);
159127
Py_DECREF(pytc);
160128
pytc = newpytc;
161129
if (pytc) {
162-
pyactname = TC2CppName(pytc, "*", false);
130+
pyactname = Utility::CT2CppName(pytc, "*", false);
163131
} else
164132
PyErr_Clear();
165133
}

src/Utility.cxx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg,
519519
}
520520

521521
if (arg && PyCallable_Check(arg)) {
522+
// annotated/typed Python function
522523
PyObject* annot = PyObject_GetAttr(arg, PyStrings::gAnnotations);
523524
if (annot) {
524525
if (PyDict_Check(annot) && 1 < PyDict_Size(annot)) {
@@ -540,6 +541,7 @@ static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg,
540541
tpn << ')';
541542
tmpl_name.append(tpn.str());
542543

544+
Py_DECREF(annot);
543545
return true;
544546

545547
} else
@@ -549,6 +551,40 @@ static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg,
549551
} else
550552
PyErr_Clear();
551553

554+
// ctypes function pointer
555+
PyObject* argtypes = PyObject_GetAttrString(arg, "argtypes");
556+
PyObject* ret = PyObject_GetAttrString(arg, "restype");
557+
if (argtypes && ret) {
558+
std::ostringstream tpn;
559+
PyObject* pytc = PyObject_GetAttr(ret, PyStrings::gCTypesType);
560+
tpn << CT2CppNameS(pytc, false)
561+
<< " (*)(";
562+
Py_DECREF(pytc);
563+
564+
for (Py_ssize_t i = 0; i < PySequence_Length(argtypes); ++i) {
565+
if (i) tpn << ", ";
566+
PyObject* item = PySequence_GetItem(argtypes, i);
567+
pytc = PyObject_GetAttr(item, PyStrings::gCTypesType);
568+
tpn << CT2CppNameS(pytc, false);
569+
Py_DECREF(pytc);
570+
Py_DECREF(item);
571+
}
572+
573+
tpn << ')';
574+
tmpl_name.append(tpn.str());
575+
576+
Py_DECREF(ret);
577+
Py_DECREF(argtypes);
578+
579+
return true;
580+
581+
} else {
582+
PyErr_Clear();
583+
Py_XDECREF(ret);
584+
Py_XDECREF(argtypes);
585+
}
586+
587+
// callable C++ type (e.g. std::function)
552588
PyObject* tpName = PyObject_GetAttr(arg, PyStrings::gCppName);
553589
if (tpName) {
554590
const char* cname = CPyCppyy_PyText_AsString(tpName);
@@ -625,6 +661,37 @@ std::string CPyCppyy::Utility::ConstructTemplateArgs(
625661
return tmpl_name;
626662
}
627663

664+
//----------------------------------------------------------------------------
665+
std::string CPyCppyy::Utility::CT2CppNameS(PyObject* pytc, bool allow_voidp)
666+
{
667+
// helper to convert ctypes' `_type_` info to the equivalent C++ name
668+
const char* name = "";
669+
if (CPyCppyy_PyText_Check(pytc)) {
670+
char tc = ((char*)CPyCppyy_PyText_AsString(pytc))[0];
671+
switch (tc) {
672+
case '?': name = "bool"; break;
673+
case 'c': name = "char"; break;
674+
case 'b': name = "char"; break;
675+
case 'B': name = "unsigned char"; break;
676+
case 'h': name = "short"; break;
677+
case 'H': name = "unsigned short"; break;
678+
case 'i': name = "int"; break;
679+
case 'I': name = "unsigned int"; break;
680+
case 'l': name = "long"; break;
681+
case 'L': name = "unsigned long"; break;
682+
case 'q': name = "long long"; break;
683+
case 'Q': name = "unsigned long long"; break;
684+
case 'f': name = "float"; break;
685+
case 'd': name = "double"; break;
686+
case 'g': name = "long double"; break;
687+
case 'z': name = "const char*"; break;
688+
default: name = (allow_voidp ? "void*" : nullptr); break;
689+
}
690+
}
691+
692+
return name;
693+
}
694+
628695
//----------------------------------------------------------------------------
629696
static inline bool check_scope(const std::string& name)
630697
{

src/Utility.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ PyCallable* FindBinaryOperator(const std::string& lcname, const std::string& rcn
3939
enum ArgPreference { kNone, kPointer, kReference, kValue };
4040
std::string ConstructTemplateArgs(
4141
PyObject* pyname, PyObject* tpArgs, PyObject* args = nullptr, ArgPreference = kNone, int argoff = 0, int* pcnt = nullptr);
42+
std::string CT2CppNameS(PyObject* pytc, bool allow_voidp);
43+
inline PyObject* CT2CppName(PyObject* pytc, const char* cpd, bool allow_voidp)
44+
{
45+
const std::string& name = CT2CppNameS(pytc, allow_voidp);
46+
if (!name.empty()) {
47+
if (name == "const char*") cpd = "";
48+
return CPyCppyy_PyText_FromString((std::string{name}+cpd).c_str());
49+
}
50+
return nullptr;
51+
}
4252

4353
// helper for generating callbacks
4454
void ConstructCallbackPreamble(const std::string& retType,

0 commit comments

Comments
 (0)