Julia 调用 C 和 Fortran 代码

Julia 调用 C 和 Fortran 的函数,既简单又高效。

被调用的代码应该是共享库的格式。大多数 C 和 Fortran 库都已经被编译为共享库。如果自己使用 GCC (或 Clang )编译代码,需要添加 -shared 和 -fPIC 选项。Julia 调用这些库的开销与本地 C 语言相同。

调用共享库和函数时使用多元组形式: (:function, "library") 或 ("function", "library") ,其中 function 是 C 的导出函数名, library 是共享库名。共享库依据名字来解析,路径由环境变量来确定,有时需要直接指明。

多元组内有时仅有函数名(仅 :function 或 "function" )。此时,函数名由当前进程解析。这种形式可以用来调用 C 库函数, Julia 运行时函数,及链接到 Julia 的应用中的函数。

使用 ccall 来生成库函数调用。 ccall 的参数如下:

  1. (:function, "library") 多元组对儿(必须为常量,详见下面)
  2. 返回类型,可以为任意的位类型,包括 Int32 , Int64 , Float64 ,或者指向任意类型参数 T 的指针 Ptr{T} ,或者仅仅是指向无类型指针 void* 的 Ptr
  3. 输入的类型的多元组,与上述的返回类型的要求类似。输入必须是多元组,而不是值为多元组的变量或表达式
  4. 后面的参数,如果有的话,都是被调用函数的实参

下例调用标准 C 库中的 clock :

julia> t = ccall( (:clock, "libc"), Int32, ())
    2292761

    julia> t
    2292761

    julia> typeof(ans)
    Int32
clock 函数没有参数,返回 Int32 类型。输入的类型如果只有一个,常写成一元多元组,在后面跟一逗号。例如要调用 getenv 函数取得指向一个环境变量的指针,应这样调用:
julia> path = ccall( (:getenv, "libc"), Ptr{Uint8}, (Ptr{Uint8},), "SHELL")
    Ptr{Uint8} @0x00007fff5fbffc45

    julia> bytestring(path)
    "/bin/bash"
注意,类型多元组的参数必须写成 (Ptr{Uint8},) ,而不是 (Ptr{Uint8}) 。这是因为 (Ptr{Uint8}) 等价于 Ptr{Uint8} ,它并不是一个包含 Ptr{Uint8} 的一元多元组:
julia> (Ptr{Uint8})
    Ptr{Uint8}

    julia> (Ptr{Uint8},)
    (Ptr{Uint8},)
实际中要提供可复用代码时,通常要使用 Julia 的函数来封装 ccall ,设置参数,然后检查 C 或 Fortran 函数中可能出现的任何错误,将其作为异常传递给 Julia 的函数调用者。下例中, getenv C 库函数被封装在 env.jl 里的 Julia 函数中:
function getenv(var::String)
      val = ccall( (:getenv, "libc"),
                  Ptr{Uint8}, (Ptr{Uint8},), var)
      if val == C_NULL
        error("getenv: undefined variable: ", var)
      end
      bytestring(val)
    end
上例中,如果函数调用者试图读取一个不存在的环境变量,封装将抛出异常:
julia> getenv("SHELL")
    "/bin/bash"

    julia> getenv("FOOBAR")
    getenv: undefined variable: FOOBAR
下例稍复杂些,显示本地机器的主机名:
function gethostname()
      hostname = Array(Uint8, 128)
      ccall( (:gethostname, "libc"), Int32,
            (Ptr{Uint8}, Uint),
            hostname, length(hostname))
      return bytestring(convert(Ptr{Uint8}, hostname))
    end
此例先分配出一个字节数组,然后调用 C 库函数 gethostname 向数组中填充主机名,取得指向主机名缓冲区的指针,在默认其为空结尾 C 字符串的前提下,将其转换为 Julia 字符串。 


C 库函数一般都用这种方式从函数调用者那儿,将申请的内存传递给被调用者,然后填充。在 Julia 中分配内存,通常都需要通过构建非初始化数组,然后将指向数据的指针传递给 C 函数。

调用 Fortran 函数时,所有的输入都必须通过引用来传递。

& 前缀说明传递的是指向标量参数的指针,而不是标量值本身。下例使用 BLAS 函数计算点积:

