【问题标题】:How to create a closure for a C function如何为 C 函数创建闭包
【发布时间】:2015-03-18 08:08:33
【问题描述】:

我正在使用 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 总是在f 的主体内返回nil。如何将env(环境)参数构造为ecl_make_cclosure_va,以便本机代码可以读取闭包中的值?我以为它只是一个 alist,但显然不是,而且我没有找到在 Google 上构建它的示例。

【问题讨论】:

  • 您的代码是 C++,而不是 C。ECL 用于 C,而不是 C++
  • 你可能应该深入研究 FFI....
  • 另外,请注意 boundp 不处理词法变量,仅处理符号。例如,(let ((x 42)) (boundp 'x)) =&gt; NIL。即使 C 代码是正确的 CL 翻译,翻译错误的 CL 代码也无济于事。 :) Common Lisp 根本没有定义太多的环境自省(尽管很好的实现可能会提供一些)。
  • 环境中更常见的接口之一是CLtL-2 environments API
  • @JoshuaTaylor 知道如何使用 ECL API 获取这些词法变量之一的值吗?

标签: c lambda closures common-lisp ecl


【解决方案1】:

编辑:我首先添加了第二个相当干净、简短的答案,这可能不能完全满足您的要求,但可能已经足够接近了。原始的更长的答案说明了为什么完全按照您的意愿去做很复杂,但建议之后仍然存在一个可能的起点。

更简单的方法

我已经根据 ECL 接口对此进行了概述,但它可能在很大程度上可以通过 FFI 接口完成,以获得更清洁、更便携的方法。

  1. 定义一个 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
}
  1. 包装函数,使其可由 lisp 调用 (ecl_def_c_function ?)

  2. 用 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);
  1. 调用封装的 C 函数。

原答案

这是一个略长的回答“不容易”,但是:

了解 ecl 功能的最简单方法是使用它将简单的脚本编译为 C (ecl -c &lt;filename.c&gt; -compile &lt;filename.lisp&gt;)。这是一个简单的 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,并在列表中附加xy。然后它调用ecl_make_cclosure_va。在LC1_g0(lambda 的包装)中,它使用cl_object env0 = cl_env_copy-&gt;function-&gt;cclosure.env; 访问环境。

请注意,访问环境中的值是因为它知道顺序,而不是能够通过名称获取它们。

您基本上必须复制这种机制来访问 c 函数中的闭包变量(尽管您可以生成一个以方便的方式存储值的环境(例如 (HI 2) 的列表)。

您最好的选择可能是使用 ecl_bds_bind 将环境中的位绑定为特殊变量(请参阅http://ecls.sourceforge.net/new-manual/re04.html)。如果你这样做了,那么我想你可以使用HI。虽然要注意它是动态的而不是词法绑定的。

虽然工作量很大!这真的不是“面向用户的界面”。

【讨论】:

  • 所以你可以使用cl_env_copy-&gt;function-&gt;cclosure.env;访问环境?这似乎已经足够好了,我可以忍受使用 CADR 和家庭手动提取变量。感谢您的详细回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-26
  • 2012-03-20
  • 1970-01-01
  • 2022-07-27
  • 2016-12-11
  • 2019-04-26
  • 1970-01-01
相关资源
最近更新 更多