我是朱莉娅的新手,我试图在语言层面理解ccall
是什么。在语法级别,它看起来像一个普通的函数,但它的参数显然不同:
请注意,参数类型元组必须是文字元组,而不是a 元组值变量或表达式。
此外,如果我评估一个绑定到Julia REPL中的函数的变量,我会得到像
这样的东西julia> max
max (generic function with 15 methods)
但如果我尝试对ccall
执行相同的操作:
julia> ccall
ERROR: syntax: invalid "ccall" syntax
显然,ccall
是一种特殊的语法,但它也不是宏(没有@
前缀,无效的宏使用会产生更具体的错误)。那么,它是什么?它是用语言编写的东西,还是我可以用一些我不熟悉的语言构造来定义自己的东西?
如果它是一些烘焙的语法,为什么它决定使用函数调用符号,而不是将其实现为宏或设计更可读和不同的语法?
答案 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编译器转换为特定于平台的机器代码。 (请参阅ccall
和code_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
视为两件事。
大多数情况下,如果在包中使用ccall
,则需要调用编译库中的某些代码。在这个意义上,它是C-Call,如R-Call或Py-Call。
我认为mlewe/BlossomV.jl是一个很好的紧凑例子。
有关更强烈的示例oxinabox/SLEEF.jl。
作为一个FFI,它不必与julia共享内存空间/进程 - PyCall.jl,RCall.jl和Matlab.jl没有。
只要结果回来就没关系。
在这些情况下,理论上可以用某种ccall
替换safe_ccall
,ccall
将在一个单独的进程中运行被调用的库,如果库被称为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的一部分)。
在已经分配变量后,您可以使用#undef
到ccall
变量进行操作。
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代码。
允许直接控制朱莉娅过程的运行。