Page 1 of 1

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

Posted: Thu May 15, 2014 3:49 pm
by csfreebird
Today, I try to call MongoDB C Driver API in newLISP code.
Below is this C function declaration :

Code: Select all

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:

Code: Select all

typedef struct
{
   uint32_t domain;
   uint32_t code;
   char          message[504];
} bson_error_t;
My newlisp code snippets:

Code: Select all

(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:

Code: Select all

(Mongo:get_coll_names s e)
ERR: value expected : nil
called from user defined function Mongo:get_coll_names

Re: Don't know how to pass a pointer of structure to C funct

Posted: Wed May 21, 2014 4:53 pm
by ryuo
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/newlis ... ml#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/newlis ... tml#struct

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

Re: Don't know how to pass a pointer of structure to C funct

Posted: Sat May 24, 2014 5:16 am
by csfreebird
Thanks for your advice. I tried a few minutes today. Get some progress with more questions.
In my mongo.lsp file:

Code: Select all

(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:

Code: Select all

(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

Code: Select all

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.

Re: Don't know how to pass a pointer of structure to C funct

Posted: Sat May 24, 2014 8:57 pm
by ryuo
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:

Code: Select all

char *xyz[] =
{
  "a",
  "b",
  0
};
Now the code for the newLISP code:

Code: Select all

(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.

Re: Don't know how to pass a pointer of structure to C funct

Posted: Sun May 25, 2014 6:58 am
by Lutz
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:

Code: Select all

; 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:

Code: Select all

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 and http://www.newlisp.org/newlisp-10.6.0/q ... /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.