imported function parameter question

Started by HPW, December 13, 2003, 11:10:08 AM

Previous topic - Next topic

HPW

I have a question to the import of function from DLL's:



I get for example the following doc for the DLL (Example from a C#-demo-source)



[DllImport("custom.dll")] static extern int customcommand(double dLeft, double dTop, double dWidth, double dHeight, int iPage, double dPosition, int iOptions, string sContents);


How are the typs of parameter handled in newlisp?

Can I or must I define them in any way?

How do I call the function with double, int?
Hans-Peter

Lutz

#1
Currently you only can pass arguments which are 32 bit wide, so for int and string you should be able to just plug in what you have in newLISP. Floats (doubles) will not work as entry arguments but you can get them back as return values.



The return values you can get with get-string, get-int and get-float (see the manual). Any other return value which is not 'int', 'float' or 'string' but some sort of structure, you could plug in the number returned from dllEvalStr into 'unpack' to get out the structure members or you add the offset of the structure member and then use get-integer/string/float(double) to get the members.





Lutz

Lutz

#2
Theoretically you should be able to pass doubles too by packing them first with 'pack' and then 'upack'-ing them into integers then passing them in the imported  function (two integers per double). Something like this:



(unpack "lu lu" (pack "lf" 1.23)) => (2061584302 1072934420)



I am not sure at the moment in which order the upper and lower portion of the double float goes on the calling stack. Ask you local assembly guru ;), I guess on Intel lower part before higher part.



In the forseeable future there will be no facility in newLISP to make this easier. The 'import' facility is mainly there to write database modules, import system calls from system libraries etc..



Basically anthing can be done the way things are now, if you are aware of how things work on the assembly language level on your particular platform and know calling (stack) conventions.



Lutz

HPW

#3
Since I am not a assembly guru ;) and no other local assembly guru is at hand, here a question, which was asked on the neobook forum:



DECLARE FUNCTION MessageBox LIB "USER32.DLL" ALIAS "MessageBoxA" (BYVAL hWnd AS DWORD, lpText AS ASCIIZ, lpCaption AS ASCIIZ, BYVAL dwType AS DWORD) AS LONG



That functions works with this:



(import "user32.dll" "MessageBoxA")

(MessageBoxA 0 "This is the body" "Caption" 1)



But the try with this:



DECLARE FUNCTION GetComputerName LIB "KERNEL32.DLL" ALIAS "GetComputerNameA" (lpBuffer AS ASCIIZ, nSize AS DWORD) AS LONG



(import "Kernel32.dll" "GetComputerNameA")

works so far.

But until now no way to call it without crash.

The difference might be the 'BYVAL' before the DWORD parameter.



Any idea?
Hans-Peter

Lutz

#4
By value means that a parameter is passed "by its value" rather than the address. In newLISP 'import' all integers and floats (yes there are floats now in upcoming release 7400) are passed by value as default. Only strings are passed by address as default. When you do a:



(import "some.lib" "foo")



and call:



(foo 1 2.34 "hello")



The the first parameter '1' will be passed by value the second '2.34. also as a value (internally to 32bit values are pushed on the stack) and "hello" by address, which means the 32bit string address will be pushed onto the stack.



These are the defaults. You also could pass an integer or float value by address doing the following:



(foo (pack "ld"  1) (pack "lf" 2.34) "hello")



So now everything is passed by address. This is very unusual for integers and floats but common and the default for strings.



When it says 'lpText' or 'lpCaption BY ASCIIZ' it means by address the 'ACIIZ' means that it is a ASCII string terminated by a 0  (zero). This works with newLISP, where all strings are zero terminated. newLISP string buffers can also be non ASCIIZ, which means they can have embedded zeroes. In this case the length of it can be retrieved by doing a '(length str)'.



All this was for parameters going into the function. Sometimes as in the case of GetComputerNameA, the function wants a space where to put the outgoing result. In this case you have to reserve space and tell it the length of the space you have reserved.



Let me show what I mean with an example using GetWindowsDirectoryA

