Skip to content

Commit 7cd0a88

Browse files
Merge pull request livecode#2423 from runrevmark/lcb-native_callback
[[ ForeignCallbacks ]] Implement basic version of C callable LCB clos…
2 parents 2801490 + c4ddfe8 commit 7cd0a88

22 files changed

Lines changed: 735 additions & 177 deletions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# LiveCode Builder Language
2+
3+
## Foreign Handler Types
4+
5+
It is now possible declare foreign handler types:
6+
7+
foreign handler type MyCallback(in pContext as optional pointer, in pValue as any) as CBool
8+
9+
When used in the context of a foreign handler definition, a foreign handler type
10+
will cause automatic bridging of the LCB handler to a C function pointer which
11+
can be called directly by the native code.
12+
13+
The function pointers created in this fashion have lifetime equivalent to that
14+
of the calling context. In particular, for widgets they will last as long as the
15+
widget does, for all other module types they will last as long as the module is
16+
loaded.

docs/specs/livecode_builder_language_reference.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ A module may use any other module, as long as doing so does not cause a cycle in
130130
Definition
131131
: ( 'public' | 'private' ) ConstantDefinition
132132
| ( 'public' | 'private' ) TypeDefinition
133+
| ( 'public' | 'private' ) HandlerTypeDefinition
133134
| ( 'public' | 'private' ) VariableDefinition
134135
| ( 'public' | 'private' ) HandlerDefinition
135136
| ( 'public' | 'private' ) ForeignHandlerDefinition
@@ -201,6 +202,23 @@ The remaining types are as follows:
201202
202203
> **Note:** In a subsequence update you will be able to define record types (named collections of values - like structs in C) and handler types (allowing dynamic handler calls through a variable - like function pointers in C).
203204
205+
## Handler Types
206+
207+
HandlerTypeDefinition
208+
: [ 'foreign' ] 'handler' 'type' <Name: Identifier> '(' [ ParameterList ] ')' [ 'returns' <ReturnType: Type> ]
209+
210+
A handler type definition defines a type which can hold a handler. Variables of
211+
such types can hold handlers (just like function pointers in C) which allows them
212+
to be called dynamically.
213+
214+
If the handler type is defined as foreign then automatic bridging to a C function
215+
pointer will occur when the type appears as the type of a parameter in a foreign
216+
handler definition.
217+
218+
> **Note:** Passing an LCB handler to a foreign function requires creation of
219+
a function pointer. The lifetime of the function pointer is the same as the widget
220+
or module which created it.
221+
204222
## Variables
205223

206224
VariableDefinition
@@ -296,7 +314,7 @@ If the return type is of a Ref type, then it must be a copy.
296314
If an out parameter is of a Ref type, then it must be a copy (on exit)
297315

298316
If an inout parameter is of a Ref type, then its existing value must be released, and replaced by a copy (on exit).
299-
317+
300318
The binding string for foreign handlers has the following form:
301319

302320
[lang:][library>][class.]function[!calling]
@@ -366,7 +384,7 @@ There are a number of built-in statements which define control flow, variables,
366384

367385
VariableStatement
368386
: 'variable' <Name: Identifier> [ 'as' <TypeOf: Type> ]
369-
387+
370388
A variable statement defines a handler-scope variable. Such variables can be used after the variable statement, but not before.
371389

372390
> **Note:** Variables are currently not block-scoped, they are defined from the point of declaration to the end of the handler - this might change in a subsequent revision.
@@ -465,7 +483,7 @@ If a Value expression is specified, it is evaluated and returned as the result o
465483

466484
PutStatement
467485
: 'put' <Value: Expression> into <Target: Expression>
468-
486+
469487
SetStatement
470488
: 'set' <Target: Expression> 'to' <Value: Expression>
471489

libfoundation/include/foundation.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,18 @@ struct MCHandlerTypeFieldInfo
16731673
// an MCHandlerTypeFieldInfo where name is null.
16741674
MC_DLLEXPORT bool MCHandlerTypeInfoCreate(const MCHandlerTypeFieldInfo *fields, index_t field_count, MCTypeInfoRef return_type, MCTypeInfoRef& r_typeinfo);
16751675

