Jump to content

String pointer passed to CallDll ?


Recommended Posts

Hello,

I'm a newcomer to AutoIt. I wish to use AutoIt as quick test bank for Delphi DLLs which I'm developing quite heavily, as supplements to a freeware Basic clone. My development environment is MS Windows XP Pros SP3, with Delphi 6 Personal Edition.

Now, some of my DLL functions need a string pointer as inpur parameter. The function than modifies the string in-memory, using de passed string pointer as address to the first data bye of the string. I made certain that enough space is reserved where the pointer points to, so there is no risk of data corruption. All my functions return a single 32 bit signed integer value.

The first check I tried, tries to call a function defined in Delphi as follows:

function KGFdllVersion(pout: ptruint):integer; stdcall; export;

Thus, my function accepts a pointer pout to the first byte of a string (25 bytes fixed length). A 25 byte Ascii identification string will be returned ad that address. Ands the function will return a numeric integer value (it will actually be 190).

So, after looking at the documentation, I came up with the following script:

Local $dll, $ver, $sver
$sver = "1234567890123456789012345"+chr(0)
Local $stString = DllStructCreate("char v[26]")
DllStructSetData($stString, 1, $sver)
$dll = DllOpen("AutoIt_test.dll")
$ver = DllCall($dll, "int", "KGFdllVersion", "ptr", DllStructGetPtr($stString))
DllClose($dll)
MsgBox(0,"",$ver[0])
MsgBox(0,"",DllStructGetData($stString,1))

The initial value of $sver is just a space holder, with a security string stop at the end. I discovered that I fave to use the DllStruct commands to produce a real pointer. But problem: I only get nice memory violations. Calling the same function with the target Basic langage works just file, so the DLL isn't the problem. Just for information, here is the calling code in the Basic program:

dim ver%, sver$
sver$ = "1234567890123456789012345"
dll_on "AutoIt_test.dll"
ver% = dll_call1("KGFdllVersion",adr(sver$))
message str$(ver%)+" = " + sver$

When I replace line 6 with

$ver = DllCall($dll, "int", "KGFdllVersion", "str*", $stString)

there is no more crash, but the reserved space is unchanged. Obviously, the text string written by the DLL function goes somewhere, but not into my string variable.

Now, the question is: what have I missed ? I checked within the DLL function: both version do crash before I even can display the firct check message in the dll function. replacing "str*" with "ptr* (nonsens, I know), there is no more crash, the returned integer value is correct, but obviously the value of $stString hansn't be changed.

Link to comment
Share on other sites

Result: the fonction is called, there is no crash, the numerical value returns fine, but I don't have the string modified through the pointer. This is obvious, because in your proposition, you didn't pass anything as pointer - just an empty string.

I uploaded a ZIP file with all relevent information here: http://www.4shared.com/zip/A2A9K4oz/AutoIt_test.html

This file contains: http://www.4shared.com/photo/phOKfVyb/AutoIt_test_content.html

AutoIt_test.dbr and TextToPanoramic.pas are the Delphi sources of a working test version of my DLL.

Auto_It_test.dll is the corresponding DLL.

Use_AutoIt_test.bas and Use_AutoIt_test.exe are source and executable of a working basic program showing what the result should be.

test.au3 is the source of my AutoIt script. There are 3 tenatives to call the DLL function. The first two are commented out, the third one is your suggestion

What it should do: pass a pointer (the memory address) of the first data byte os $sver as parameter to the function. The function will override 25 bytes starting at that memory address.

Link to comment
Share on other sites

From helpfile:

str an ANSI string (a minimum of 65536 chars is allocated).

If you pass an empty string, an pointer is generated. You receive the result of the 'out' parameter in the returning array.

Programming today is a race between software engineers striving to
build bigger and better idiot-proof programs, and the Universe
trying to produce bigger and better idiots.
So far, the Universe is winning.

Link to comment
Share on other sites

I can not open your links on this PC. But this method should work! Maybe your DLL doesn't work fine.

Here an working ByRef example for strings and dword in DllCall:

Global $aRet = DllCall("kernel32.dll", "BOOL", "GetComputerName", "str", "", "DWORD*", 65535)
MsgBox(64, "GetComputerName", "ComputerName: " & $aRet[1] & @CRLF & "Length: " & $aRet[2] & " characters")

Programming today is a race between software engineers striving to
build bigger and better idiot-proof programs, and the Universe
trying to produce bigger and better idiots.
So far, the Universe is winning.

Link to comment
Share on other sites

Sorry, the DLL is working fine. Have a look at this picture. It shows the Basic program using the DLL, and the output of both the returned integer value AND the string modified through its address specified via adr(sver$):

http://www.4shared.com/photo/57zKjAyY/Result_In_Basic.html

