用Julia从C的内存地址读/写结构体数据

4
我正在将我的应用程序嵌入到Julia中,我需要一种很好的方法来从Julia和C++读/写相同的结构。
在Python中,我可以简单地这样做:
ffi.cdef("""
    struct keyboard_s {
        int forward;
        int backward;
        int left;
        int right;
        int jump;
    }

    struct keyboard_s *app_get_keyboard();
"""

app = ffi.dlopen("app.dll")

thekeyboard = app.app_get_keyboard();

thekeyboard.forward = 1; # this would immediatly change the memory in C

然而,我在 Julia 中尝试类似的操作,但 Julia 总是只能复制数据,无法改变来自 C 的外部内存地址。
type keyboard_s
    forward::Int32
    backward::Int32
    left::Int32
    right::Int32
    jump::Int32
end

# lets imply this would return the memory struct just like app_get_keyboard()
# I just use malloc(sizeof(keyboard_s)) so everybody here can test for themselves...
address = ccall(:malloc, (Int64), (Int64, ), sizeof(keyboard_s))

# address is now a valid Int64 address, so lets map it as pointer of type keyboard_s
ptr = Ptr{keyboard_s}(address)

# thekeyboard contains now the random data from the c static memory
thekeyboard = unsafe_load(ptr)

# this will change only the value of "thekeyboard",
# it doesn't touch C the Int64 address memory pointer...
thekeyboard.forward = 123 # this has no effect on the real memory address :(

# lets load the keyboard again from same address
thekeyboard = unsafe_load(ptr)

thekeyboard.forward == 123 # this is false! no effect whatsoever in C memory from Julia

我该如何在Julia中共享结构体的内存地址与C?
2个回答

2

好的,我想出了一种不太繁琐的方式。我基本上通过使用符号来重载[]运算符来访问结构字段,像这样:

thekeyboard[:forward] = 123

代码:

function offsetof(type_, member::Symbol)
  for (i, item) in enumerate(fieldnames(type_))
    if item == member
      return fieldoffset(type_, i)
    end
    #print(typeof(i))
  end
  # what to do when symbol not in type_?
  throw("$type_ has no member named $member")
end

function GetStructType(type_, member::Symbol)
  for (i, item) in enumerate(fieldnames(type_))
    if item == member
      return fieldtype(type_, i)
    end
    #print(typeof(i))
  end
  # what to do when symbol not in type_?
  throw("$type_ has no member named $member")
end

function Base.getindex(ptr::Ptr{T}, s::Symbol) where {T}
  address = UInt(ptr)
  if address == 0
    throw("Base.getindex(Ptr::{$T}) would dereference a NULL pointer")
  end
  offset = offsetof(T, s)
  fieldtype = GetStructType(T, s)
  fieldptr = Ptr{fieldtype}(address + offset)
  #log("Symbol $s $ptrtype address=$address offset=$offset fieldtype=$fieldtype ptr=$ptr fieldptr=$fieldptr\n")
  #return 123
  return unsafe_load(fieldptr)
end

function Base.setindex!(ptr::Ptr{T}, value, s::Symbol) where {T}
  address = UInt(ptr)
  if address == 0
    throw("Base.setindex!(Ptr) would write to a NULL pointer")
  end
  offset = offsetof(T, s)
  fieldtype = GetStructType(T, s)
  fieldptr = Ptr{fieldtype}(address + offset)
  #log("Symbol $s $ptrtype address=$address offset=$offset fieldtype=$fieldtype ptr=$ptr fieldptr=$fieldptr\n")
  unsafe_store!(fieldptr, value)
  return value
end

1
那是真的,但你可以使用修改后的副本将其复制到指定的指针/地址。使用你的方法,添加的步骤将是:
thekeyboard = unsafe_load(ptr)  #> keyboard_s(62752576, 0, 1836674671, 1601402223, 909193782)
thekeyboard.forward = 123;       
unsafe_store!(ptr, thekeyboard);
thekeyboard = unsafe_load(ptr)  #> keyboard_s(123, 0, 1836674671, 1601402223, 909193782)

是的,我知道这一点,但我不想因为一个字段的值被更新就复制整个结构体。而且在线程方面也有问题。我刚刚完成了自己的自定义函数来访问单个字段,稍后会发布。 - kungfooman
1
哦,我明白你的意思了。你想要一个共享模型,而不仅仅是访问。在这种情况下,是的,你可能需要直接将新值分配给内存中类型的字段。 - Tasos Papastylianou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接