[plt-scheme] String as output parameter of FFI call to W32 API GetLogicalDriveStringsW?

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Mon Feb 1 21:56:17 EST 2010

No, `(_ptr o _string)' is suitable for a function that allocates a
UTF-8 string and returns it in a `char**', while the
GetLogicalDriveStringsW() function takes an already-allocated area to
fill with UTF-16 strings.

GetLogicalDriveStringsW() is one of those functions that's no fun to
call, whether you're in C or Scheme. It's probably best to first
GetLogicalDriveStringsW() with a zero-sized buffer, in which case it
tells you how big a buffer is needed. After a second call, you have to
pull out the UTF-16 strings that are written into the buffer.

I'd wrap the function like this:

 (define get-logical-drive-strings
   (let ([prim
          (get-ffi-obj "GetLogicalDriveStringsW" kernel32
                       (_fun #:abi 'stdcall
                             _uint32
                             _bytes
                             -> _uint32))])
     (lambda ()
       (let* ([len (prim 0 #f)]
              ;; Allocate buffer of needed size:
              [bstr (make-bytes (* (add1 len) 2))])
         (prim len bstr)
         ;; Pull out the strings:
	 (let loop ([offset 0])
	   (let ([s (cast (ptr-add bstr offset) _pointer _string/utf-16)])
	     (if (equal? s "") ;; empty string terminates the sequence
		 null
		 (cons s (loop (+ offset 
				  (* 2 (add1 (string-length s)))))))))))))

The use of `cast' is an easy (if a bit hack-ish) way to convert the raw
UTF-16 bytes into a Scheme string.


There's also `filesystem-root-list' from `scheme/base', which might be
all you want in this case. It filters the result of
GetLogicalDriveStrings() to leave out disks that can't be read at the
moment, though.


At Mon, 01 Feb 2010 19:17:26 -0700, Chognkai Zhu wrote:
> Use (_ptr o _string) to do that.
> 
> Chongkai
> 
> 
> Andrea Girotto wrote:
> > Hello,
> > I have a problem calling via FFI a Windows API function:
> > GetLogicalDrivesStringsW.
> >
> > First of all, if I write:
> >
> > #lang scheme
> > (require scheme/foreign)
> > (unsafe!)
> >
> > (define kernel32 (ffi-lib "kernel32"))
> >
> > (define get-logical-drive
> >   (get-ffi-obj "GetLogicalDrives" kernel32
> >                (_fun #:abi 'stdcall -> _uint32)
> >                (lambda () (error 'kernel32 "Missing GetLogicalDrives")) ) )
> >
> > (let ((drives (get-logical-drive)))
> >    (display drives)
> >    (display #\tab)
> >    (display (number->string drives 2))(newline) )
> >
> > the answer is:
> >
> > 262157  1000000000000001101
> >
> > So it is telling me that there are A:, C:, D: and S: (and FFI is
> > working! :-)). That's correct, and I have the same answer with:
> >
> >   
> >> (display (filesystem-root-list))(newline)
> >>     
> > (C:\ D:\ S:\)
> >
> > (the disk A: is missing because, in my opinion, the
> > "filesystem_root_list" function in src/mzscheme/src/file.c is checking
> > it with the "GetDiskFreeSpace" and there's absolutely no floppy in the
> > system...).
> >
> > Now I need to get the same information in another way (...long story:
> > at the end of this path I will enumerate the USB sticks connected to
> > the computer, that is a relatively simple task with Linux, but such an
> > incredible complex task with Windows API, maybe it's only me and my
> > inexperience with Windows...):
> >
> > (define get-logical-drive-strings-w
> >   (get-ffi-obj "GetLogicalDriveStringsW" kernel32
> >                (_fun #:abi 'stdcall
> >                      _uint32
> >                      _string
> >                      -> _uint32)
> >                (lambda () (error 'kernel32 "Missing
> > GetLogicalDriveStrings")) ) )
> >
> > (define get-logical-drive-strings
> >   (let* ((n 128)
> > 	 (s (make-string n #\~)))
> >     (display (get-logical-drive-strings-w 128 s))(newline)
> >     (display s)(newline)
> >     (display (string->list s))(newline) ) )
> >
> > I'm filling 128 chars with the "~" character before calling the
> > GetLogicalDriveStringsW.
> > This Windows API should return the number of characters written in the
> > string. Instead, the answer is:
> >
> > 16
> > 
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> ~
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > (~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ 
> ~ ~
> >  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ 
> ~ ~
> >  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ 
> ~ ~
> >  ~ ~ ~ ~ ~ ~ ~ ~)
> >
> > The "16" number is the GetLogicaDriveStringsW that it is telling me
> > having written 16 characters...
> > But in the string there is only the sequence of "~" that I used to
> > initialize it...
> >
> > My suspect :-) is that I cannot call a FFI function using a Scheme
> > string as output parameter.
> >
> > BTW: the "filesystem_root_list" function in src/mzscheme/src/file.c is
> > calling "GetLogicalDriveStrings" to list the filesystems' roots. I
> > think that "GetLogicalDriveStrings" is a C/C++ macro of
> > "GetLogicalDriveStringsW" or "GetLogicalDriveStringsA", because the
> > first one is triggering the error message of "get-ffi-obj", the others
> > two are not...
> >
> > So how can I pass a string as output parameter (to be filled from
> > GetLogicalDriveStringsW) and read it back?
> >
> > Thank you,
> > Best regards,
> > Andrew.
> > _________________________________________________
> >   For list-related administrative tasks:
> >   http://list.cs.brown.edu/mailman/listinfo/plt-scheme
> >   
> 
> _________________________________________________
>   For list-related administrative tasks:
>   http://list.cs.brown.edu/mailman/listinfo/plt-scheme


Posted on the users mailing list.