(import "kernel32.dll" "GetWindowsDirectoryA")
(set 'str "                                                           ")
; reserve enough space, so the name fits
(GetWindowsDirectoryA str (length str))
str => "C:\WINDOWS00                      "

The second statement reserves space for the result coming back. When you do a 'print' on 'str' you will only see everything up to the 00 (zero). Or you can do a:



(slice str 0 (find "00" str)) => "C:\WINDOWS"



to extract it.



Doing the same for "GetComputerNameA" will crash the system, because in this case also the size of the str fields has to be passed as an address:



(import "kernel32.dll" "GetComputerNameA")

(set 'str "                                             ")  
; reserve enough space so the name fits

(set 'lpNum (pack "lu" (length str)))  ;; get size in a buffer lpNum

(GetComputerNameA str lpNum)       ;; call the function

str =>  "LUTZ-PC00                                     "

(slice str 0 (find "00" str)) => "LUTZ-PC"


The reason GetComputerNameA wants the size passed by 'LPDWORD' is that the field 'lpNum' serves as both an input and an output field (read the Win32 description of GetComputerName to understand why).



All this works already on 7.3.xx versions (except for pasi9ng floats). This is what you get in 7.4.0:



(1) floats can be passed (see 7.4.0 release manual for example)



(2) up to 14 parameters can be passed (floats count double)



Lutz



ps: 7.4.0 is ready I am still improving docs and it goes online on Wednesday/17th

HPW

#5
Lutz,



thanks for the detailed info!



>(yes there are floats now in upcoming release 7400)



Ho, Ho, Santa Claus seems to have something in his bag for us!

I thought we knew all about upcoming 7400.

Surprise, surprise ;-)
Hans-Peter

HPW

#6
Lutz,

I tried your examples.

Can it be that it does not work with 7.400 RC2?



I always get back integers.



> (GetComputerNameA str lpNum)

0



Oops, have not backup older versions, so can not go back and test.
Hans-Peter

HPW

#7
Found a 7317 on my memory-stick.



But it is the same:



newLISP v7.3.17 Copyright (c) 2003 Lutz Mueller. All rights reserved.

> (import "kernel32.dll" "GetComputerNameA")
GetComputerNameA <77E45F4C>
> (set 'str " ")
" "
> (set 'lpNum (pack "lu" (length str)))
"01000000"
> (GetComputerNameA str lpNum)
0
> (slice str 0 (find "00" str))

value expected : (find "00" str)

> (import "kernel32.dll" "GetWindowsDirectoryA")

GetWindowsDirectoryA <77E505B0>
> (set 'str " ")
" "
> (GetWindowsDirectoryA str (length str))

11
>


Tested on WIN/XP PRO german.
Hans-Peter

Lutz

#8
I just realize the spaces where mageled in the post:



(set 'str "                                      ") ; many spaces


I will also correct this in the original post. You need to reserve enough spaces so the result Windows will put into str.



And yes! of course you get back integers from GetWindowsDirectoryA or from

GetComputerNameA. Those are the result codes, '0' (zero) meaning it did fail, because tou did not allocate enought space! The result of the computer name or directory name is in 'str'. Read also the docs about GetWindowsDirectory and

GetComputerName from Microsoft about the return values!



Lutz

HPW

#9
Aah, now i see it!



Thanks again!



May be a command like this would be nice for newlisp in such case.



(define (repstr str num    newstr)
(setq newstr "")(dotimes(x num)(setq newstr(append newstr str))))


(repstr " " 100)   => 100x space
Hans-Peter

Lutz

#10
yes, that would work, or try this one:



(define (allocate n)
    (join (map (fn (x) " ") (sequence 1 n))))




Lutz

HPW

#11
Shorter = More Lispy ;-)



Great!



You win! (as always, still have to learn so much, but it is fun)
Hans-Peter

Sammo

#12
(define (repstr n char-index)
     (join (map char (series char-index 1 n))))

(repstr 10 32) -> "          " ; 10 spaces
(repstr 10 65) -> "AAAAAAAAAA"

HPW

#13
Also great.



The old autolisper has to change his old style-lisp.
Hans-Peter

Lutz

#14
Another qick note to the examples: instead of importing GetWindowsDirectory, you may be quicker doing a:



(getenv "SYSTEMROOT") => "C:\WINDOWS"



There is a whole bunch of important information in the Win32 environment. Find out about it with:



(environ) = > big list of environment strings coming here



Both calls also work on Linux/BSD, but of course with different results.



Lutz