在语言层面,究竟是什么`ccall`?

时间:2017-02-15 23:33:41

标签: function syntax macros julia ffi

我是朱莉娅的新手,我试图在语言层面理解ccall是什么。在语法级别,它看起来像一个普通的函数,但它的参数显然不同:

  

请注意,参数类型元组必须是文字元组,而不是a   元组值变量或表达式。

此外,如果我评估一个绑定到Julia REPL中的函数的变量,我会得到像

这样的东西
julia> max
max (generic function with 15 methods)

但如果我尝试对ccall执行相同的操作:

julia> ccall
ERROR: syntax: invalid "ccall" syntax

显然,ccall是一种特殊的语法,但它也不是宏(没有@前缀,无效的宏使用会产生更具体的错误)。那么,它是什么?它是用语言编写的东西,还是我可以用一些我不熟悉的语言构造来定义自己的东西?

如果它是一些烘焙的语法,为什么它决定使用函数调用符号,而不是将其实现为宏或设计更可读和不同的语法?

2 个答案:

答案 0 :(得分:10)

在当前的夜间(以及即将发布的0.6版本)中,您观察到的大部分特殊行为has been removed(请参阅this pull-request)。 ccall不再是保留字,因此可以用作函数或宏名称。

然而仍有一点点奇怪:允许定义一个名为ccall的3或4参数函数,但实际调用这样的函数会给出{{1}的错误(其他数量的参数都可以)。原因直接针对您的问题:

  

那么,它是什么?它是否被烘焙成语言

是的,ccall argument types,虽然它不再是0.6中的关键字,但仍然以某种方式“融入”该语言:

  • 在语法降低期间识别ccall表达式表单并specially handled。这个降低步骤做了几件事,包括在:ccall([four args...])的调用中包装参数,允许从Julia对象到C兼容对象的自定义转换;以及提取可能需要rooted的参数,以防止在unsafe_convert期间对引用对象进行垃圾收集。 (参见ccall输出,或尝试code_lowered函数;有关编译器here的更多信息。)。
  • expand在代码生成后端需要extensive handling,包括:在指定的共享库中查找请求的函数名,以及生成LLVM call指令 - 最终由LLVM Just-In-Time编译器转换为特定于平台的机器代码。 (请参阅ccallcode_llvm的不同阶段。
  

如果它是一些烘焙的语法,为什么决定使用它   函数调用符号,而不是将其实现为宏或   设计一个更具可读性和独特性的语法?

由于上面详述的原因,code_native需要特殊处理,无论它看起来像宏还是函数。在this mailing list thread,其中一位朱莉娅的创作者(Stefan Karpinski)评论了为什么不把它变成一个宏:

  

我想我们可以将它作为一个宏重新实现,但那真的只会让魔法进一步下降。

就“更可读和更独特的语法”而言,也许这是一个品味问题。我不清楚为什么其他一些语法会更好(除了方便LuaJIT / CFFI样式的内联C语法解析,我是其中的粉丝)。我对ccall唯一强烈的个人愿望就是让参数和类型相邻(例如ccall),因为使用较长的参数列表可能会带来不便。在0.6中,可以将此表单实现为宏!

答案 1 :(得分:4)

Julia 0.5及更早版本。 它不是一个功能,它不是一个宏。 这确实是语言特有的东西。 这是一种内在的。 In julia 0.6 this changes

它在很多方面更像是宏而不是函数调用。 但在其他方面它不是 - 它不会返回AST。 它确实调用了一个函数,并且在一个足够低的级别上它看起来类似于调用julia函数。

为什么它看起来像它的方式的历史超出了我,你需要听取其中一个从事最早的语言代码工作的人的意见。 现在它无处不在,而且是难以改变的事情之一 - 但并非不可能。 It would trigger up for 3 years of bikeshedding though :-P

我喜欢将ccall视为两件事。

  • 外部函数接口,用于C和其他编译语言(例如Fortran,Rust显然可以工作)
  • 访问语言“运行时”的原始内容。

外部函数接口(FFI)

大多数情况下,如果在包中使用ccall,则需要调用编译库中的某些代码。在这个意义上,它是C-Call,如R-Call或Py-Call。 我认为mlewe/BlossomV.jl是一个很好的紧凑例子。 有关更强烈的示例oxinabox/SLEEF.jl

作为一个FFI,它不必与julia共享内存空间/进程 - PyCall.jl,RCall.jl和Matlab.jl没有。 只要结果回来就没关系。 在这些情况下,理论上可以用某种ccall替换safe_ccallccall将在一个单独的进程中运行被调用的库,如果库被称为segfaulted,则不会使julia发生段错误。 但到目前为止,还没有人写过这样的方法/包。

使用ccall进行FFI甚至可以在Base中完成,就像访问MPFR to define BigFloat一样。 但这不是Base中使用ccall的主要原因。

访问语言的内容。

ccall正是推动大部分计划“做事”的原因。 它在整个Base中使用,用于调用src中的函数。

为此,ccall基本上在编译级别触发函数调用,将指令指针直接转换为malloc ed函数的编译代码。就像调用函数一样,如果整个事情都是用C语言编写的。

您可以在base/threadingconstructs.jl中看到ccall用于管理线程上的工作 - 从src/threading.c触发代码。

用于将磁盘的一部分映射到内存。 mmap.jl。 - 显然不能从另一个过程完成。

用于制作代码non-intruptable

的一部分

使用调用LibC来执行ccall之类的操作来分配内存(尽管现在这主要用作FFI的一部分)。

在已经分配变量后,您可以使用#undefccall变量进行操作。 ccall在很多方面都是该语言的“主要”关键。

结论

我在这里将 public IEnumerable<PostJob> GetAllJobsByUserId(int id) { using (var context = new CleanerDataContext(@"Data Source=.\sqlexpress;Initial Catalog='Cleaning Lady';Integrated Security=True")) { var loadOptions = new DataLoadOptions(); loadOptions.LoadWith<PostJob>(c => c.Cleaner); loadOptions.LoadWith<PostJob>(c => c.User); context.LoadOptions = loadOptions; return context.PostJobs.Where(r => r.userId == id).ToList(); } } 描述为两件事,FFI函数和语言“运行时”的核心部分。这种二元性并不真实,并且有很多重叠,比如文件处理(是FFI吗?)。 很多人都期望ccall来自它的FFI用途。 这里ccall可能只是一个功能。 它实际上的行为来自于它作为语言的核心部分 - 将Base中标准库的julia代码链接到src的低级C代码。 允许直接控制朱莉娅过程的运行。