1/*
2 * Special wrappers for pointer values
3 *
4 * Pointer arguments to methods can be split into 3 classes:
5 * - Pass-by-reference arguments (in, out, inout).
6 *   This is supported by using the modifiers _C_IN, _C_OUT and _C_INOUT
7 * - Pointers to buffers
8 *   This requires special support, you can't detect the size of the buffer
9 *   from the signature
10 * - Opaque values
11 *   Types like FSRef are for all intents and purposes opaque values,
12 *   that happen to be represented using pointers (to structs). The functions
13 *   in this file allow extension modules to register functions to convert these
14 *   values to and from their Python representation.
15 *
16 * NOTE:
17 * - The pythonify and depythonify functions have the same interface as
18 *   the *_New and *_Convert functions in MacPython (pymactoolbox.h), this
19 *   makes it easier to interface with that packages.
20 */
21#include "pyobjc.h"
22
23
24#import <CoreFoundation/CoreFoundation.h>
25
26struct wrapper {
27	const char* signature;
28	int offset;
29	PyObject* (*pythonify)(void*);
30	int (*depythonify)(PyObject*, void*);
31};
32
33/* Using an array is pretty lame, this needs to be replaced by a more
34 * efficient datastructure. However: As long as their is only a limited
35 * number of custom wrappers this should not be a problem.
36 */
37static struct wrapper* items = 0;
38static Py_ssize_t item_count = 0;
39
40/*
41 * If signature is a pointer to a structure return the index of the character
42 * just beyond the end of the struct name. This information is needed because
43 * @encode(struct foo*) can return two different strings:
44 * 1) ^{foo} if the compiler has not yet seen a full definition of struct foo
45 * 2) ^{foo=...} if the compiler has not yet seen a full definition of
46 *    the struct
47 * We want to treat those two pointer as the same type, therefore we need to
48 * ignore everything beyond the end of the struct name.
49 */
50static int find_end_of_structname(const char* signature) {
51	if (signature[1] == _C_CONST && signature[2] == _C_STRUCT_B) {
52		char* end1;
53		char* end2;
54
55		end1 = strchr(signature, _C_STRUCT_E);
56		end2 = strchr(signature, '=');
57
58		if (end2 == NULL) {
59			return end1 - signature;
60		} else {
61			return end2 - signature;
62		}
63
64	} else if (signature[1] == _C_STRUCT_B) {
65		char* end1;
66		char* end2;
67
68		end1 = strchr(signature, _C_STRUCT_E);
69		end2 = strchr(signature, '=');
70
71		if (end2 == NULL) {
72			return end1 - signature;
73		} else {
74			return end2 - signature;
75		}
76	}
77	return strlen(signature);
78}
79
80static struct wrapper*
81FindWrapper(const char* signature)
82{
83	Py_ssize_t i;
84
85	for (i = 0; i < item_count; i++) {
86		if (strncmp(signature, items[i].signature, items[i].offset) == 0) {
87			/* See comment just above find_end_of_structname */
88			if (signature[1] == _C_CONST && signature[2] == _C_STRUCT_B) {
89				char ch = signature[items[i].offset];
90				if (ch == '=' || ch == _C_STRUCT_E) {
91					return items + i;
92				}
93
94			} else if (signature[1] == _C_STRUCT_B) {
95				char ch = signature[items[i].offset];
96				if (ch == '=' || ch == _C_STRUCT_E) {
97					return items + i;
98				}
99
100			} else {
101				if (signature[items[i].offset] == '\0') {
102					return items + i;
103				}
104			}
105		}
106	}
107	return NULL;
108}
109
110static PyObject*
111ID_to_py(void* idValue)
112{
113	return pythonify_c_value(@encode(id), &idValue);
114}
115
116static int
117py_to_ID(PyObject* obj, void* output)
118{
119	return depythonify_c_value(@encode(id), obj, output);
120}
121
122int PyObjCPointerWrapper_RegisterID(const char *signature) {
123	return PyObjCPointerWrapper_Register(signature,
124		(PyObjCPointerWrapper_ToPythonFunc)&ID_to_py,
125		(PyObjCPointerWrapper_FromPythonFunc)&py_to_ID);
126}
127
128int
129PyObjCPointerWrapper_Register(
130	const char* signature,
131	PyObjCPointerWrapper_ToPythonFunc pythonify,
132	PyObjCPointerWrapper_FromPythonFunc depythonify
133
134	)
135{
136	struct wrapper* value;
137
138	/*
139	 * Check if we already have a wrapper, if so replace that.
140	 * This makes it possible to replace a default wrapper by something
141	 * better.
142	 */
143	if (signature == NULL) {
144		return -1;
145	}
146	value = FindWrapper(signature);
147	if (value != NULL) {
148		value->pythonify = pythonify;
149		value->depythonify = depythonify;
150		return 0;
151	}
152
153	if (items == NULL) {
154		items = PyMem_Malloc(sizeof(struct wrapper));
155		if (items == NULL) {
156			PyErr_NoMemory();
157			return -1;
158		}
159		item_count = 1;
160	} else {
161		struct wrapper* tmp;
162
163		tmp = PyMem_Realloc(
164			items, sizeof(struct wrapper) *  (item_count+1));
165		if (tmp == NULL) {
166			PyErr_NoMemory();
167			return -1;
168		}
169		items = tmp;
170		item_count ++;
171	}
172
173	value = items + (item_count-1);
174
175	value->signature = PyObjCUtil_Strdup(signature);
176	if (value->signature == NULL) {
177		PyErr_NoMemory();
178		item_count --;
179		return -1;
180	}
181
182	value->offset = find_end_of_structname(value->signature);
183
184	value->pythonify = pythonify;
185	value->depythonify = depythonify;
186
187	return 0;
188}
189
190
191
192PyObject*
193PyObjCPointerWrapper_ToPython(const char* type, void* datum)
194{
195	struct wrapper* item;
196	PyObject* result;
197
198	item = FindWrapper(type);
199	if (item == NULL) {
200		return NULL;
201	}
202
203	if (item->pythonify == ID_to_py) {
204		result = PyObjC_FindPythonProxy(*(id*)datum);
205		if (result != NULL) {
206			return result;
207
208		} else if (*(void**)datum == kCFAllocatorUseContext) {
209			/* kCFAllocatorUseContext is a bit too magic for its
210			 * own good.
211			 *
212			 * Note that this is a crude hack, but as long as this
213			 * is the only such object I don't think its worthwhile
214			 * to add generic support for this.
215			 */
216			result = PyObjCCF_NewSpecial2(
217				CFAllocatorGetTypeID(), *(void**)datum);
218
219			PyObjC_RegisterPythonProxy(*(id*)datum, result);
220			return result;
221		}
222	}
223
224	result = item->pythonify(*(void**)datum);
225	return result;
226}
227
228
229int
230PyObjCPointerWrapper_FromPython(
231	const char* type, PyObject* value, void* datum)
232{
233	struct wrapper* item;
234	int r;
235
236	if (value == PyObjC_NULL) {
237		*(void**)datum = NULL;
238		return 0;
239	}
240
241	item = FindWrapper(type);
242	if (item == NULL) {
243		return -1;
244	}
245
246	r = item->depythonify(value, datum);
247	if (r == 0) {
248		return 0;
249	} else {
250		return -1;
251	}
252}
253
254int PyObjCPointerWrapper_HaveWrapper(const char* type)
255{
256	return (FindWrapper(type) != NULL);
257}
258
259
260static PyObject*
261PyObjectPtr_New(void *obj)
262{
263	return (PyObject*)obj;
264}
265
266static int
267PyObjectPtr_Convert(PyObject* obj, void* pObj)
268{
269	*(void**)pObj = (void *)obj;
270	return 0;
271}
272
273static PyObject*
274class_new(void *obj)
275{
276	return pythonify_c_value("#", obj);
277}
278
279static int
280class_convert(PyObject* obj, void* pObj)
281{
282	return depythonify_c_value("#", obj, pObj);
283}
284
285
286#if PY_MAJOR_VERSION == 2
287
288static int dontClose(FILE* fp __attribute__((__unused__)))
289{
290	return 0;
291}
292static PyObject*
293FILE_New(void *obj)
294{
295	FILE* fp = (FILE*)obj;
296	char* mode = "r";
297
298#if defined(__SRW)
299	/* This is a hack, but allows us to pass the right file mode into
300	 * Python.
301	 */
302	if (fp->_flags & __SWR) {
303		mode = "w";
304	} else if (fp->_flags & __SRW) {
305		mode = "w+";
306	}
307
308#endif
309	return PyFile_FromFile(fp, "<objc-file>", mode, dontClose);
310}
311
312static int
313FILE_Convert(PyObject* obj, void* pObj)
314{
315	*(FILE**)pObj = PyFile_AsFile(obj);
316	if (*(FILE**)pObj == NULL) {
317		return 1;
318	}
319
320	return 0;
321}
322
323#endif
324
325/*
326 * Generic CF type support
327 */
328static PyObject*
329CF_to_py(void* cfValue)
330{
331	return PyObjC_IDToCFType((id)cfValue);
332}
333
334static int
335py_to_CF(PyObject* obj, void* output)
336{
337	id tmp = PyObjC_CFTypeToID(obj);
338	if (tmp == NULL && PyErr_Occurred()) {
339		return -1;
340	}
341	*(void**)output = tmp;
342	return 0;
343}
344
345int PyObjCPointerWrapper_RegisterCF(const char *signature) {
346	return PyObjCPointerWrapper_Register(signature,
347		(PyObjCPointerWrapper_ToPythonFunc)&CF_to_py,
348		(PyObjCPointerWrapper_FromPythonFunc)&py_to_CF);
349}
350
351
352int
353PyObjCPointerWrapper_Init(void)
354{
355	int r = 0;
356
357	r = PyObjCPointerWrapper_RegisterCF(@encode(CFURLRef));
358	if (r == -1) return -1;
359
360	r = PyObjCPointerWrapper_RegisterCF(@encode(CFSetRef));
361	if (r == -1) return -1;
362
363#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_2
364	r = PyObjCPointerWrapper_RegisterCF(@encode(CFNetServiceRef));
365	if (r == -1) return -1;
366#endif
367
368	r = PyObjCPointerWrapper_RegisterCF(@encode(CFReadStreamRef));
369	if (r == -1) return -1;
370
371	r = PyObjCPointerWrapper_RegisterCF(@encode(CFRunLoopRef));
372	if (r == -1) return -1;
373
374	r = PyObjCPointerWrapper_Register(@encode(PyObject*),
375		PyObjectPtr_New, PyObjectPtr_Convert);
376	if (r == -1) return -1;
377
378	r = PyObjCPointerWrapper_Register("^{objc_class=}",
379		class_new, class_convert);
380	if (r == -1) return -1;
381
382
383#if PY_MAJOR_VERSION == 2
384	r = PyObjCPointerWrapper_Register(@encode(FILE*),
385		FILE_New, FILE_Convert);
386	if (r == -1) return -1;
387#endif
388
389	return 0;
390}
391