I intercepted, within the DLL, the value passed as parameter and which is supposed to be a string address. Yes, there is some non-zero value, but this value does NOT point to the first byte of the string ! I don't know where it points, but not where it's supposed to.

Link to comment
Share on other sites

This will be my last post until 28 December 2012 - 01:05 AM. The forum is blocking me !

So, I'll try to be as clear as possible.

I stopped the DLL execution right un entry into the function. And I dumped the memory starting at the address passed as first (and only) parameter.

Now, have a look on the following AutoIt source:

Local $dll, $ver, $sver
$sver = "1234567890123456789012345"+chr(0)

Local $stString = DllStructCreate("char v[26]")
DllStructSetData($stString, 1, $sver)

$dll = DllOpen("AutoIt_test.dll")
$ver = DllCall($dll, "int", "KGFdllVersion", "ptr", DllStructGetPtr($stString))
;$ver = DllCall($dll, "int", "KGFdllVersion", "str*", $stString)
;$ver = DllCall($dll, "int", "KGFdllVersion", "str", "")
DllClose($dll)
MsgBox(0,"","result=" & $ver[0] & @crlf & "param1=" & $ver[1])
MsgBox(0,"",DllStructGetData("returned string=" & $stString,1))

When I use one of the two lines commented out, the address points to a random memory area, There is no meaningfull data. And in neither case, the data I write into memory starting at this address will be returned in $ver[1].