1676+
// Create a description of a foreign handler with the given signature.
1677+
// If field_count is negative, the fields array must be terminated by
1678+
// an MCHandlerTypeFieldInfo where name is null.
1679+
//
1680+
// Note: Foreign handlers and handlers are interchangeable for the most part. The
1681+
// distinction is made so that the FFI knows when it needs to bridge from an
1682+
// MCHandlerRef to a C function ptr.
1683+
MC_DLLEXPORT bool MCForeignHandlerTypeInfoCreate(const MCHandlerTypeFieldInfo *fields, index_t field_count, MCTypeInfoRef return_type, MCTypeInfoRef& r_typeinfo);
1684+
1685+
// Returns true if the handler is of foreign type.
1686+
MC_DLLEXPORT bool MCHandlerTypeInfoIsForeign(MCTypeInfoRef typeinfo);
1687+
16761688
// Get the return type of the handler. A return-type of kMCNullTypeInfo means no
16771689
// value is returned.
16781690
MC_DLLEXPORT MCTypeInfoRef MCHandlerTypeInfoGetReturnType(MCTypeInfoRef typeinfo);
@@ -1685,7 +1697,10 @@ MC_DLLEXPORT MCHandlerTypeFieldMode MCHandlerTypeInfoGetParameterMode(MCTypeInfo
16851697

16861698
// Return the type of the index'th parameter.
16871699
MC_DLLEXPORT MCTypeInfoRef MCHandlerTypeInfoGetParameterType(MCTypeInfoRef typeinfo, uindex_t index);
1688-
1700+
1701+
// Returns the 'native' layout ptr (an ffi_cif) for the handler type
1702+
MC_DLLEXPORT bool MCHandlerTypeInfoGetLayoutType(MCTypeInfoRef typeinfo, int abi, void*& r_cif);
1703+
16891704
//////////
16901705

16911706
MC_DLLEXPORT bool MCErrorTypeInfoCreate(MCNameRef domain, MCStringRef message, MCTypeInfoRef& r_typeinfo);
@@ -2658,6 +2673,8 @@ MC_DLLEXPORT const MCHandlerCallbacks *MCHandlerGetCallbacks(MCHandlerRef handle
26582673

26592674
MC_DLLEXPORT bool MCHandlerInvoke(MCHandlerRef handler, MCValueRef *arguments, uindex_t argument_count, MCValueRef& r_value);
26602675
MC_DLLEXPORT /*copy*/ MCErrorRef MCHandlerTryToInvokeWithList(MCHandlerRef handler, MCProperListRef& x_arguments, MCValueRef& r_value);
2676+
2677+
MC_DLLEXPORT bool MCHandlerGetFunctionPtr(MCHandlerRef handler, void*& r_func_ptr);
26612678

26622679
////////////////////////////////////////////////////////////////////////////////
26632680
//
@@ -2666,6 +2683,8 @@ MC_DLLEXPORT /*copy*/ MCErrorRef MCHandlerTryToInvokeWithList(MCHandlerRef handl
26662683

26672684
MC_DLLEXPORT extern MCTypeInfoRef kMCOutOfMemoryErrorTypeInfo;
26682685
MC_DLLEXPORT extern MCTypeInfoRef kMCGenericErrorTypeInfo;
2686+
MC_DLLEXPORT extern MCTypeInfoRef kMCUnboundTypeErrorTypeInfo;
2687+
MC_DLLEXPORT extern MCTypeInfoRef kMCUnimplementedErrorTypeInfo;
26692688

26702689
MC_DLLEXPORT bool MCErrorCreate(MCTypeInfoRef typeinfo, MCArrayRef info, MCErrorRef& r_error);
26712690

@@ -2703,6 +2722,12 @@ MC_DLLEXPORT MCErrorRef MCErrorPeek(void);
27032722

27042723
// Throw an out of memory error.
27052724
MC_DLLEXPORT bool MCErrorThrowOutOfMemory(void);
2725+
2726+
// Throw an unbound type error.
2727+
MC_DLLEXPORT bool MCErrorThrowUnboundType(MCTypeInfoRef type);
2728+
2729+
// Throw an unimplemented error.
2730+
MC_DLLEXPORT bool MCErrorThrowUnimplemented(MCStringRef thing);
27062731

27072732
// Throw a generic runtime error (one that hasn't had a class made for it yet).
27082733
// The message argument is optional (nil if no message).

libfoundation/src/foundation-error.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ along with LiveCode. If not see <http://www.gnu.org/licenses/>. */
2525

2626
MC_DLLEXPORT_DEF MCTypeInfoRef kMCOutOfMemoryErrorTypeInfo;
2727
MC_DLLEXPORT_DEF MCTypeInfoRef kMCGenericErrorTypeInfo;
28+
MC_DLLEXPORT_DEF MCTypeInfoRef kMCUnboundTypeErrorTypeInfo;
29+
MC_DLLEXPORT_DEF MCTypeInfoRef kMCUnimplementedErrorTypeInfo;
2830

2931
static MCErrorRef s_last_error = nil;
3032

@@ -388,6 +390,18 @@ bool MCErrorThrowOutOfMemory(void)
388390
return false;
389391
}
390392

393+
MC_DLLEXPORT_DEF
394+
bool MCErrorThrowUnboundType(MCTypeInfoRef p_type)
395+
{
396+
return MCErrorCreateAndThrow(kMCUnboundTypeErrorTypeInfo, "type", MCNamedTypeInfoGetName(p_type), nil);
397+
}
398+
399+
MC_DLLEXPORT_DEF
400+
bool MCErrorThrowUnimplemented(MCStringRef p_reason)
401+
{
402+
return MCErrorCreateAndThrow(kMCUnimplementedErrorTypeInfo, "reason", p_reason, nil);
403+
}
404+
391405
MC_DLLEXPORT_DEF
392406
bool MCErrorThrowGeneric(MCStringRef p_reason)
393407
{
@@ -448,6 +462,12 @@ bool __MCErrorInitialize(void)
448462

449463
if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.lang.GenericError"), MCNAME("runtime"), MCSTR("%{reason}"), kMCGenericErrorTypeInfo))
450464
return false;
465+
466+
if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.lang.UnboundTypeError"), MCNAME("runtime"), MCSTR("attempt to use unbound named type %{type}"), kMCUnboundTypeErrorTypeInfo))
467+
return false;
468+
469+
if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.lang.UnimplementedError"), MCNAME("runtime"), MCSTR("%{reason}"), kMCUnboundTypeErrorTypeInfo))
470+
return false;
451471

452472
if (!MCErrorCreate(kMCOutOfMemoryErrorTypeInfo, nil, s_out_of_memory_error))
453473
return false;

libfoundation/src/foundation-handler.cpp

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include <foundation.h>
1818
#include <foundation-auto.h>
1919

20+
#include <ffi.h>
21+
2022
#include "foundation-private.h"
2123

2224
////////////////////////////////////////////////////////////////////////////////
@@ -34,6 +36,7 @@ bool MCHandlerCreate(MCTypeInfoRef p_typeinfo, const MCHandlerCallbacks *p_callb
3436
MCMemoryCopy(MCHandlerGetContext(self), p_context, p_callbacks -> size);
3537

3638
self -> typeinfo = MCValueRetain(p_typeinfo);
39+
self -> function_ptr = nil;
3740
self -> callbacks = p_callbacks;
3841

3942
r_handler = self;
@@ -95,10 +98,158 @@ const MCHandlerCallbacks *MCHandlerGetCallbacks(MCHandlerRef self)
9598

9699
////////////////////////////////////////////////////////////////////////////////
97100

101+
static void __exec_closure(ffi_cif *cif, void *ret, void **args, void *user_data)
102+
{
103+
MCHandlerRef t_handler;
104+
t_handler = (MCHandlerRef)user_data;
105+
106+
MCTypeInfoRef t_signature;
107+
t_signature = t_handler -> typeinfo;
108+
109+
// Check the arity of the handler - at the moment we can only handle 16
110+
// arguments.
111+
uindex_t t_arity;
112+
t_arity = MCHandlerTypeInfoGetParameterCount(t_signature);
113+
if (t_arity > 16)
114+
{
115+
MCErrorThrowUnimplemented(MCSTR("closures only supported for handlers with at most 16 parameters"));
116+
return;
117+
}
118+
119+
// Check that we can resolve the return type.
120+
MCTypeInfoRef t_return_type;
121+
t_return_type = MCHandlerTypeInfoGetReturnType(t_signature);
122+
123+
MCResolvedTypeInfo t_resolved_return_type;
124+
if (!MCTypeInfoResolve(t_return_type, t_resolved_return_type))
125+
{
126+
MCErrorThrowUnboundType(t_return_type);
127+
return;
128+
}
129+
130+
// Build the argument list, doing foreign type bridging as necessary.
131+
MCValueRef t_value_result;
132+
MCValueRef t_value_args[16];
133+
uindex_t t_arg_index;
134+
t_arg_index = 0;
135+
t_value_result = nil;
136+
for(t_arg_index = 0; t_arg_index < t_arity; t_arg_index++)
137+
{
138+
MCHandlerTypeFieldMode t_mode;
139+
t_mode = MCHandlerTypeInfoGetParameterMode(t_signature, t_arg_index);
140+
141+
MCTypeInfoRef t_type;
142+
t_type = MCHandlerTypeInfoGetParameterType(t_signature, t_arg_index);
143+
144+
// We don't support anything other than 'in' mode parameters at the moment.
145+
if (t_mode != kMCHandlerTypeFieldModeIn)
146+
{
147+
MCErrorThrowUnimplemented(MCSTR("closures only support in arguments"));
148+
goto cleanup;
149+
}
150+
151+
// Make sure we can resolve the parameter type.
152+
MCResolvedTypeInfo t_resolved_type;
153+
if (!MCTypeInfoResolve(t_type, t_resolved_type))
154+
{
155+
MCErrorThrowUnboundType(t_type);
156+
goto cleanup;
157+
}
158+
159+
// If the parameter type is foreign then we must attempt to bridge it
160+
// from the passed in type; otherwise we just retain the valueref.
161+
if (MCTypeInfoIsForeign(t_resolved_type . type))
162+
{
163+
const MCForeignTypeDescriptor *t_descriptor;
164+
t_descriptor = MCForeignTypeInfoGetDescriptor(t_resolved_type . type);
165+
if (t_descriptor -> defined != nil &&
166+
!t_descriptor -> defined(args[t_arg_index]))
167+
t_value_args[t_arg_index] = MCValueRetain(kMCNull);
168+
else
169+
{
170+
if (t_descriptor -> bridgetype != kMCNullTypeInfo)
171+
{
172+
if (!t_descriptor -> doimport(args[t_arg_index], false, t_value_args[t_arg_index]))
173+
goto cleanup;
174+
}
175+
else
176+
{
177+
if (!MCForeignValueCreate(t_resolved_type . named_type, args[t_arg_index], (MCForeignValueRef&)t_value_args[t_arg_index]))
178+
goto cleanup;
179+
}
180+
}
181+
}
182+
else
183+
{
184+
t_value_args[t_arg_index] = MCValueRetain(*(MCValueRef*)args[t_arg_index]);
185+
}
186+
}
187+
188+
// Actually call the LCB handler.
189+
if (!MCHandlerInvoke(t_handler, t_value_args, t_arity, t_value_result))
190+
goto cleanup;
191+
192+
// If the return type is not 'void' then we must map the value back
193+
// appropriately.
194+
if (t_resolved_return_type . named_type != kMCNullTypeInfo)
195+
{
196+
if (MCTypeInfoIsForeign(t_resolved_return_type . type))
197+
{
198+
const MCForeignTypeDescriptor *t_descriptor;
199+
t_descriptor = MCForeignTypeInfoGetDescriptor(t_resolved_return_type . type);
200+
if (!t_descriptor -> doexport(t_value_result, false, ret))
201+
goto cleanup;
202+
}
203+
else
204+
{
205+
*(MCValueRef *)ret = t_value_result;
206+
t_value_result = nil;
207+
}
208+
}
209+
210+
cleanup:
211+
if (t_value_result != nil)
212+
MCValueRelease(t_value_result);
213+
for(uindex_t i = 0; i < t_arg_index; i++)
214+
MCValueRelease(t_value_args[i]);
215+
}
216+
217+
MC_DLLEXPORT_DEF
218+
bool MCHandlerGetFunctionPtr(MCHandlerRef self, void*& r_function_ptr)
219+
{
220+
if (self -> function_ptr != nil)
221+
{
222+
r_function_ptr = self -> function_ptr;
223+
return true;
224+
}
225+
226+
ffi_cif *t_cif;
227+
if (!MCHandlerTypeInfoGetLayoutType(self -> typeinfo, (int)FFI_DEFAULT_ABI, (void*&)t_cif))
228+
return false;
229+
230+
self -> closure = ffi_closure_alloc(sizeof(ffi_closure), &self -> function_ptr);
231+
if (self -> closure == nil)
232+
return MCErrorThrowOutOfMemory();
233+
234+
if (ffi_prep_closure_loc((ffi_closure *)self -> closure, t_cif, __exec_closure, self, self -> function_ptr) != FFI_OK)
235+
{
236+
ffi_closure_free(self -> closure);
237+
self -> closure = nil;
238+
return MCErrorThrowGeneric(MCSTR("unexpected libffi failure"));
239+
}
240+
241+
r_function_ptr = self -> function_ptr;
242+
return true;
243+
}
244+
245+
////////////////////////////////////////////////////////////////////////////////
246+
98247
void __MCHandlerDestroy(__MCHandler *self)
99248
{
100249
if (self -> callbacks -> release != nil)
101250
self -> callbacks -> release(MCHandlerGetContext(self));
251+
if (self -> function_ptr != nil)
252+
ffi_closure_free(self -> function_ptr);
102253
}
103254

104255
hash_t __MCHandlerHash(__MCHandler *self)

libfoundation/src/foundation-private.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ struct __MCValue
5454
enum
5555
{
5656
kMCTypeInfoTypeCodeMask = 0xff,
57+
kMCTypeInfoFlagHandlerIsForeign = 1 << 8,
5758

5859
// We use typecodes well above the fixed ones we have to
5960
// indicate 'special' typeinfo (i.e. those with no real
@@ -65,6 +66,13 @@ enum
6566
kMCTypeInfoTypeIsForeign = 251,
6667
};
6768

69+
struct MCHandlerTypeLayout
70+
{
71+
MCHandlerTypeLayout *next;
72+
int abi;
73+
char cif[1];
74+
};
75+
6876
struct __MCTypeInfo: public __MCValue
6977
{
7078
union
@@ -89,6 +97,8 @@ struct __MCTypeInfo: public __MCValue
8997
MCHandlerTypeFieldInfo *fields;
9098
uindex_t field_count;
9199
MCTypeInfoRef return_type;
100+
void **layout_args;
101+
MCHandlerTypeLayout *layouts;
92102
} handler;
93103
struct
94104
{
@@ -421,6 +431,8 @@ struct __MCHandler: public __MCValue
421431
{
422432
MCTypeInfoRef typeinfo;
423433
const MCHandlerCallbacks *callbacks;
434+
void *closure;
435+
void *function_ptr;
424436
char context[1];
425437
};
426438

0 commit comments

Comments
 (0)