function compute_dot(DX::Vector{Float64}, DY::Vector{Float64})
      assert(length(DX) == length(DY))
      n = length(DX)
      incx = incy = 1
      product = ccall( (:ddot_, "libLAPACK"),
                      Float64,
                      (Ptr{Int32}, Ptr{Float64}, Ptr{Int32}, Ptr{Float64}, Ptr{Int32}),
                      &n, DX, &incx, DY, &incy)
      return product
    end

前缀 & 的意思与 C 中的不同。对引用的变量的任何更改,都是对 Julia 不可见的。 & 并不是真正的地址运算符,可以在任何语法中使用它,例如 &0 和 &f(x) 。

注意在处理过程中,C 的头文件可以放在任何地方。目前还不能将 Julia 的结构和其他非基础类型传递给 C 库。通过传递指针来生成、使用非透明结构类型的 C 函数,可以向 Julia 返回 Ptr{Void} 类型的值,这个值以 Ptr{Void} 的形式被其它 C 函数调用。可以像任何 C 程序一样,通过调用库中对应的程序,对对象进行内存分配和释放。

把 C 类型映射到 Julia

Julia 自动调用 convert 函数,将参数转换为指定类型。例如:

ccall( (:foo, "libfoo"), Void, (Int32, Float64),
          x, y)
会按如下操作:
ccall( (:foo, "libfoo"), Void, (Int32, Float64),
          convert(Int32, x), convert(Float64, y))
如果标量值与 & 一起被传递作为 Ptr{T} 类型的参数时,值首先会被转换为 T 类型。

数组转换

把数组作为一个 Ptr{T} 参数传递给 C 时,它不进行转换。Julia 仅检查元素类型是否为 T ,然后传递首元素的地址。这样做可以避免不必要的复制整个数组。

因此,如果 Array 中的数据格式不对时,要使用显式转换,如 int32(a) 。

如果想把数组 不经转换 而作为一个不同类型的指针传递时,要么声明参数为 Ptr{Void} 类型,要么显式调用convert(Ptr{T}, pointer(A)) 。

类型相关

基础的 C/C++ 类型和 Julia 类型对照如下。每个 C 类型也有一个对应名称的 Julia 类型,不过冠以了前缀 C 。这有助于编写简便的代码(但 C 中的 int 与 Julia 中的 Int 不同)。

与系统无关:

unsigned char Cuchar Uint8
short Cshort Int16
unsigned short Cushort Uint16
int Cint Int32
unsigned int Cuint Uint32
long long Clonglong Int64
unsigned long long Culonglong Uint64
intmax_t Cintmax_t Int64
uintmax_t Cuintmax_t Uint64
float Cfloat Float32
double Cdouble Float64
ptrdiff_t Cptrdiff_t Int
ssize_t Cssize_t Int
size_t Csize_t Uint
void Void
void* Ptr{Void}
char* (or char[], e.g. a string) Ptr{Uint8}
char* (or char[]) Ptr{Ptr{Uint8}}
struct T* (T 正确表示一个定义好的 bit 类型) Ptr{T} (在参数列表中使用 &variable_name 调用)
struct T (T 正确表示一个定义好的 bit 类型) T (在参数列表中使用 &variable_name 调用)
jl_value_t* (任何 Julia 类型) Ptr{Any}

对应于字符串参数( char* )的 Julia 类型为 Ptr{Uint8} ,而不是 ASCIIString 。参数中有 char** 类型的 C 函数,在 Julia 中调用时应使用 Ptr{Ptr{Uint8}} 类型。例如,C 函数:

int main(int argc, char **argv);
在 Julia 中应该这样调用:
argv = [ "a.out", "arg1", "arg2" ]
    ccall(:main, Int32, (Int32, Ptr{Ptr{Uint8}}), length(argv), argv)
对于 wchar_t* 参数,Julia 类型为 Ptr{Wchar_t},并且数据可以通过 wstring(s) 方法转换为原始的 Julia 字符串(等同于 utf16(s)utf32(s) ,这取决于 Cwchar_t 的宽度)。还要注意 ASCII, UTF-8, UTF-16, 和UTF-32 字符串数据在 Julia 内部是以 NUL 结尾的,所以它能够传递到 C 函数中以 NUL 为结尾的数据,而不用再做一个拷贝。
联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2022

备案号:京ICP备15003423号-3