This repository has been archived by the owner on Jun 27, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathpymex.c
212 lines (194 loc) · 6.8 KB
/
pymex.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/* Copyright (c) 2009 Ken Watford ([email protected])
For full license details, see the LICENSE file. */
/* The pymex kernel. See commands.c for kernel commands,
or do `c = pymex` for a list. */
#include "pymex.h"
#include <mex.h>
#include <dlfcn.h>
#define XMACRO_DEFS "commands.c"
/* Macros used during x-macro expansion. */
#define PYMEX_SIG(name) \
void name##_pymexfun(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
#define PYMEX_DEFINE(name, min, max, doc, body) \
PYMEX_SIG(name) { \
PYMEX_DEBUG("<start " #name ">\n"); \
if (nrhs < min || nrhs > max) { \
mexErrMsgIdAndTxt("pymex:" #name ":nargchk", \
"Bad number of args: %d <= %d <= %d", \
min, nrhs, max); } \
do body while (0); \
PYMEX_DEBUG("<end " #name ">\n"); \
}
#define PYMEX_MAKECELL(name, min, max, doc, body) \
mxSetCell(plhs[0], PYMEX_CMD_##name, mxCreateString(#name));
#define PYMEX_GETDOC(name, min, max, doc, body)\
else if (!strcmp(#name, helpname)) { \
mxFree(helpname); \
plhs[0] = mxCreateString(doc); \
}
#define PYMEX_STRCMP(name, min, max, doc, body) \
else if (!strcmp(#name, cmdstring)) { \
mxFree(cmdstring); \
name##_pymexfun(nlhs, plhs, nrhs-1, prhs+1); \
}
#define PYMEX_ENUM(name, min, max, doc, body) \
PYMEX_CMD_##name,
#define PYMEX_CASE(name, min, max, doc, body) \
case PYMEX_CMD_##name: \
name##_pymexfun(nlhs, plhs, nrhs-1, prhs+1); \
break;
/* Define pymex command enums via x-macro */
#define PYMEX(name, min, max, doc, body) PYMEX_ENUM(name,min,max,doc,body)
enum PYMEX_COMMAND {
#include XMACRO_DEFS
NUMBER_OF_PYMEX_COMMANDS,
};
#undef PYMEX
/* Define pymex commands via x-macro */
#define PYMEX(name, min, max, doc, body) PYMEX_DEFINE(name,min,max,doc,body)
#include XMACRO_DEFS
#undef PYMEX
/* mex body and related functions */
static void ExitFcn(void) {
Py_Finalize();
PYMEX_DEBUG("[python: finalized]\n");
}
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
if (!Py_IsInitialized()) {
/*
This dlopen is currently needed because I have
recently been unable to import python shared
library modules (like, say, numpy). I spent half
a day staring at ld's manpage and trying various
combinations to no avail. I even managed to compile
against the static libpython2.6-pic.a and could
verify the presence and globalness of the symbols,
but could not determine a way to get the loaded
libraries to actually use them.
Should probably make this line configurable in the
makefile... later.
*/
dlopen("libpython2.6.so", RTLD_LAZY | RTLD_GLOBAL);
Py_Initialize();
PYMEX_DEBUG("[python: initialized]\n");
initmexmodule();
initmxmodule();
initmatmodule();
initengmodule();
mexAtExit(ExitFcn);
mexLock(); /* See Issue #3 */
}
if (nrhs < 1 || mxIsEmpty(prhs[0])) {
if (nlhs == 1) {
plhs[0] = mxCreateCellMatrix(1,NUMBER_OF_PYMEX_COMMANDS+1);
#define PYMEX(name,min,max,doc,body) PYMEX_MAKECELL(name,min,max,doc,body)
#include XMACRO_DEFS
#undef PYMEX
mxSetCell(plhs[0], NUMBER_OF_PYMEX_COMMANDS, mxCreateString("help"));
}
else {
mexEvalStringWithTrap("help('pymex.m')");
}
}
else if (mxIsNumeric(prhs[0])) {
enum PYMEX_COMMAND cmd = (int) mxGetScalar(prhs[0]);
/* While the numeric command selector is no longer used
for various reasons, I have left it in since it is
faster than the string-based command selector and
could theoretically be useful in the future.
*/
/* Switch body defined via x-macro expansion */
switch (cmd) {
#define PYMEX(name,min,max,doc,body) PYMEX_CASE(name,min,max,doc,body)
#include XMACRO_DEFS
#undef PYMEX
default:
mexErrMsgIdAndTxt("pymex:NotImplemented",
"pymex command %d not implemented", cmd);
}
}
else if (mxIsChar(prhs[0])) {
char *cmdstring = mxArrayToString(prhs[0]);
if (!cmdstring) {
mexErrMsgIdAndTxt("pymex:badstring",
"Could not extract the command string.");
}
else if (!strcmp("help", cmdstring)) {
if (nrhs < 2 || !mxIsChar(prhs[1])) {
mexErrMsgIdAndTxt("pymex:nohelp",
"Please specify a PYMEX command to get help for it.");
}
char *helpname = mxArrayToString(prhs[1]);
if (!helpname) {
mexErrMsgIdAndTxt("pymex:badstring",
"Could not extract the command string.");
}
else if (!strcmp(helpname, "help")) {
plhs[0] = mxCreateString("Given the name of another PYMEX command, "
"produces its docstring.");
}
#define PYMEX(name,min,max,doc,body) PYMEX_GETDOC(name,min,max,doc,body)
#include XMACRO_DEFS
#undef PYMEX
else {
mexErrMsgIdAndTxt("pymex:nohelp",
"No command '%s' found. Commands are case sensitive.",
helpname);
}
mxFree(cmdstring);
}
/* a bunch of else-ifs are generated here */
#define PYMEX(name,min,max,doc,body) PYMEX_STRCMP(name,min,max,doc,body)
#include XMACRO_DEFS
#undef PYMEX
else {
mexErrMsgIdAndTxt("pymex:NotImplemented",
"pymex command '%s' not implemented", cmdstring);
/* mxFree(cmdstring) */
/* We'd like to free it, but this won't run.
Hopefully MATLAB's magic memory manager
will handle this for us. If not, this shouldn't
be a memory leak. Unless someone decides
to write code that calls pymex directly, misspells
the command string, catchs and ignores
the error, and then somehow doesn't notice that
whatever they're doing isn't working.
*/
}
}
else {
mexErrMsgIdAndTxt("pymex:badcmd",
"I don't really know what to do with a %s",
mxGetClassName(prhs[0]));
}
/* Detect and pass on python errors */
PyObject *err = PyErr_Occurred();
if (err) {
PyObject *err_type, *err_value, *err_traceback;
PyErr_Fetch(&err_type, &err_value, &err_traceback);
if (!err_value)
err_value = PyUnicode_FromString("<no value>");
/* TODO: This seems a tad overcomplicated for some
simple string concatentaion. */
PyObject *pyid = PyUnicode_FromString("Python:");
PyObject *errname = PyObject_GetAttrString(err_type, "__name__");
PyObject *msgid = PyUnicode_Concat(pyid, errname);
Py_DECREF(pyid);
Py_DECREF(errname);
PyObject *tuple = Py_BuildValue("ON", msgid, PyObject_Str(err_value));
PyObject *format = PyUnicode_FromString("%s -> %s\n");
PyObject *pymsg = PyUnicode_Format(format, tuple);
PyObject *b_id = PyUnicode_AsASCIIString(msgid);
PyObject *b_msg = PyUnicode_AsASCIIString(pymsg);
char *id = PyBytes_AsString(b_id);
char *msg = PyBytes_AsString(b_msg);
Py_DECREF(b_id);
Py_DECREF(b_msg);
Py_DECREF(msgid);
Py_DECREF(tuple);
Py_DECREF(format);
Py_DECREF(pymsg);
PyErr_Clear();
mexErrMsgIdAndTxt(id, msg);
}
}