Use another language to call a function: Difference between revisions
Use another language to call a function (view source)
Revision as of 18:54, 13 June 2017
, 7 years ago→{{header|TXR}}: Remove first solution, improve carray solution, put callback discussion at end.
(→Using cptr and memcpy: Show C side-by-side.) |
(→{{header|TXR}}: Remove first solution, improve carray solution, put callback discussion at end.) |
||
Line 1,067:
=={{header|TXR}}==
This is really two tasks: how to accept foreign callbacks, and how to link code to a C program which controls the <code>main</code> startup function.
Line 1,097 ⟶ 1,095:
gcc -g --shared query.c -o query.c</lang>
In this situation, the most appropriate FFI type to use for the foreign buffer is the <code>carray</code> type. This type allows TXR Lisp code to manipulate a foreign array while retaining its identity, so that it is able to pass the same pointer to the foreign code that it received from that code. <code>carray</code> also solves the problem of dealing with the common representational approach in C when arrays are represented by pointers, and do not include their size as part of their type information. A <code>carray</code> object can be constructed with an zero size, which can be adjusted when the size is known, using <code>carray-set-length</code>.
Like the <code>array</code> type, <code>carray</code> has specialized behaviors when its element type is <code>char</code>, <code>bchar</code> or <code>wchar</code>. The <code>carray-get</code> function will decode a string from the underlying array, and <code>carray-put</code> will encode a string into the array. In the case of the <code>char</code> type, this involves UTF-8 coding.
Callbacks are modeled as "FFI closures". The macro <code>deffi-cb</code> defines a function which itself isn't a callback, but is rather a combinator which converts a Lisp function into a FFI callback.
<lang txrlisp>(with-dyn-lib "./query.so"
(deffi query "query" void (closure)))
(deffi-cb query-cb int ((
(query (query-cb (lambda (
(symacrolet ((size [sizeptr 0]))
(let* ((s "Here am I")
Line 1,112 ⟶ 1,114:
(cond
((> l size) 0)
(t (carray-set-length
(set size l))))))))</lang>
Line 1,121 ⟶ 1,124:
Note that the obvious way of passing a <code>size_t</code> value by pointer, namely <code>(ptr size-t)</code> doesn't work. While the callback will receive the size (FFI will decode the pointer type's semantics and get the size value), updating the size will not propagate back to the caller, because it becomes, effectively, a by-value parameter. A <code>(ptr size-t)</code> object has to be embedded in an aggregate that is passed by reference, in order to have two-way semantics. Here we use the trick of treating the <code>size_t *</code> as an array of 1, which it ''de facto'' is. In the callback, we establish local symbol macro which lets us just refer to <code>[sizeptr 0]</code> it as <code>size</code>.
=== Using <code>cptr</code> and <code>memcpy</code> ===▼
▲=== Using <code>carray</code> ===
<lang txrlisp>(with-dyn-lib "./query.so" ▼
(deffi query "query" void (closure))) ▼
(with-dyn-lib nil ▼
(deffi memcpy "memcpy" cptr (cptr str size-t))) ▼
(deffi-cb query-cb int (cptr (ptr (array 1 size-t)))) ▼
(query (query-cb (lambda (buf sizeptr) ; int lambda(void *buf, size_t *sizeptr)▼
(symacrolet ((size [sizeptr 0])) ; { #define size sizeptr[0]▼
(let* ((s "Here am I") ; char *s = "Here am I";▼
(l (length s))) ; size_t l = strlen(s);▼
((> l size) 0) ; { return 0; } else▼
(t (memcpy buf s l) ; { memcpy(buf, s, l);▼
(set size l)))))))) ; return size = l; } }</lang>▼
Here, the use of the <code>str</code> type in the <code>memcpy</code> interface means that FFI automatically produces a UTF-8 encoding of the string in a temporary buffer. The pointer to that temporary buffer is what is passed into <code>memcpy</code>. The temporary buffer is released after <code>memcpy</code> returns.▼
To reveal the similarity between the Lisp logic and how a C function might be written, the corresponding C code is shown.▼
However, that C code's semantics is, of course, devoid of any hidden UTF-8 conversion.▼
===Exceptions from Callback===
(deffi-cb query-cb int ((carray char) (ptr (array 1 size-t))))▼
If the callback throws an exception or performs any other non-local return, it will return a default return value of all zero bits in the given return type. This value can be specified, but the zero default suits our particular situation
▲ (l (length s)))
▲ ((> l size) 0)
We can explore this interactively:
▲If the callback throws an exception or performs any other non-local return, it will return a default return value of all zero bits in the given return type. This value can be specified, but the zero default suits our particular situation:
<pre>$ txr
Line 1,165 ⟶ 1,172:
Here we can see that when the callback throws the <code>error</code> exception, the C code prints <code>query: callback failed</code>, due to receiving the default abort return value of zero. Then, the exception continues up to the interactive prompt.
If a return value other than zero indicates that the callback failed, that can be arranged with an additional argument in <code>deffi-cb</code>:
▲=== Using <code>cptr</code> and <code>memcpy</code> ===
▲A more succinct approach is possible if we avail ourselves of the <code>memcpy</code> function via FFI. We can receive the data as an opaque foreign pointer represented by the <code>cptr</code> type. We can set up <code>memcpy</code> so that its destination argument and return value is a <code>cptr</code>, but the source argument is a string:
Now the <code>query-cb</code> function generates callbacks that return -1 to the caller, rather than zero, if aborted by a non-local control transfer such as an exception.
▲<lang txrlisp>(with-dyn-lib "./query.so"
▲ (deffi query "query" void (closure)))
▲(with-dyn-lib nil
▲ (deffi memcpy "memcpy" cptr (cptr str size-t)))
▲(deffi-cb query-cb int (cptr (ptr (array 1 size-t))))
▲(query (query-cb (lambda (buf sizeptr) ; int lambda(void *buf, size_t *sizeptr)
▲ (symacrolet ((size [sizeptr 0])) ; { #define size sizeptr[0]
▲ (let* ((s "Here am I") ; char *s = "Here am I";
▲ (l (length s))) ; size_t l = strlen(s);
▲ (cond ; if (length > size)
▲ ((> l size) 0) ; { return 0; } else
▲ (t (memcpy buf s l) ; { memcpy(buf, s, l);
▲ (set size l)))))))) ; return size = l; } }</lang>
▲Here, the use of the <code>str</code> type in the <code>memcpy</code> interface means that FFI automatically produces a UTF-8 encoding of the string in a temporary buffer. The pointer to that temporary buffer is what is passed into <code>memcpy</code>. The temporary buffer is released after <code>memcpy</code> returns.
▲To reveal the similarity between the Lisp logic and how a C function might be written, the corresponding C code is shown.
▲However, that C code's semantics is, of course, devoid of any hidden UTF-8 conversion.
=={{header|zkl}}==
|