Don't know how to pass a pointer of structure to C function

Started by csfreebird, May 15, 2014, 08:49:08 AM

Previous topic - Next topic

csfreebird

Today, I try to call MongoDB C Driver API  in newLISP code.

Below is this C function declaration :
char **
mongoc_database_get_collection_names (mongoc_database_t *database,
                                      bson_error_t      *error);


I have got database pointer successfully before,  but do not know how to pass a pointer as bson_error_t, bson_error_t is a struct in C defined like so:
typedef struct
{
   uint32_t domain;
   uint32_t code;
   char          message[504];
} bson_error_t;


My newlisp code snippets:
(import library "mongoc_database_get_collection_names" "void*" "void*" "void*")
(define (get_coll_names s e)
  (mongoc_database_get_collection_names s e))


I called the above function in another file, but got error:
(Mongo:get_coll_names s e)
ERR: value expected : nil
called from user defined function Mongo:get_coll_names

ryuo

#1
Although I am new to newlisp, I have experience with C. I believe the problem you are having is that the function you are trying to call expects a "pointer" to the struct. The struct must be allocated somewhere in memory first, and then you need to feed the address of the instance of that struct to the function. You probably need to retrieve the address via the 'address' function, documented here:



http://www.newlisp.org/downloads/newlisp_manual.html#address">http://www.newlisp.org/downloads/newlis ... ml#address">http://www.newlisp.org/downloads/newlisp_manual.html#address



It only gives examples with strings and integers, but it would be weird if it didn't support other types. This may or may not solve the problem, as it could be something else to do with lisp. But, I just noticed that it looked like you were trying to pass the struct and not a pointer to the struct.



Edit:



I also noticed upon a second look that you don't post code that converts the C struct. A look into the API reveals a struct function:



http://www.newlisp.org/downloads/newlisp_manual.html#struct">http://www.newlisp.org/downloads/newlis ... tml#struct">http://www.newlisp.org/downloads/newlisp_manual.html#struct



I think you also need to do this in addition to getting the address for the struct.

csfreebird

#2
Thanks for your advice. I tried a few minutes today. Get some progress with more questions.

In my mongo.lsp file:



(context 'Mongo)

(set 'files '(
     "/usr/local/lib/libmongoc-1.0.so"))

(set 'library (files (or
     (find true (map file? files))
     (throw-error "cannot find libmongoc library"))))

(import "/usr/local/lib/libbson-1.0.so")
(import "/usr/local/lib/libbson-1.0.so" "bson_new")
(import "/usr/local/lib/libbson-1.0.so" "bson_append_utf8")

(import library "mongoc_client_new" "void*")
(import library "mongoc_client_get_collection")
(import library "mongoc_database_get_collection_names" "void*" "void*" "void*")
(import library "mongoc_collection_find")

(define (get_coll_names s e)
  (mongoc_database_get_collection_names s e))

(define (connect url)
  (mongoc_client_new url))

;; return mongoc_collection_t
(define (get_coll session db_name coll_name)
  (mongoc_client_get_collection session db_name, coll_name))


In my test.lsp file, I wrote:

(load "mongo.lsp")
(set 'client (Mongo:connect "mongodb://127.0.0.1/"))
(set 'coll (Mongo:get_coll client "kaimei" "user"))

(struct 'bson_error
"int"
"int"
"char*" ;; size is 504
)

(set 'e (pack bson_error 0 0 ""))
(set 'names (Mongo:get_coll_names coll (address e)))
(println names)


When running test.lsp, I got output

2014/05/24 13:14:04.0658: [ 3305]:     INFO:      cluster: Client initialized in direct mode.
20444896


That means connection to mongo server is established successufly.

And it seems the get_coll_names returned char**, but I  do not know  how to  parse it into newlisp list.

ryuo

#3
Basically you need to dereference the pointer and apply pointer arithmetic. It's doable from newLISP, but this requires behavior which is tied to the size of pointers for the platform the interpreter is running on. For the platforms I know of, you need to know this:



32 bit X86 is 4 bytes and requires get-int function call

64 bit X86 is 8 bytes and requires get-long function call



Ideally, this is something that should be added to the interpreter's builtin functions. The user should at least have a portable way of determining this platform's pointer sizes.



First the C code of the compiled shared library:



char *xyz[] =
{
  "a",
  "b",
  0
};


Now the code for the newLISP code:



(import "./test.so" "xyz")

(constant 'PointerSize 8)

(constant 'get-pointer (fn (x) (get-long x)))

(set 'i 0)

(until (= (get-pointer (+ (address xyz) i)) 0)
(println (get-string (get-pointer (+ (address xyz) i))))
(set 'i (+ i PointerSize))
)


This is just a sample of how to get the strings. I am assuming that the database library you are using is passing an array of C strings, with the last one being a null pointer. If you are running newLISP on a 32 bit OS, you will need to change the PointerSize to 4 and the get-pointer function to use get-int instead.

Lutz

#4
Here another way to import char * string arrays using unpack. The format "lu" assumes that pointers are 32bit. The simple FFI syntax is used and the size of the array must be known:
; directly from the data

> (import "ret_array.so" "data")
data@6E842000
> (map get-string (unpack "lu lu lu" (address data)))
("This is" "an array" "of strings")
>

; via function returning a ptr array

> (import "ret_array.so" "ret_array")
ret_array@6E841240
> (map get-string (unpack "lu lu lu" (ret_array)))
("This is" "an array" "of strings")
>


and this is the C code:
char * data[] = {"This is", "an array", "of strings"};

char * * ret_array()
{
return(data);
}


For the extended FFI using libffi see the files

http://www.newlisp.org/newlisp-10.6.0/util/ffitest.c">http://www.newlisp.org/newlisp-10.6.0/util/ffitest.c and http://www.newlisp.org/newlisp-10.6.0/qa-specific-tests/qa-libffi">http://www.newlisp.org/newlisp-10.6.0/q ... /qa-libffi">http://www.newlisp.org/newlisp-10.6.0/qa-specific-tests/qa-libffi for many examples. qa-libffi containes code to compile ffitest.c to a library and works on most platforms - thanks to 'rickyboy' -. Remember that the extended FFI is only available when the word "libffi" appears in the newLISP start banner. The simple FFI used in above example is always available in any flavor of newLISP.