0

EDIT: Documentation for the relevant library methods!

Full disclosure - I know next to nothing about C/C++ and literally nothing about pointers and buffers and all that fun stuff. Apologies if this is a stupid question, but I've been working on this for hours and haven't got very far at all.

So I have an external C library that I'm referencing from a Ruby script. There's a bunch of different functions which I need to access, and I've got some simple ones working, but I'm struggling with a more complicated function that I need to work.

The good part - I have working example code in Python.

The challenge I'm facing is around the whole buffer side of things using FFI - I've not found a lot of relevant examples on how to make this work, and I'm struggling a bit.

Here's the function I want to replicate in Ruby using FFI:

def getActiveID32():
    """
    Will return a tuple consisting number of bits
    read and actual data. Minimum 8 byte of data will
    be returned.
    """
    rawData = ""
    buffer_size = ctypes.c_short(32);
    pcproxlib.GetActiveID32.restype = ctypes.c_short
    # create a buffer of given size to pass it
    # to get the raw data.
    raw_data_tmp = (ctypes.c_ubyte * buffer_size.value)()
    #as per documentation 250 millisecond sleep is required
    # to get the raw data.
    time.sleep(250/1000.0)
    nbBits = pcproxlib.GetActiveID32(raw_data_tmp , buffer_size)
    bytes_to_read = int((nbBits + 7) / 8) ;
    # will read at least 8 bytes
    if bytes_to_read < 8:
        bytes_to_read = 8

    for i in range(0 , bytes_to_read):
        temp_buf = "%02X " % raw_data_tmp[i]
        rawData = temp_buf + rawData
    return (nbBits , rawData)

Here's the example code that I have working for the simple functions (eg BeepNow but not for this more complicated function, getActiveID32.

Included is the code I've been playing with, but that is clearly too simplified, and doesn't work.

require 'ffi'

module PCProxLib
  extend FFI::Library
  ffi_lib File.expand_path('lib/32/libhidapi-hidraw.so.0')
  ffi_lib File.expand_path('lib/32/libpcProxAPI.so')
  attach_function :usbConnect, [], :short
  attach_function :getPartNumberString, [], :string
  attach_function :getActiveID32, [:string, :int], :int
  attach_function :getActiveID, [], :int
  attach_function :BeepNow, [:int, :bool], :bool
end

puts PCProxLib.usbConnect()

puts PCProxLib.BeepNow(3, false)

sleep 0.25
buffer = ""
puts PCProxLib.getActiveID32(buffer, 64)
puts buffer

Thanks heaps for any help :)

EDIT:

Based on @matt's comment, I've revised the code to the following:

require 'ffi'

module PCProxLib
  extend FFI::Library
  ffi_lib File.expand_path('lib/32/libhidapi-hidraw.so.0')
  ffi_lib File.expand_path('lib/32/libpcProxAPI.so')
  attach_function :usbConnect, [], :short
  attach_function :USBDisconnect, [], :short
  attach_function :getActiveID32, [:pointer, :int], :int
end

PCProxLib.usbConnect()

puts PCProxLib.getPartNumberString() // verifying connection to reader

def read_card
  sleep 0.5
  buffer = FFI::MemoryPointer.new(:uint8, 32)
  bits_written = PCProxLib.getActiveID32(buffer, 32)
  puts bits_written
  puts buffer.read_bytes(32)
end

20.times { read_card }

PCProxLib.USBDisconnect()

When I scan a card (while the code is looping), the bits_written value jumps from 0 to 32, which seems good. However the buffer.read_bytes(32) value is always null.

I've tried some other MemoryPointer methods (like read, read_string etc but get the same results.

If I try .inspecting the buffer.read_bytes(32), I get:

"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

...which is interesting? This is the same, whether I scan a card or not.

petjb
  • 1
  • 1
  • If the buffer contains zeros after the call then that would suggest the function is writing zeros. But I am wondering about the delay you are adding, surely it would make sense for that to be _after_ the call, to give it time to get and write the data? – matt Jul 07 '21 at 14:54
  • @matt yep agreed. Re the delay - good point. I tried moving the delay around, same result unfortunately. I've reached out to the manufacturer for clarification, hopefully they can shed some light... – petjb Jul 08 '21 at 03:39
  • @matt I've added a link to the relevant documentation at the top of the question, this may help clarify things? – petjb Jul 13 '21 at 02:15

1 Answers1

1

It looks like the function getActiveID32 has a signature that looks something like

int getActiveID32(uint8_t *buffer, int size)

and you would use it by first creating a buffer for it to write into, and then passing a pointer to that buffer and its size as arguments when you call it, something like:

// Define BUFFER_SIZE first.
uint8_t *buffer = malloc(BUFFER_SIZE);
int bits_written = getActiveID32(buffer, BUFFER_SIZE);
// There are now bits_written bits in buffer for you to use.
// You will need to call free on buffer when you are finished.

With Ruby’s FFI you can do this with a Pointer. First declare the function to accept a pointer (a string is like a pointer, but in this case we need to use a pointer directly):

attach_function :getActiveID32, [:pointer, :int], :int

Now when you want to call it, you need to first create a MemoryPointer of the desired size, and pass it as the parameter to the function. FFI will then convert this into a pointer to the allocated memory when calling the C function.

# Assuming you want a 32 byte buffer.
buffer = FFI::MemoryPointer.new(:uint8, 32)
bits_written = PCProxLib.getActiveID32(buffer, 32)

Now buffer will contain the data written by the function. You can access this data using the methods on MemoryPointer, for example to copy that data into a Ruby (binary) string you can use buffer.read_bytes(32).

MemoryPointer will also handle freeing the allocated memory when it is garbage collected.

matt
  • 78,533
  • 8
  • 163
  • 197
  • Please refer to the edit in the question because comment formatting seems beyond me today. Thanks very much for your help, it's hugely appreciated. – petjb Jul 07 '21 at 06:28