Jump to content
Yorgo

how to DllCall() for a function that returns a whole struct?

Recommended Posts

Yorgo

hello,

as far as I understood the documentation, "DllCall()" returns an array (32-/64-bit or variable sized?) values,
the first @ index 0 being the actual return value of the called function!?

but:
- what if that function does not only return any scalar value, but a whole struct??
how do I specify the DllCall wrapper for any such function??

 

Example:

<C-code>
struct ftdi_version_info version;

version = ftdi_get_library_version();
printf("Initialized libftdi %s (major: %d, minor: %d, micro: %d, snapshot ver: %s)\n",
       version.version_str, version.major, version.minor, version.micro, version.snapshot_str);

<Header>
struct ftdi_version_info
{
    int major;
    int minor;
    int micro;
    const char *version_str;
    const char *snapshot_str;
};

 

Share this post


Link to post
Share on other sites
Danyfirex

Hello. You need to do something like this.

 

Global Const $sTag_ftdi_version_info="int major;int minor;int micro;ptr  pversion_str;ptr psnapshot_str"

Local $aRet=DllCall("somedll.dll","ptr","ftdi_get_library_version")

Local $t_ftdi_version_info=DllStructCreate($sTag_ftdi_version_info,$aRet[0])
Local $tversion_str=DllStructCreate("char Data[256]",DllStructGetData($t_ftdi_version_info,"pversion_str"))

ConsoleWrite(DllStructGetData($t_ftdi_version_info,"major") & @CRLF)
ConsoleWrite(DllStructGetData($tversion_str,1) & @CRLF)

Saludos

Share this post


Link to post
Share on other sites
Yorgo

nope, doesn't work...

this 'ftdi_get_library_version()' does not return a pointer to the version structure, but the structure itself - you may have missed that...

should be something like DllCall("libftdi1.dll", "struct", "ftdi_get_library_version") then - but this together with the rest of your code does not give valid output

Share this post


Link to post
Share on other sites
jchd

Weird.


This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Share this post


Link to post
Share on other sites
RTFC

