ccall调用的c++函数需要填入struct和struct数组作为出参应该怎么填?

我用到的C的结构体定义如下:

typedef struct t_HDataField {
  char field_name[32];  //字段名
  int field_type;            //字段类型
  int field_op;
  int field_size;             //字段长度
  int field_flags; 
} HDataField;

typedef struct t_HDataType {
  char type[32]; 
  int field_count;                            /// 字段数量
  HDataField fields[512];              /// 字段定义数组
  int data_size;                              /// 数据类型长度,即所有字段长度的和
} HDataType;

typedef struct t_HDataItem {
  char symbol[24];           /// 标的代码
  int index;                       /// 索引
  int trading_day;                            /// 日期
  int64_t local_time;                         /// Unix时间
  int time_point_seq_no;                ///序号
  int type_id;                                /// 数据类型id
  const uint8_t* data;                  /// 指向数据内容的指针
} HDataItem;

typedef struct t_HCodeInfo {
  char symbol[24];           /// 标的代码
  int index;                       /// 索引
  int total_items_num;        /// 该代码在数据文件中总记录数
  int type_items_nums[24];  /// 该代码在数据文件中每种数据类型的记录数
  const uint8_t* data;                        /// 指向代码信息内容的指针(可能为空指针)
} HCodeInfo;

我想调用的dll中的函数如下:

 * 打开给定路径的数据库文件。
 *
 * @param db_id         数据库句柄
 * @param path          文件路径,即相对于根目录的文件相对路径
 * @param ci_type       代码信息数据类型定义,输出参数。
 * @param data_types    数据类型定义数组,输出参数。
 * @param count         数据类型定义数量。
 *                      输入时为输入的data_types数组长度,输出为返回的数据类型数量。
 *                      若输入时该值比需要返回的数量小时,接口返回HRetCode_NotEnoughMemory
 *
 * @return              成功返回HDB文件句柄,失败返回0
 */
uint64_t hdb_open_file(uint64_t db_id, const char* path, int flags,
                               HDataType* ci_type, HDataType* data_types,
                               int* count);

我把它们按如下方式在julia里面定义好了,

struct HDataField
  field_name::NTuple{32, Char}
  field_type::Cint
  field_op::Cint
  field_size::Cint
  field_flags::Cint
end

struct HDataType
  type::NTuple{32, Char}
  field_count::Cint
  fields::NTuple{512,HDataField}
  data_size::Cint
end

struct HDataItem
  symbol::NTuple{24, Char}
  index::Cint
  trading_day::Cint
  local_time::Int64
  time_point_seq_no::Cint
  type_id::Cint           
  data::Ptr{UInt8}
end

struct HCodeInfo
  symbol::NTuple{24,Char}
  index::Cint
  total_items_num::Cint
  type_items_nums::NTuple{64,Cint}
  data::Ptr{UInt8}
end

并打算按如下方式用julia的ccall来调用:

hdb_open_file = Libdl.dlsym(lib, :hdb_open_file)
db = 162994
md_types = Vector{HDataType}(undef, 16)
type_num = 16
ci_type = md_types[0]
md_type_r = Ref(md_types)
type_num_r = Ref(type_num)
ci_type_r = Ref(type_num)
fileid = ccall(hdb_open_file, UInt64, (UInt64, Ptr{UInt8}, Cint, Ref{HDataType}, 
Ref{HDataType}, Ref{Cint}), Int(db), 
"marketdata/tick_20201225", 0, ci_type_r, md_type_r, type_num_r)

结果运行后报错如下:

ERROR: MethodError: Cannot `convert` an object of type Base.RefValue{Vector{HDataType}} to an object of type HDataType
Closest candidates are:
  convert(::Type{T}, ::T) where T at C:\Users\linan\AppData\Local\Programs\Julia-1.7.2\share\julia\base\essentials.jl:218
  HDataType(::Any, ::Any, ::Any, ::Any) at REPL[58]:2
Stacktrace:
 [1] Base.RefValue{HDataType}(x::Base.RefValue{Vector{HDataType}})
   @ Base .\refvalue.jl:8
 [2] convert(#unused#::Type{Ref{HDataType}}, x::Base.RefValue{Vector{HDataType}})
   @ Base .\refpointer.jl:104
 [3] cconvert(T::Type, x::Base.RefValue{Vector{HDataType}})
   @ Base .\essentials.jl:417
 [4] top-level scope
   @ .\REPL[85]:1

问题究竟出在哪里? 跪求高手过来看看

ccall的第三个参数里,不需要考虑用Ref/Ptr,一律用Ptr是没有问题的。

(UInt64, Ptr{UInt8}, Cint, Ref{HDataType}, Ref{HDataType}, Ref{Cint})

(UInt64, Ptr{UInt8}, Cint, Ptr{HDataType}, Ptr{HDataType}, Ptr{Cint})

1 个赞

全都换成Ptr还是会有这个错误

ERROR: MethodError: no method matching unsafe_convert(::Type{Ptr{HDataType}}, ::Base.RefValue{Vector{HDataType}})
Closest candidates are:
  unsafe_convert(::Type{Ptr{T}}, ::SharedArrays.SharedArray{T}) where T at C:\Users\linan\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\SharedArrays\src\SharedArrays.jl:361
  unsafe_convert(::Type{Ptr{T}}, ::SharedArrays.SharedArray) where T at C:\Users\linan\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\SharedArrays\src\SharedArrays.jl:362
  unsafe_convert(::Type{Ptr{T}}, ::Base.ReinterpretArray{T, N, S, A} where {N, A<:(AbstractArray{S})}) where {T, S} at C:\Users\linan\AppData\Local\Programs\Julia-1.7.2\share\julia\base\reinterpretarray.jl:315
  ...
Stacktrace:
 [1] top-level scope
   @ .\REPL[93]:1
hdb_open_file = Libdl.dlsym(lib, :hdb_open_file)
db = 162994
data_types = Vector{HDataType}(undef, 16)
count_ref = Ref{Cint}(length(data_types))
ci_type = pointer(data_types)
fileid = ccall(hdb_open_file, UInt64, (UInt64, Ptr{UInt8}, Cint, Ptr{HDataType}, 
Ptr{HDataType}, Ptr{Cint}), db, 
"marketdata/tick_20201225", 0, ci_type, data_types, count_ref)

这个 Ref 不等价于 C 的 &, 再好好看看文档吧。

1 个赞

Gnimuc真的是高手啊,太厉害了。最后我有一点疑问,

uint64_t hdb_open_file(uint64_t db_id, const char* path, int flags,
                               HDataType* ci_type, HDataType* data_types,
                               int* count);

在CPP中使用这个函数,ci_type的作用其实是出参,我只需要创建一个HDataType类型的变量,并把它的地址传过去就可以了,这个变量不需要初始化的。在julia里面,我如果用:

ci_type = HDataType(undef)

来创建一个未初始化的结构体就会报错:

Closest candidates are:
  HDataType(::Any, ::Any, ::Any, ::Any) at REPL[2]:2
  HDataType(::NTuple{32, Char}, ::Int32, ::NTuple{512, HDataField}, ::Int32) at REPL[2]:2
Stacktrace:
 [1] top-level scope
   @ REPL[5]:1

julia难道不支持建立一个未初始化的结构体吗?
但是对于HDataType型的数组md_types又明明可以用

md_types = Vector{HDataType}(undef, 16)

来得到一个未初始化的HDataType型的数组,这里让我很疑惑。

ci_type = HDataType(undef)

ci_type = Ref{HDataType}()

1 个赞

谢谢,完美解决我的问题。