1 |
/* ASCEND modelling environment |
2 |
Copyright (C) 2006 Carnegie Mellon University |
3 |
|
4 |
This program is free software; you can redistribute it and/or modify |
5 |
it under the terms of the GNU General Public License as published by |
6 |
the Free Software Foundation; either version 2, or (at your option) |
7 |
any later version. |
8 |
|
9 |
This program is distributed in the hope that it will be useful, |
10 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 |
GNU General Public License for more details. |
13 |
|
14 |
You should have received a copy of the GNU General Public License |
15 |
along with this program; if not, write to the Free Software |
16 |
Foundation, Inc., 59 Temple Place - Suite 330, |
17 |
Boston, MA 02111-1307, USA. |
18 |
*//** |
19 |
@file |
20 |
Import handler to provide external python script functionality for ASCEND. |
21 |
*//* |
22 |
by John Pye, Oct 2006 |
23 |
*/ |
24 |
|
25 |
#include <Python.h> |
26 |
|
27 |
#include <stdio.h> |
28 |
#include <string.h> |
29 |
|
30 |
#include <utilities/ascConfig.h> |
31 |
#include <utilities/error.h> |
32 |
#include <general/ospath.h> |
33 |
|
34 |
#include <compiler/importhandler.h> |
35 |
#include <compiler/extfunc.h> |
36 |
|
37 |
|
38 |
ImportHandlerCreateFilenameFn extpy_filename; |
39 |
ImportHandlerImportFn extpy_import; |
40 |
|
41 |
ExtMethodDestroyFn extpy_destroy; |
42 |
|
43 |
|
44 |
#ifndef ASC_EXPORT |
45 |
# error "Where is ASC_EXPORT?" |
46 |
#endif |
47 |
|
48 |
struct ExtPyData{ |
49 |
PyObject *fn; |
50 |
char *name; |
51 |
}; |
52 |
|
53 |
/** |
54 |
This is the function called from "IMPORT extpy" |
55 |
|
56 |
It sets up the functions in this external function library |
57 |
*/ |
58 |
extern ASC_EXPORT(int) extpy_register(){ |
59 |
int result = 0; |
60 |
|
61 |
struct ImportHandler *handler; |
62 |
handler = ASC_NEW(struct ImportHandler); |
63 |
|
64 |
handler->name = "extpy"; |
65 |
handler->filenamefn = &extpy_filename; |
66 |
handler->importfn = &extpy_import; |
67 |
|
68 |
result = importhandler_add(handler); |
69 |
|
70 |
if(result){ |
71 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed to register import handler (error = %d)",result); |
72 |
} |
73 |
return result; |
74 |
} |
75 |
|
76 |
/*------------------------------------------------------------------------------ |
77 |
PYTHON METHOD INVOKER |
78 |
*/ |
79 |
|
80 |
ExtMethodRun extpy_invokemethod; |
81 |
|
82 |
/** Method invoker. extpy will supply a pointer to this function whenever it |
83 |
registers a python function as an external script method. This function will |
84 |
then dereference the user_data field into a python function, and execute that |
85 |
python function. |
86 |
|
87 |
One difficult aspect is the question of how to usefully pass the 'context' |
88 |
argument to Python? |
89 |
*/ |
90 |
int extpy_invokemethod(struct Instance *context, struct gl_list_t *args, void *user_data){ |
91 |
PyObject *arglist, *result; |
92 |
struct ExtPyData *extpydata; |
93 |
/* cast user data to PyObject pointer */ |
94 |
|
95 |
/* CONSOLE_DEBUG("USER_DATA IS AT %p",user_data); */ |
96 |
|
97 |
extpydata = (struct ExtPyData *) user_data; |
98 |
|
99 |
/* ERROR_REPORTER_HERE(ASC_USER_NOTE,"RUNNING PYTHON METHOD"); */ |
100 |
/* CONSOLE_DEBUG("RUNNING PYTHON METHOD..."); */ |
101 |
|
102 |
CONSOLE_DEBUG("Running python method '%s'",extpydata->name); |
103 |
|
104 |
PyErr_Clear(); |
105 |
|
106 |
/* CONSOLE_DEBUG("PyObject 'fn' is at %p",extpydata->fn); */ |
107 |
|
108 |
if(!PyCallable_Check(extpydata->fn)){ |
109 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"user_data is not a PyCallable"); |
110 |
return 1; |
111 |
} |
112 |
|
113 |
/* |
114 |
We need to be able to convert C 'struct Instance' pointers to Python 'Instance' objects. |
115 |
This functionality is implemented in 'ascpy' but we're not going to link to that here, |
116 |
so we will use the importhandler 'setsharedpointer' trick to pass the object to the |
117 |
'registry' then write a routine in ascpy that will cast it into the appropriate |
118 |
Python object. |
119 |
*/ |
120 |
importhandler_setsharedpointer("context",(void *)context); |
121 |
|
122 |
/* |
123 |
Eventually we'll work out how to pass the instance object directly into Python. For |
124 |
the moment, just have a placeholder variable instead: |
125 |
*/ |
126 |
arglist = Py_BuildValue("(i)", 666); |
127 |
|
128 |
/* CONSOLE_DEBUG("CALLING PYTHON"); */ |
129 |
result = PyEval_CallObject(extpydata->fn, arglist); |
130 |
/* CONSOLE_DEBUG("DONE CALLING PYTHON"); */ |
131 |
Py_DECREF(arglist); |
132 |
|
133 |
if(PyErr_Occurred()){ |
134 |
/** @TODO we really want to capture these error messages and output them to the GUI, instead of outputting them to the console */ |
135 |
PyErr_Print(); |
136 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed running python method (see console)"); |
137 |
return 1; |
138 |
} |
139 |
|
140 |
return 0; |
141 |
} |
142 |
|
143 |
/** |
144 |
Free memory associated with a registered script method. |
145 |
@return 0 on success |
146 |
*/ |
147 |
int extpy_destroy(void *user_data){ |
148 |
struct ExtPyData *extpydata; |
149 |
extpydata = (struct ExtPyData *)user_data; |
150 |
Py_DECREF(extpydata->fn); |
151 |
ASC_FREE(extpydata->name); |
152 |
ASC_FREE(extpydata); |
153 |
return 0; |
154 |
} |
155 |
|
156 |
/*------------------------------------------------------------------------------ |
157 |
'EXTPY' PYTHON STATIC MODULE |
158 |
*/ |
159 |
|
160 |
static PyObject *extpy_getbrowser(PyObject *self, PyObject *args){ |
161 |
PyObject *browser; |
162 |
if(args!=NULL){ |
163 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"args is not NULL?!"); |
164 |
} |
165 |
browser = (PyObject *)importhandler_getsharedpointer("browser"); |
166 |
if(browser==NULL){ |
167 |
return Py_None; |
168 |
} |
169 |
return Py_BuildValue("O",browser); |
170 |
} |
171 |
|
172 |
static PyObject *extpy_registermethod(PyObject *self, PyObject *args){ |
173 |
PyObject *fn, *name, *docstring; |
174 |
const char *cname, *cdocstring; |
175 |
int res; |
176 |
int nargs = 1; |
177 |
struct ExtPyData *extpydata; |
178 |
|
179 |
PyArg_ParseTuple(args,"O:registermethod", &fn); |
180 |
if(!PyCallable_Check(fn)){ |
181 |
PyErr_SetString(PyExc_TypeError,"parameter must be callable"); |
182 |
return NULL; |
183 |
} |
184 |
|
185 |
/* CONSOLE_DEBUG("FOUND FN=%p",fn); */ |
186 |
|
187 |
name = PyObject_GetAttr(fn,PyString_FromString("__name__")); |
188 |
if(name==NULL){ |
189 |
CONSOLE_DEBUG("No __name__ attribute"); |
190 |
PyErr_SetString(PyExc_TypeError,"No __name__ attribute"); |
191 |
return NULL; |
192 |
} |
193 |
cname = PyString_AsString(name); |
194 |
|
195 |
/* CONSOLE_DEBUG("REGISTERED METHOD '%s' HAS %d ARGS",cname,nargs); */ |
196 |
|
197 |
docstring = PyObject_GetAttr(fn,PyString_FromString("func_doc")); |
198 |
cdocstring = "(no help)"; |
199 |
if(name!=NULL){ |
200 |
cdocstring = PyString_AsString(docstring); |
201 |
CONSOLE_DEBUG("DOCSTRING: %s",cdocstring); |
202 |
} |
203 |
|
204 |
extpydata = ASC_NEW(struct ExtPyData); |
205 |
extpydata->name = ASC_NEW_ARRAY(char,strlen(cname)+1); |
206 |
extpydata->fn = fn; |
207 |
strcpy(extpydata->name, cname); |
208 |
|
209 |
res = CreateUserFunctionMethod(cname,&extpy_invokemethod,nargs,cdocstring,(void *)extpydata,&extpy_destroy); |
210 |
Py_INCREF(fn); |
211 |
|
212 |
/* CONSOLE_DEBUG("EXTPY 'fn' IS AT %p",fn); */ |
213 |
|
214 |
/* CONSOLE_DEBUG("EXTPY INVOKER IS AT %p",extpy_invokemethod); */ |
215 |
|
216 |
if(res){ |
217 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Problem registering external script method (%d)",res); |
218 |
PyErr_SetString(PyExc_Exception,"unable to register script method"); |
219 |
return NULL; |
220 |
} |
221 |
|
222 |
ERROR_REPORTER_HERE(ASC_PROG_NOTE,"Registered python method '%s'\n",cname); |
223 |
|
224 |
/* nothing gets returned (but possibly an exception) */ |
225 |
Py_INCREF(Py_None); |
226 |
return Py_None; |
227 |
} |
228 |
|
229 |
static PyMethodDef extpymethods[] = { |
230 |
{"getbrowser", extpy_getbrowser, METH_NOARGS,"Retrieve browser pointer"} |
231 |
,{"registermethod", extpy_registermethod, METH_VARARGS,"Register a python method as an ASCEND script method"} |
232 |
,{NULL,NULL,0,NULL} |
233 |
}; |
234 |
|
235 |
PyMODINIT_FUNC initextpy(void){ |
236 |
PyObject *obj; |
237 |
obj = Py_InitModule3("extpy", extpymethods,"Module for accessing shared ASCEND pointers from python"); |
238 |
} |
239 |
|
240 |
/*------------------------------------------------------------------------------ |
241 |
STANDARD IMPORT HANDLER ROUTINES |
242 |
*/ |
243 |
|
244 |
/** |
245 |
Create a filename base on a partial filename. In that case of python, this |
246 |
just means adding '.py' to the end. |
247 |
|
248 |
@param partialname the filename without suffix, as specified in the user's "IMPORT" command |
249 |
@return new filename, or NULL on failure |
250 |
*/ |
251 |
char *extpy_filename(const char *partialname){ |
252 |
char *name; |
253 |
int len; |
254 |
if(partialname==NULL){ |
255 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Partial name is NULL, can't work out filename"); |
256 |
return NULL; |
257 |
} |
258 |
|
259 |
len = strlen(partialname); |
260 |
name = ASC_NEW_ARRAY_CLEAR(char,len+4); |
261 |
strcpy(name,partialname); |
262 |
strcat(name,".py"); |
263 |
CONSOLE_DEBUG("New filename is '%s'",name); |
264 |
return name; |
265 |
} |
266 |
|
267 |
/** |
268 |
Import a python script located at the path indicated. |
269 |
|
270 |
@return 0 on success, else error codes (TBD) |
271 |
*/ |
272 |
int extpy_import(const struct FilePath *fp, const char *initfunc, const char *partialpath){ |
273 |
char *name; |
274 |
name = ospath_str(fp); |
275 |
FILE *f; |
276 |
PyObject *pyfile; |
277 |
int iserr; |
278 |
|
279 |
CONSOLE_DEBUG("Importing Python script %s",name); |
280 |
if(Py_IsInitialized()){ |
281 |
CONSOLE_DEBUG("Python was already initialised"); |
282 |
}else{ |
283 |
CONSOLE_DEBUG("INITIALISING PYTHON"); |
284 |
Py_Initialize(); |
285 |
CONSOLE_DEBUG("COMPLETED ATTEMPT TO INITIALISE PYTHON"); |
286 |
} |
287 |
|
288 |
if(!Py_IsInitialized()){ |
289 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to initialise Python"); |
290 |
CONSOLE_DEBUG("UNABLE TO INITIALIZE PYTHON"); |
291 |
ASC_FREE(name); |
292 |
return 1; |
293 |
} |
294 |
|
295 |
initextpy(); |
296 |
|
297 |
if(PyRun_SimpleString("import ascpy")){ |
298 |
CONSOLE_DEBUG("Failed importing 'ascpy'"); |
299 |
return 1; |
300 |
} |
301 |
|
302 |
pyfile = PyFile_FromString(name,"r"); |
303 |
if(pyfile==NULL){ |
304 |
CONSOLE_DEBUG("Failed opening script"); |
305 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to open '%s' (%s)",partialpath,name); |
306 |
ASC_FREE(name); |
307 |
return 1; |
308 |
} |
309 |
|
310 |
f = PyFile_AsFile(pyfile); |
311 |
if(f==NULL){ |
312 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to cast PyObject to FILE*"); |
313 |
ASC_FREE(name); |
314 |
return 1; |
315 |
} |
316 |
|
317 |
PyErr_Clear(); |
318 |
|
319 |
iserr = PyRun_AnyFileEx(f,name,1); |
320 |
|
321 |
if(iserr){ |
322 |
/* according to the manual, there is no way of determining anything more about the error. */ |
323 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"An error occurring in importing the script '%s'",name); |
324 |
return 1; |
325 |
} |
326 |
|
327 |
ERROR_REPORTER_HERE(ASC_PROG_NOTE,"Imported python script '%s'",partialpath); |
328 |
|
329 |
ASC_FREE(name); |
330 |
return 0; |
331 |
} |
332 |
|