如何为 C 函数创建闭包
How to create a closure for a C function
我正在使用 C API 与 ECL 交互,我正尝试从具有某些存储状态的本机函数创建闭包对象。
我试过这个:
cl_object f(long nargs, ...) {
std::cout << nargs << std::endl;
std::cout << "has value?" << std::endl;
cl_print(1, cl_boundp(c_string_to_object("HI")));
std::cout << "\ndone" << std::endl;
return Cnil;
}
auto closure = ecl_make_cclosure_va(f, c_string_to_object("((HI . 2))"), Cnil);
ecl_defparameter(c_string_to_object("F"), closure);
这使得函数 f
可以在 lisp 中用 (funcall f)
调用,但是 cl_boundp
总是 returns nil
在 f
的主体中.如何将 env
(环境)参数构造为 ecl_make_cclosure_va
以便本机代码可以读取闭包中的值?我认为它只是一个列表但显然不是,而且我没有找到在 Google.
上构建一个列表的示例
编辑:我首先添加了第二个相当干净、较短的答案,它可能不完全符合您的要求,但可能已经足够接近了。原始的、较长的答案说明了为什么完全按照您的意愿行事很复杂,但表明可能的起点仍然存在。
更简单的方法
我已经根据 ECL 接口对此进行了概述,但它可能主要通过 FFI 接口来完成,以获得更清洁、更便携的方法。
- 定义一个 c 函数,它接受它需要的所有变量,包括闭包变量
代码:
// defined here as a non-varargs function - you'll need to change it slightly
cl_object f(cl_object hi, cl_object func_param1, cl_object_fund_param2) {
// note you can pass hi back to lisp fairly easily
cl_object name = ecl_make_symbol("do-something-useful","CL-USER");
cl_funcall(2,name,hi);
// some other stuff
}
封装函数,使其可以被 lisp 调用 (ecl_def_c_function
?)
用 lisp 包装闭包
代码:
cl_object wrapped_c_function = cl_safe_eval(c_string_to_object(
"(let ((hi 2))
#'(lambda (x y) (your-c-function hi x y)))"),Cnil,Cnil);
- 调用包装的 C 函数。
原回答:
这个回答有点长 "not easily" 但是:
了解 ecl 功能的最简单方法是使用它将一个简单的脚本编译为 C (ecl -c <filename.c> -compile <filename.lisp>
)。这是一个简单的 lisp 代码,它生成一个带有可变参数列表的闭包
(defun make-closure-function (x y)
#'(lambda (&rest arguments) (apply '+ (append (list x y) arguments))))
(defun main ()
(let ((f (make-closure-function 1 2)))
(print (funcall f 3)))
(format t "~%"))
(main)
这是它生成的 C 代码的相关部分
/* function definition for MAKE-CLOSURE-FUNCTION */
/* optimize speed 3, debug 0, space 0, safety 2 */
static cl_object L2make_closure_function(cl_object v1x, cl_object v2y)
{
cl_object env0;
cl_object CLV0, CLV1;
const cl_env_ptr cl_env_copy = ecl_process_env();
cl_object value0;
ecl_cs_check(cl_env_copy,value0);
{
env0 = ECL_NIL;
CLV0 = env0 = CONS(v1x,env0); /* X */
CLV1 = env0 = CONS(v2y,env0); /* Y */
{
cl_object v3;
v3 = ecl_make_cclosure_va((cl_objectfn)LC1__g0,env0,Cblock);
value0 = v3;
cl_env_copy->nvalues = 1;
return value0;
}
}
}
/* closure G0 */
/* optimize speed 3, debug 0, space 0, safety 2 */
static cl_object LC1__g0(cl_narg narg, ...)
{
cl_object T0, T1;
cl_object CLV0, CLV1;
const cl_env_ptr cl_env_copy = ecl_process_env();
cl_object env0 = cl_env_copy->function->cclosure.env;
cl_object value0;
ecl_cs_check(cl_env_copy,value0);
/* Scanning closure data ... */
CLV1 = env0; /* Y */
CLV0 = _ecl_cdr(CLV1);
{ /* ... closure scanning finished */
{
cl_object v1arguments;
ecl_va_list args; ecl_va_start(args,narg,narg,0);
v1arguments = cl_grab_rest_args(args);
ecl_va_end(args);
T0 = cl_list(2, ECL_CONS_CAR(CLV0), ECL_CONS_CAR(CLV1));
T1 = ecl_append(T0,v1arguments);
value0 = cl_apply(2, ECL_SYM("+",14), T1);
return value0;
}
}
}
/* function definition for MAIN */
/* optimize speed 3, debug 0, space 0, safety 2 */
static cl_object L3main()
{
cl_object T0;
const cl_env_ptr cl_env_copy = ecl_process_env();
cl_object value0;
ecl_cs_check(cl_env_copy,value0);
{
TTL:
{
cl_object v1f;
v1f = L2make_closure_function(ecl_make_fixnum(1), ecl_make_fixnum(2));
T0 = ecl_function_dispatch(cl_env_copy,v1f)(1, ecl_make_fixnum(3));
ecl_print(T0,ECL_NIL);
}
value0 = cl_format(2, ECL_T, VV[1]);
return value0;
}
}
它在 make_closure_function
中创建 env0
,并在列表中附加 x
和 y
。然后它调用 ecl_make_cclosure_va
。在 LC1_g0
(lambda 的包装)中,它使用 cl_object env0 = cl_env_copy->function->cclosure.env;
.
访问环境
请注意,访问环境中的值是因为它知道顺序,而不是能够通过名称获取它们。
您基本上必须复制此机制以访问 c 函数中的闭包变量(尽管您可以生成一个以方便的方式存储值的环境(例如 (HI 2)
的列表)。
您最好的选择可能是使用 ecl_bds_bind(请参阅 http://ecls.sourceforge.net/new-manual/re04.html)将来自环境的位绑定为特殊变量。如果你这样做,那么我想你可以使用 HI
。尽管要注意它是动态的而不是词法绑定的。
虽然工作量很大!这真的不是 "user-facing interface".
我正在使用 C API 与 ECL 交互,我正尝试从具有某些存储状态的本机函数创建闭包对象。
我试过这个:
cl_object f(long nargs, ...) {
std::cout << nargs << std::endl;
std::cout << "has value?" << std::endl;
cl_print(1, cl_boundp(c_string_to_object("HI")));
std::cout << "\ndone" << std::endl;
return Cnil;
}
auto closure = ecl_make_cclosure_va(f, c_string_to_object("((HI . 2))"), Cnil);
ecl_defparameter(c_string_to_object("F"), closure);
这使得函数 f
可以在 lisp 中用 (funcall f)
调用,但是 cl_boundp
总是 returns nil
在 f
的主体中.如何将 env
(环境)参数构造为 ecl_make_cclosure_va
以便本机代码可以读取闭包中的值?我认为它只是一个列表但显然不是,而且我没有找到在 Google.
编辑:我首先添加了第二个相当干净、较短的答案,它可能不完全符合您的要求,但可能已经足够接近了。原始的、较长的答案说明了为什么完全按照您的意愿行事很复杂,但表明可能的起点仍然存在。
更简单的方法
我已经根据 ECL 接口对此进行了概述,但它可能主要通过 FFI 接口来完成,以获得更清洁、更便携的方法。
- 定义一个 c 函数,它接受它需要的所有变量,包括闭包变量
代码:
// defined here as a non-varargs function - you'll need to change it slightly
cl_object f(cl_object hi, cl_object func_param1, cl_object_fund_param2) {
// note you can pass hi back to lisp fairly easily
cl_object name = ecl_make_symbol("do-something-useful","CL-USER");
cl_funcall(2,name,hi);
// some other stuff
}
封装函数,使其可以被 lisp 调用 (
ecl_def_c_function
?)用 lisp 包装闭包
代码:
cl_object wrapped_c_function = cl_safe_eval(c_string_to_object(
"(let ((hi 2))
#'(lambda (x y) (your-c-function hi x y)))"),Cnil,Cnil);
- 调用包装的 C 函数。
原回答:
这个回答有点长 "not easily" 但是:
了解 ecl 功能的最简单方法是使用它将一个简单的脚本编译为 C (ecl -c <filename.c> -compile <filename.lisp>
)。这是一个简单的 lisp 代码,它生成一个带有可变参数列表的闭包
(defun make-closure-function (x y)
#'(lambda (&rest arguments) (apply '+ (append (list x y) arguments))))
(defun main ()
(let ((f (make-closure-function 1 2)))
(print (funcall f 3)))
(format t "~%"))
(main)
这是它生成的 C 代码的相关部分
/* function definition for MAKE-CLOSURE-FUNCTION */
/* optimize speed 3, debug 0, space 0, safety 2 */
static cl_object L2make_closure_function(cl_object v1x, cl_object v2y)
{
cl_object env0;
cl_object CLV0, CLV1;
const cl_env_ptr cl_env_copy = ecl_process_env();
cl_object value0;
ecl_cs_check(cl_env_copy,value0);
{
env0 = ECL_NIL;
CLV0 = env0 = CONS(v1x,env0); /* X */
CLV1 = env0 = CONS(v2y,env0); /* Y */
{
cl_object v3;
v3 = ecl_make_cclosure_va((cl_objectfn)LC1__g0,env0,Cblock);
value0 = v3;
cl_env_copy->nvalues = 1;
return value0;
}
}
}
/* closure G0 */
/* optimize speed 3, debug 0, space 0, safety 2 */
static cl_object LC1__g0(cl_narg narg, ...)
{
cl_object T0, T1;
cl_object CLV0, CLV1;
const cl_env_ptr cl_env_copy = ecl_process_env();
cl_object env0 = cl_env_copy->function->cclosure.env;
cl_object value0;
ecl_cs_check(cl_env_copy,value0);
/* Scanning closure data ... */
CLV1 = env0; /* Y */
CLV0 = _ecl_cdr(CLV1);
{ /* ... closure scanning finished */
{
cl_object v1arguments;
ecl_va_list args; ecl_va_start(args,narg,narg,0);
v1arguments = cl_grab_rest_args(args);
ecl_va_end(args);
T0 = cl_list(2, ECL_CONS_CAR(CLV0), ECL_CONS_CAR(CLV1));
T1 = ecl_append(T0,v1arguments);
value0 = cl_apply(2, ECL_SYM("+",14), T1);
return value0;
}
}
}
/* function definition for MAIN */
/* optimize speed 3, debug 0, space 0, safety 2 */
static cl_object L3main()
{
cl_object T0;
const cl_env_ptr cl_env_copy = ecl_process_env();
cl_object value0;
ecl_cs_check(cl_env_copy,value0);
{
TTL:
{
cl_object v1f;
v1f = L2make_closure_function(ecl_make_fixnum(1), ecl_make_fixnum(2));
T0 = ecl_function_dispatch(cl_env_copy,v1f)(1, ecl_make_fixnum(3));
ecl_print(T0,ECL_NIL);
}
value0 = cl_format(2, ECL_T, VV[1]);
return value0;
}
}
它在 make_closure_function
中创建 env0
,并在列表中附加 x
和 y
。然后它调用 ecl_make_cclosure_va
。在 LC1_g0
(lambda 的包装)中,它使用 cl_object env0 = cl_env_copy->function->cclosure.env;
.
请注意,访问环境中的值是因为它知道顺序,而不是能够通过名称获取它们。
您基本上必须复制此机制以访问 c 函数中的闭包变量(尽管您可以生成一个以方便的方式存储值的环境(例如 (HI 2)
的列表)。
您最好的选择可能是使用 ecl_bds_bind(请参阅 http://ecls.sourceforge.net/new-manual/re04.html)将来自环境的位绑定为特殊变量。如果你这样做,那么我想你可以使用 HI
。尽管要注意它是动态的而不是词法绑定的。
虽然工作量很大!这真的不是 "user-facing interface".