With the shown version (using the DllStruct construction tu build a pointer, the situation seems slightly better, because the address points effectively to the first byte ou my data loaded into $sver. So, this seems quite nice. The pointer is OK.

But (and unfortunately there are 2 BUT's):

1. after the first byte of my data, a byte width déciumal value 46 is inserted. My other data bytes follow normally. So, I get 1 . 2 3 4 5 etc instead of 1 2 3 4 5 etc. Strange, isn't it ?

2. while I cand read the data, I can't write into it. AutoIt crashes, and the information is in this picture:

http://www.4shared.com/photo/egX3Zk98/AutoIt_crash.html

Link to comment
Share on other sites

So, I get 1 . 2 3 4 5 etc instead of 1 2 3 4 5 etc. Strange, isn't it ?

Well, normally this would be strange but as long we cannot see how this function really work I wouldn't say this is strange. Actually for some reason after calling this function in that way you have no longer a string but a number in scientific notation.

If you say the parameter is a pointer to a string your first example call should work. But returning no meaningfull data if I were you I would check the function, probably something is wrong.

When the words fail... music speaks.

Link to comment
Share on other sites

These two works for me:

$ver = DllCall($dll, "int", "KGFdllVersion", "ptr*", DllStructGetPtr($stString))

or

$ver = DllCall($dll, "int", "KGFdllVersion", "str*", $sver)

Also remove the Chr(0). I think you meant to concatenate it (...45" & Chr(0)), not add it (math is a harsh mistress), but it doesn't seem to be needed anyway.

Link to comment
Share on other sites

  • Moderators

klausgunther,

This will be my last post until 28 December 2012 - 01:05 AM. The forum is blocking me !

Not any more - I have lifted the restriction for you. :)

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

@AdmiralAlkex:

your hint was decisif - the "+chr(0)" was clearly a newbe error. At least, I should have coded "& chr(0), just as I did it in my MsgBox statements.

With that modification, and

$ver = DllCall($dll, "int", "KGFdllVersion", "str*", $sver)

It works well. I recover the result string in $ver[1]. But

$ver = [url="http://www.autoitscript.com/autoit3/docs/functions/DllCall.htm"]DllCall[/url]($dll, "int", "KGFdllVersion", "ptr*", [url="http://www.autoitscript.com/autoit3/docs/functions/DllStructGetPtr.htm"]DllStructGetPtr[/url]($stString))

doesn't work - the data of the first element os stString is NOT overwritten !

Here is my new AutoIt code:

Local $dll, $ver, $sver
$sver = "1234567890123456789012345"

Local $stString = DllStructCreate("char v[25]")
DllStructSetData($stString, 1, $sver)

$dll = DllOpen("AutoIt_test.dll")

; *** working code
$ver = DllCall($dll, "int", "KGFdllVersion", "str*", $sver)
$sver = $ver[1]
MsgBox(0,"","A: result=" & $ver[0] & @crlf & "param1=" & $ver[1] & "  $sver=" & $sver)

; *** NOT working code - the data element 1 of $stString is NOT overwritten !
$ver = DllCall($dll, "int", "KGFdllVersion", "ptr*", DllStructGetPtr($stString))
MsgBox(0,"","B: result=" & $ver[0] & @crlf & "$stString=" & DllStructGetData($stString, 1, $sver))

DllClose($dll)

@Andreik:

here is the Delhi code. First the AutoIt_test.dpr:

library AutoIt_test;
{ Remarque importante concernant la gestion de mémoire de DLL : ShareMem doit
  être la première unité de la clause USES de votre bibliothèque ET de votre projet
  (sélectionnez Projet-Voir source) si votre DLL exporte des procédures ou des
  fonctions qui passent des chaînes en tant que paramètres ou résultats de fonction.
  Cela s'applique à toutes les chaînes passées de et vers votre DLL --même celles
  qui sont imbriquées dans des enregistrements et classes. ShareMem est l'unité
  d'interface pour le gestionnaire de mémoire partagée BORLNDMM.DLL, qui doit
  être déployé avec vos DLL. Pour éviter d'utiliser BORLNDMM.DLL, passez les
  informations de chaînes avec des paramètres PChar ou ShortString. }uses
  SysUtils,
  Classes,
  Dialogs,   // just used for debugging purposes
  TextToPanoramic in 'TextToPanoramic.pas'
  ;
{$R *.res}function KGFdllVersion(pout: integer):integer; stdcall; export;
var
        ver: string;
        pver: pointer;
        i: array [0..8] of byte;
        pi: pbyte;
begin
{ start of memory dump starting at the address passed as parameter }
pi := pointer(pout);
i[0] := pi^;
inc(pi);
i[1] := pi^;
inc(pi);
i[2] := pi^;
inc(pi);
i[3] := pi^;
inc(pi);
i[4] := pi^;
inc(pi);
i[5] := pi^;
inc(pi);
i[6] := pi^;
inc(pi);
i[7] := pi^;
inc(pi);
i[8] := pi^;
showmessage('hello '+inttostr(i[0])+' '+inttostr(i[1])+' '+inttostr(i[2])+' '+inttostr(i[3])+' '+inttostr(i[4])
  +' '+inttostr(i[5])+' '+inttostr(i[6])+' '+inttostr(i[7])+' '+inttostr(i[8]));
{ end of dump - section to be suppressed }{ this is the real function code: }
        ver := 'KGF.dll V01.90 22/12/2012';       { taille: 25 caractères}
        pver := @ver;
        CopyTextToPanoramic(integer(pver),0,pout);
        result := 190;                             { et la valeur numérique, à diviser par 100}
end;
exports
  KGFdllVersion;begin
end.

And the internal TextToPanoramic.pas unit:

unit TextToPanoramic;
{
Cette unité contient la procédure
    CopyTextToPanoramic(source,offset,destination);
source et destination sont des pointeurs sur des strings Panoramic ou des PChar
en Delphi. offset est le décalage à partir duquel on écrit dans la destination
( 0 signifie depuis le début). La copie s'arrêtre automatiquement dès que la fin
d'un des strings est rencontré.
}
interface
procedure CopyTextToPanoramic(inp, offs, out:integer); stdcall;implementation
procedure CopyTextToPanoramic(inp, offs, out:integer); stdcall;
var
    xout,yinp: pchar;
    n: integer;
begin
    xout := pchar(pstring(out)^);
    yinp := pchar(pstring(inp)^);
    if offs>0 then begin
        for n:=1 to offs do begin
            inc(xout);
            if (xout^ = #0) then exit  { sortir si l'offset va à la fin}
        end
    end;
    while (xout^ <> #0) and (yinp^ <> #0) do begin
        xout^ := yinp^;
        inc(xout);
        inc(yinp);
    end;
end;
end.

@Melba23

I'm very gratefull to you for your intervention !

Link to comment
Share on other sites

  • Moderators

klausgunther,

How can I modify a posted message

By using the "Edit" button that appears at the bottom right of the post alongside the "Quote" buttons when you place your mouse in that area. ;)

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

...

With that modification, and

$ver = DllCall($dll, "int", "KGFdllVersion", "str*", $sver)

It works well. I recover the result string in $ver[1]. But

$ver = [url="http://www.autoitscript.com/autoit3/docs/functions/DllCall.htm"]DllCall[/url]($dll, "int", "KGFdllVersion", "ptr*", [url="http://www.autoitscript.com/autoit3/docs/functions/DllStructGetPtr.htm"]DllStructGetPtr[/url]($stString))

doesn't work - the data of the first element os stString is NOT overwritten !

...

I am not sure what you are expecting from that DllStructGetData() line so I can only suggest to remove that $sver. The third parameter is the index of an array to get, so passing the string "KGF.dll V01.90 22/12/2012" is not really going to do much good.

This:

MsgBox(0,"","B: result=" & $ver[0] & @crlf & "$stString=" & DllStructGetData($stString, 1))

Will show this:

KGF.dll V01.90 22/12/2012

If that's not what you want, then I don't know. I don't speak Delphi, nor French.
Link to comment
Share on other sites

How do you get this result ? For clarity, here is my complete code:

Local $dll, $ver, $sver

$sver = "1234567890123456789012345"

Local $stString = DllStructCreate("char v[25]")

DllStructSetData($stString, 1, $sver)

$dll = DllOpen("AutoIt_test.dll")

; *** working code

$ver = DllCall($dll, "int", "KGFdllVersion", "str*", $sver)

$sver = $ver[1]

MsgBox(0,"","A: result=" & $ver[0] & @crlf & "param1=" & $ver[1] & " $sver=" & $sver)

; *** NOT working code - the data element 1 of $stString is NOT overwritten !

$ver = DllCall($dll, "int", "KGFdllVersion", "ptr*", DllStructGetPtr($stString))

MsgBox(0,"","B: result=" & $ver[0] & @crlf & "$stString=" & DllStructGetData($stString, 1))

MsgBox(0,"","B1: " & $ver[1])

DllClose($dll)

I used the line you suggested.

The first element of ste structure is still unchanged, abd $ver[1] does not contain something meaningfull.

I always thought that writing bytes to a memory address will change these bytes. What I need, is replacing a string partially or fully in-memory, not create a new copy as in the "working" example. That's the reason I used the DllStruc stuff because there was a way to retrieve a pointer.

But maybe I'm completely blind, not seeing the obvious...

Edited by klausgunther
Link to comment
Share on other sites

AdmiralAlkex pointed you in right direction, check my comments in code.

Local $dll, $ver, $sver
$sver = "1234567890123456789012345"

Local $stString = DllStructCreate("char v[25]")
DllStructSetData($stString, 1, $sver)

$dll = DllOpen("AutoIt_test.dll")

$ver = DllCall($dll, "int", "KGFdllVersion", "ptr*", DllStructGetPtr($stString))
#cs
    $ver[0] - contain returned int value of your function
    $ver[1] - is the pointer of the structure (so cannot be something meaningfull for you but for application yes)
    Your data is located in the structure that starts where this pointer say. Here is not changed $sver variable.
#ce

MsgBox(0,"","Return value: " & $ver[0] & @CRLF & _
            "Structure pointer: " & $ver[1] & @CRLF & _
            "Data from structure: " & DllStructGetData($stString,"v"))

DllClose($dll)
Edited by Andreik

When the words fail... music speaks.

Link to comment
Share on other sites

Maybe I have missed the point of what you want to do, but to send string to a dll in Delphi, and get a string back is fairly simple.

This function in Delphi for example

function sometest(rrr: pchar): pchar; stdcall;
var
ff: string;
begin
ff := 'Here is some text to send back in answer to ' + rrr;

Result := pchar(ff);

end;

and this call in AutoIt

$ver = DllCall($dll,'str','sometest','str','abcdef')

MsgBox(0,"","Returned value: " & $ver[0])

If you use a structure in AutoIt and send the pointer you need to make sure that the returned string is no longer than the structure allowed for, so it might be wise to add a parameter for the length. Returning a string from Delphi doesn't require that the string must be a certain length.

Edited by martin
Serial port communications UDF Includes functions for binary transmission and reception.printing UDF Useful for graphs, forms, labels, reports etc.Add User Call Tips to SciTE for functions in UDFs not included with AutoIt and for your own scripts.Functions with parameters in OnEvent mode and for Hot Keys One function replaces GuiSetOnEvent, GuiCtrlSetOnEvent and HotKeySet.UDF IsConnected2 for notification of status of connected state of many urls or IPs, without slowing the script.
Link to comment
Share on other sites

@Andreik:

Ok. I see. But there remains a question: Where is my data going ? In my DLL function, y write the string "KGF.dll V01.90 28/12/2012", byte by byte, into the memory pointed by the first (and only) parameter of the function. I intuitively thought thatafter return, the correspondig memory area would show this data.

@Martin:

Thank you for your hint with Delphi.. I'll tell you a bit more about the usage of my DLLs and functions. I'm using a very nice Basic clone named Panoramic. This Basic can call DLL functions, but only with a very restrictive syntax:

a. all parameters are 32/64 bit integer values

b; all parameters are passed "by value"

c. every funtion returns a single 32/64 bit integer value

So, you see the consequences:

1. © implies that no string result can be returned

2. (B) implies that no parameter can be modified by any function

3. when a string is to be passed to a function, one must pass the address of the string as integer value (consequence of (a)

Its up to the function to interprete that address correctly.

4. when a value other then a single integer function result must be returned, one has to pass the address of the receiving memory

area as input parameter (consequences of (a) and (B) ). It's up to the function to know how many data bytes to write at that address.

So, that's the reason why i must return my result string using the address of the receiving memory area (a pre-charged string in this case). The initial string value is just a place holder, a memory reservation.

This all works fine from my Delphi DLL to the Panoramic language. I just wanted to have a quick check method throughout AutoIt for checking new or modified functions. The small DLL I published here, is just a test case. In reality,n there are several DLL's with several hundred functions.

Link to comment
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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...