@Yorgo: You are confusing a library (.lib) with a dll. If the ftdi library is linked with a compiled object (say, in C# or C++), then the version struct is returned directly. However, from AutoIt you need to call the D2XX dll, as detailed here (Google is your friend):

http://www.ftdichip.com/Support/Documents/ProgramGuides/D2XX_Programmer's_Guide(FT_000071).pdf

and when calling that dll (function FT_GetLibraryVersion I think), you need to supply a pointer (LPDWORD) to your own struct, which the dll call can then fill with the version data. @Danyfirex is totally correct.

Share this post


Link to post
Share on other sites
jguinch

I'm not very familiar with DllCall and structures, but in the help file I see that the valid types list contains STRUCT. Can it be used as a return type ?

Local $aRet=DllCall("somedll.dll","struct","ftdi_get_library_version")

ConsoleWrite( "ver.major : " & DllStructGetData($aRet[0], 1) )

 

Share this post


Link to post
Share on other sites
RTFC

@jguinch: I guess it's possible in theory,:think: but it's a really bad idea (both in terms of performance and maintainability), and I know of only one example on the forum where something remotely like it was partially successful (and only because the returned item turned out not to be a struct after all). But I wouldn't expect AutoIt to allocate more than 8 bytes (64K for STR??) for DllCallresult[0], so anything over the maximum would likely be clipped (never tried it though).

In the OP's context it's a moot point, because the posted snippet uses a (compiled, inapplicable) library function that works differently from the dll equivalent that can be called from AutoIt.

Edited by RTFC

Share this post


Link to post
Share on other sites
Danyfirex

It would be easier if you share the library and documentation you're using...

 

 

Saludos

Share this post


Link to post
Share on other sites
Yorgo

I'm not confusing anything, it's taken directly from the DLL documentation - not LIB documentation, example code
- see here:
https://www.intra2net.com/en/developer/libftdi/documentation/

all of those "ftdi_xyz()" functions are DLL exports, see attachement

libftdi1_exports.png

more details:
- the piece of hardware is a simple Relais-Box, 8 ports that can be switched to either A or B, connected to the PC via parallel port
- this box worked perfectly in my old PC, but: I just got a new PC that does not have any parallel port anymore!
- thus this switch-box is connected via an USB-to-parallel adapter now which is controlled via libftdi1
- we have this kind of adapter operational with libftdi1 and a self-made C-sharp programm for our company's Testsuite
- running this whole Testsuite only to have a simple switch change position is overlloaded, that's where AutoIt comes into play
- so far without success...

what does work:
- when I disconnect the USB-Cable or change the Vendor-/Product-Id in "ftdi_usb_open()" to something not matching this adatapter, the call to ftdi_usb_open() fails
- when connected and the correct Ids are used, the call succeeds
-> so it does see the adapter already
but: I cannot read the current parallel port values nor change them
---
re-coding this example concerning ftdi_get_library_version() was just to verifiy that talking to the library works
- having this function returning the whole struct and not given a pointer to copy the information into is really bad, I totally agree
nothing I can do about it -- but it still is valid C code...

Edited by Yorgo
more info

Share this post


Link to post
Share on other sites
Tekk

A function cannot return a structure, not with conventional calling conventions, however they may return them by reference.  It's just not how x86 CPU's are designed work at low level, most return values are given back in CPU registers where a structure generally will not fit.  Just because the source code looks as if it does, that is not what the compiler is really doing.

ftdi_get_library_version accepts one parameter; a pointer to a ftdi_version_info structure.  It also uses cdecl calling convention.  I know this because I loaded the library into a debugger. 

The following example does work, I have tested it.  I chose to load the structure into an array for ease of display.

#include <Array.au3>

Global $g_hDll = DllOpen("libftdi1.dll")

If $g_hDll < 0 Then
    MsgBox(0x40000, "Error!", "Failed to load library!")
Else
    Global $g_aVersion = ftdi_get_library_version()

    _ArrayDisplay($g_aVersion)
EndIf

Func ftdi_get_library_version()
    Local $ftdi_version_info = DllStructCreate("INT major;INT minor;INT micro;PTR version_str;PTR snapshot_str")

    DllCall($g_hDll, "NONE:cdecl", "ftdi_get_library_version", "STRUCT*", $ftdi_version_info)

    Local $aReturn[5][2] = [["major"], ["minor"], ["micro"], ["version_str"], ["snapshot_str"]]

    $aReturn[0][1] = $ftdi_version_info.major
    $aReturn[1][1] = $ftdi_version_info.minor
    $aReturn[2][1] = $ftdi_version_info.micro
    $aReturn[3][1] = DllStructGetData(DllStructCreate("CHAR[255]", $ftdi_version_info.version_str ), 1)
    $aReturn[4][1] = DllStructGetData(DllStructCreate("CHAR[255]", $ftdi_version_info.snapshot_str), 1)

    Return $aReturn
EndFunc

 

Share this post


Link to post
Share on other sites
Yorgo

well, I wasn't referring to what the assembler does but purely from C-language point-of-view: here it absolutely is legal and does work to return a whole structure
- the assembler underneath actually inserts references to memcpy() when compiling that piece of code to copy the "return" value into the callers scope
  (but all the nasty details about that are way off-topic here)

coming to your example code:
the api-header looks differently to what you are using - how would anyone not knowing how to debug such low-level DLL calls come to the idea to just pass a pointer and completely ignore the given interface??

I will try it out and post any results as soon as I got 'em...

Share this post


Link to post
Share on other sites
Yorgo

<results>
yes, strange enough , it does work as in your example, weird tho...
- library reports to be "v1.0.6-gafb9082" for me, hmm... seems there is no Windows build for the newest version available :(

anyway, could you PLEASE also look up "ftdi_read_pins", "ftdi_read_data" and "ftdi_write_data" - with respect to how to call those functions from AutoIt??
- those are the functions to read the current Parallel-Port settings and to change them; that's all I need here

I updated your example as follows:

#include <Array.au3>

Global $g_hDll = DllOpen("libftdi1.dll")
Global $g_pContext = 0

If $g_hDll < 0 Then
   MsgBox(0x40000, "Error!", "Failed to load library!")
Else
   Global $g_aVersion = ftdi_get_library_version()
   Local $types = DllStructCreate("BYTE byte_val;INT int_val;")
   Local $ptrByte = DllStructGetPtr($types, "byte_val")
   Local $ptrInt = DllStructGetPtr($types, "int_val")

   $ret = DllCall($g_hDll, 'PTR:cdecl', 'ftdi_new')
   $g_pContext = $ret[0]
   MsgBox(0, "Debug", "ftdi_new=" & $g_pContext & "; error=" & @error)
   $ret = DllCall($g_hDll, "INT:cdecl", "ftdi_init", "PTR", $g_pContext)
   MsgBox(0, "Debug", "ftdi_init=" & $ret[0] & "; error=" & @error)

   _ArrayDisplay($g_aVersion)

   $ret = DllCall($g_hDll, "INT:cdecl", "ftdi_usb_open", "PTR", $g_pContext, "INT", 0x0403, "INT", 0x5121)
   MsgBox(0, "Debug", "ftdi_usb_open=" & $ret[0] & "; error=" & @error)

   $types.int_val = -1
   $ret = DllCall($g_hDll, "INT:cdecl", "ftdi_read_chipid", "PTR", $g_pContext, "INT_PTR", $ptrInt)
   MsgBox(0, "Debug", "ftdi_read_chipid=" & $ret[0] & ",id=" & StringFormat("%X", $types.int_val) & "; error=" & @error)
   $ret = DllCall($g_hDll, "INT:cdecl", "ftdi_set_bitmode", "PTR", $g_pContext, "BYTE", 0xff, "BYTE", 4)
   MsgBox(0, "Debug", "ftdi_set_bitmode=" & $ret[0] & "; error=" & @error)

   $types.byte_val = -1
   $ret = DllCall($g_hDll, "INT:cdecl", "ftdi_read_pins", "PTR", $g_pContext, "BYTE_PTR", $ptrByte)
   _ArrayDisplay($ret)
   MsgBox(0, "Debug", "ftdi_read_pins [error=" & @error & "] pins=" & $types.byte_val)
EndIf

Func ftdi_get_library_version()
   Local $ftdi_version_info = DllStructCreate("INT major;INT minor;INT micro;PTR version_str;PTR snapshot_str")

   DllCall($g_hDll, "NONE:cdecl", "ftdi_get_library_version", "STRUCT*", $ftdi_version_info)

   Local $aReturn[5][2] = [["major"], ["minor"], ["micro"], ["version_str"], ["snapshot_str"]]

   $aReturn[0][1] = $ftdi_version_info.major
   $aReturn[1][1] = $ftdi_version_info.minor
   $aReturn[2][1] = $ftdi_version_info.micro
   $aReturn[3][1] = DllStructGetData(DllStructCreate("CHAR[255]", $ftdi_version_info.version_str ), 1)
   $aReturn[4][1] = DllStructGetData(DllStructCreate("CHAR[255]", $ftdi_version_info.snapshot_str), 1)

   Return $aReturn
EndFunc

Everything up to where "ftdi_read_pins" is called is reasonable; i.e. I do get a Chip-Id different from 0xFFFFFFFF when the adapter is connected
- and equal to 0xFFFFFFFF when it is not, together with a return value indicating that there was an error (-3 here)

BUT:
the call to "ftdi_read_pins" acts weird:
- the return value from DllCall is not an array as it used to be anymore, but just '0'
- @error is '-1' - which is according to the documentation "unable to use the DLL file"

why can't I all of the sudden not use the Dll anymore, and why does DllCall not return an array anymore?

----
ah, the array only is returned in case of no-errors, is it not?
but still, what does "unable to use the DLL file" *mean*? I successfully used that exact DLL file multiple times before...

Edited by Yorgo

Share this post


Link to post
Share on other sites
LarsJ
Bilgus

I agree with LarsJ I think your hangeup is probably that BYTE_PTR

You might already have found these but 

http://libftdi.sourcearchive.com/documentation/0.18-1build1/group__libftdi_gb823dd90f2359612b1546ef0ceebdc0e.html

and 

https://github.com/legege/libftdi/blob/master/examples/bitbang_cbus.c

Should give Us (or You) a little more context

from the documentation it looks like a return of 0 from read_pins = OK

 

Share this post


Link to post
Share on other sites
Yorgo

thx for that hint
- I "thought" there was something like a BYTE_PTR since there are INT_PTR and LONG_PTR existing; my bad

BUT:
why does "INT_PTR" acts different from "INT*" - must that not be the same? (well, in C it is at last)
- e.g. if I replace in my code above that "INT_PTR" where i read the Chip-Id with an "INT*" I cannot read the Chip-Id anymore :(

as for reading the pins: "BYTE*" does not generate any weird DllCall() behavior anymore, but I cannot read the pins like this!

is it safe to just use a generic "PTR" here??

<update>
anyway - when using a simple "PTR" I now not only can read the current pins but also can change them!!!!
you people rock, thanx!

Edited by Yorgo

Share this post


Link to post
Share on other sites
Andreik

* in AutoIt is used when you pass a parameter by reference, so INT* means that you pass and integer data type by reference and INT_PTR means you pass an integer with respect to system architecture to hold a pointer.


When the words fail... music speaks

Share this post


Link to post
Share on other sites
Yorgo

hmmm... that is the same, is it not?

if a variable is passed by reference, it does mean the called function writes its content into the very same memory address that was assigned for this variable by the caller of that Dll-Funtion,
i.e. once the Dll-function returned, the caller does (should) see the updated value as written into the variable by the Dll function!
- this only works when using "INT_PTR" or the generic "PTR", but not for "INT *"
to me this looks like a bug...

Share this post


Link to post
Share on other sites
LarsJ

This has nothing to do with bugs. "INT_PTR" and "INT*" does not work in the same way. You've already seen how "INT_PTR" works very well with the storage you've created in the structure.

When you use "INT*" the storage for the integer is created internally by the DllCall function and the value of the integer (not the pointer) is returned in $ret[2]:

$ret = DllCall($g_hDll, "INT:cdecl", "ftdi_read_chipid", "PTR", $g_pContext, "INT*", 0)
; 0 is just a placeholder for the output parameter
Local $iChipid = $ret[2]

You can also do it this way:

Local $iChipid
$ret = DllCall($g_hDll, "INT:cdecl", "ftdi_read_chipid", "PTR", $g_pContext, "INT*", $iChipid)
; $iChipid is just a placeholder for the output parameter
$iChipid = $ret[2]

In your original code you'll see that $ret[2] = $ptrInt.

$ret = DllCall($g_hDll, "INT:cdecl", "ftdi_read_chipid", "PTR", $g_pContext, "INT_PTR", $ptrInt)

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×