imported function parameter question

Q&A's, tips, howto's
Post Reply
HPW
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

imported function parameter question

Post by 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)

Code: Select all

[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
Posts: 5282
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

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
Posts: 5282
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

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
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

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
Posts: 5282
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

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

Code: Select all

(import "kernel32.dll" "GetWindowsDirectoryA")
(set 'str "                                                           ") 
; reserve enough space, so the name fits
(GetWindowsDirectoryA str (length str))
str => "C:\\WINDOWS\000                      "
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 \000 (zero). Or you can do a:

(slice str 0 (find "\000" 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:

Code: Select all

(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-PC\000                                     "

(slice str 0 (find "\000" 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
Last edited by Lutz on Tue Dec 16, 2003 7:25 pm, edited 2 times in total.

HPW
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

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
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

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
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

Found a 7317 on my memory-stick.

But it is the same:

Code: Select all

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))) 
"\001\000\000\000"
> (GetComputerNameA str lpNum)
0
> (slice str 0 (find "\000" str)) 

value expected : (find "\000" str)

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

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

11
> 
Tested on WIN/XP PRO german.
Hans-Peter

Lutz
Posts: 5282
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

I just realize the spaces where mageled in the post:

Code: Select all

(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
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

Aah, now i see it!

Thanks again!

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

Code: Select all

(define (repstr str num    newstr)
	(setq newstr "")(dotimes(x num)(setq newstr(append newstr str))))
(repstr " " 100) => 100x space
Hans-Peter

Lutz
Posts: 5282
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

yes, that would work, or try this one:

Code: Select all

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

Lutz

HPW
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

Shorter = More Lispy ;-)

Great!

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

Sammo
Posts: 180
Joined: Sat Dec 06, 2003 6:11 pm
Location: Loveland, Colorado USA

Post by Sammo »

Code: Select all

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

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

HPW
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

Also great.

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

Lutz
Posts: 5282
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

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

HPW
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

Sam,

Shouldn't it be:

Code: Select all

(define (repchr n char-index) 
     (join (map char (series char-index 1 n)))) 

(repchr 10 32) -> "          " ; 10 spaces 
(repchr 10 65) -> "AAAAAAAAAA"
because:

Code: Select all

> (repstr "AB" 10)
"ABABABABABABABABABAB"
All 3 functions have their use.
I think Lutz allocate fits best for init the varspace for OS-calls,
because the function name stand for its use.
Hans-Peter

HPW
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

>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)

Have not found an example for float-support in the manual of RC3.
Can you provide one here?
Hans-Peter

Lutz
Posts: 5282
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

actually there is one:

(printf "%g %s %d %c\n" 1.23 "hello" 999 65) ; the 1.23 is a float

it is so transparent to the user now, that it is easily overlooked :-)

I am glad the DLL has such a good work-out with you, I hopt that the 14 pareameter slots are enought. I remember in one of your posts you needed 5*2 + 3 = 13 already. I could have gone to 16. But just going from 10 to 14 added about 500 bytes to the executable.

Also, for initializing memory Sam's version seems to be the best (fastest) to me:

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

I guess the original point of this 'initialize memory contest' for me is that a solution with 'dotimes' is most of the time not necessary in LISP, 'map' is much faster and elegant.

Lutz

HPW
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

After a quick look in the API-reference of one possible target-DLL I check for max parameter:

Code: Select all

function AddNoteAnnotation(Left As Double,
Top As Double,
AnnotType As Long,
PopupLeft As Double,
PopupTop As Double,
PopupWidth As Double,
PopupHeight As Double,
Title As String,
Contents As String,
Red As Double,
Green As Double,
Blue As Double,
Open As Long) As Long
9 x double and 4 other.
But the question is, if it is usefull to make newLISP so capabel, or use another wrapper DLL to translate from lesser parameter to this rarly used functions. Most functions can be used with 14 or 16 parameter.
Hans-Peter

Lutz
Posts: 5282
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

what I need is some 'C' function which looks like

vacall(*functionPtr, *args[])

then I could write something general for as many parameters as you want. But how things are at the moment, I have to code (see nl-import.c ):

result = (*function)(args[0], args[1] ...... );

for every n from 0 to 14 (or 9*2 + 4 = 22 in your post), and it wastes a lot of space for a functionality rarely used.

there are the va_list(), va_start(), va_arg() macros but that doesn't help me if I don't have a function to bring the args on the call stack.

does anybody have an idea?

Lutz

HPW
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

>But just going from 10 to 14 added about 500 bytes to the executable.

So does we speak about 1 KB for 14 to 22 ?

>for every n from 0 to 14 (or 9*2 + 4 = 22 in your post),
>and it wastes a lot of space for a functionality rarely used.

Lot of space is relativ, because newLISP is so incredibel small.
Of cource you are right. When it is so rarly used by the majority
of users, it must give other workarounds through wrapper-dlls.

>then I could write something general for as many parameters as you want.

General solution are always the best. :-) But they must be possible. :-(

>does anybody have an idea?

My C knowledge is only basic and far away from a professional level.
In my used programming enviroments I mostly do not think about stacks.
(Only when I get a stack overflow)
Thats why I love lisp in all flavours! The enviroments care for me.
Hans-Peter

HPW
Posts: 1377
Joined: Thu Sep 26, 2002 9:15 am
Location: Germany
Contact:

Post by HPW »

Things get going. After the promising last enhancements, I ordered yesterday a licence for a PDF-DLL.

Code: Select all

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

> (import "iSEDQuickPDF.DLL" "iSEDNewDocument")
iSEDNewDocument <117D49C>
> (import "iSEDQuickPDF.DLL" "iSEDUnlockKey")
iSEDUnlockKey <117D3DC>
> (import "iSEDQuickPDF.DLL" "iSEDDrawText")
iSEDDrawText <117DE70>
> (import "iSEDQuickPDF.DLL" "iSEDSaveToFile")
iSEDSaveToFile <117D568>

> (iSEDUnlockKey "your license key here")
1
> (iSEDNewDocument)
18493748
> (iSEDDrawText 100.0 500.0 "Hello World")
1
> (iSEDSaveToFile "C:/temp/iSEDtest1.pdf")
1
> 
Bingo, PDF generated with newlisp.

Seems very powerfull and affordable (65$).

http://www.sedtech.com/isedquickpdf/?page=home

PS: This was the package with the max.22 slots!
Hans-Peter

Post Reply