Get Data from dll

Started by Jeremy Whilde, October 09, 2013, 11:15:08 AM

Previous topic - Next topic

Jeremy Whilde

For a dialer application I can send data to the dll with WB like this:

dllname= "C:\Path\dial_lib.dll"
DllCall(dllname,lpstr:"Dial_Drop" )

and the call is dropped correctly. However how do I get the error data back from the dll in WB?

An example is given in Delphi as follows:

;---------------

function Dial_Drop: integer; stdcall; external 'dial_lib.dll';
sends a message to the dial program to hang up a call.

Result:

-1if the message cannot be sent to the dial program
0 if the message is sent to the dial program

if an error does occur, the error message can be retrieved by calling Dial_ErrorMessage.


procedure Dial_ErrorMessage(ErrMsg: PChar); stdcall; external 'dial_lib.dll';

Copies the last error message into ErrMsg.

Result:

  None.

Notes:

1. the calling program must allocate memory for the error message.

Example Code:

procedure TForm1.Button2Click(Sender: TObject);
var
  X: array[0..256] of char;
begin
  Dial_Drop;
  Dial_ErrorMessage(x);
  ShowMessage(x);
end;

;----------

Any ideas or pointers very much appreciated.

Thanks JW

Deana

It appears from the documentation that Dial_Drop returns a integer so your code should look like this:

Code (winbatch) Select

dllname= "C:\Path\dial_lib.dll"
result = DllCall(dllname,long:"Dial_Drop" )


Notice the use of LONG rather than LPSTR. Also notice that a variable is defined to capture the return value. Which in this case should be 0  or -1. You should then add code to call Dial_ErrorMessage if Dial_Drop returns -1.

The code might look something like this:

Code (winbatch) Select

;UNDEBUGGED
dllname= "C:\Path\dial_lib.dll"
result = DllCall(dllname,long:"Dial_Drop" )
If result == -1
   lpErrorBuf = BinaryAlloc(256) ; Allocate a binary buffer
   DllCall(dllname,void:"Dial_ErrorMessage",lpBinary:lpErrorBuf )
   errorstring = BinaryPeekStr(lpErrorBuf, 0, BinaryEodGet( lpErrorBuf ))
   Pause("Dial_Drop Error", errorstring )
   BinaryFree(lpErrorBuf ) ; Free Binary Buffer
Endif



Deana F.
Technical Support
Wilson WindowWare Inc.

td

You may also need to use the DllCallCdecl function instead of DllCall.  Third party libraries are often compiled to the C declaration calling convention instead of MSFT's win32 standard calling convention.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

Jeremy Whilde

OK thanks are there any examples of DllCallCdecl

Definitely have to use LPSTR rather than LONG as LONG returns an error. LPSTR works for dialling and drop functions.
have asked manufactures for a further example of the ErrorMessage call, the DLL was produced in Delphi and hoping to get further compiled test app for error returns and the Delphi code so may help with trying to allocate memory and read back the error data. Deana's code for error messages did not work, so hoping manufacturer will be able to supply requested test app/code.

Thanks JW

Deana

Quote from: Jeremy Whilde on October 10, 2013, 01:43:32 PM
OK thanks are there any examples of DllCallCdecl

Definitely have to use LPSTR rather than LONG as LONG returns an error. LPSTR works for dialling and drop functions.
have asked manufactures for a further example of the ErrorMessage call, the DLL was produced in Delphi and hoping to get further compiled test app for error returns and the Delphi code so may help with trying to allocate memory and read back the error data. Deana's code for error messages did not work, so hoping manufacturer will be able to supply requested test app/code.

Thanks JW

Simply change the DllCall function name to DllCallCdecl. The parameters are the same for both functions. See WIL help file topic for DllCallCdecl, it has a code example.

Based on the documentation you posted for Dial_Drop, it appears to state that it returns an integer. I would have expected that to be a LONG rather than a LPSTR. If you specify LPSTR what value is getting returned?

The documentation also appears to state is uses stdcall convention, so I would think you should use DllCall. But I suppose you could try DllCallCdecl.



Deana F.
Technical Support
Wilson WindowWare Inc.

DAG_P6

Quote from: td on October 10, 2013, 06:41:55 AM
You may also need to use the DllCallCdecl function instead of DllCall.  Third party libraries are often compiled to the C declaration calling convention instead of MSFT's win32 standard calling convention.

The example cited by the OP suggests otherwise.

procedure Dial_ErrorMessage(ErrMsg: PChar); stdcall; external 'dial_lib.dll';

Unless Delphi says stdcall when it means cdecl, it seems to me that a regular DLLCall should work. Also, as I recall, if you use cdecl when stdcall is required, or vice versa, the WinBatch interpreter reports a stack frame error. 

P. S. -- I assume that the WB interpreter is just bubbling up the message generated by a call to chkesp, although I haven't compared the two side by side.
David A. Gray
You are more important than any technology.

td

Quote from: DAG_P6 on October 11, 2013, 08:37:50 PM

The example cited by the OP suggests otherwise.
procedure Dial_ErrorMessage(ErrMsg: PChar); stdcall; external 'dial_lib.dll';

So it does.

Quote from: DAG_P6 on October 11, 2013, 08:37:50 PM
Unless Delphi says stdcall when it means cdecl, it seems to me that a regular DLLCall should work. Also, as I recall, if you use cdecl when stdcall is required, or vice versa, the WinBatch interpreter reports a stack frame error. 

I doubt the documentation is wrong.  I simply overlooked the 'stdcall' in the op's original post.  We do the best we can but it is not the first time I have missed something in a user's post nor will it likely be the last.  And yes, WinBatch generates an error but it can have several causes and it has been my experience that users seldom connect it to the callee's calling convention.

Quote from: DAG_P6 on October 11, 2013, 08:37:50 PM
P. S. -- I assume that the WB interpreter is just bubbling up the message generated by a call to chkesp, although I haven't compared the two side by side.

No, WinBatch does not use chkesp nor any messages generated by chkesp.  The 32-bit and 64-bit DllCall/DllCallCdecl implementations are different but both are implemented using hand written assembly and neither relies on any c runtime library functionality (nor any compiler intrinsics for that matter) to perform their entry point calling task.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

Jeremy Whilde

OK have been making some progress with this particular dll getting things to work. But not with the error returns.

dllname = "C:\Path\dial_lib.dll"

DropReturn = DllCall(dllname,long:"Dial_Drop" )
message("Drop Return", DropReturn)

This works now as expected in that it both drops an established call and it returns -1if the message cannot be sent to the dial program
0 if the message is sent to the dial program.

dllname = "C:\Path\dial_lib.dll"
NumC = "0123456789"

DialReturn = DllCal(dllname,long:"Dial_Dial",lpstr:NumC )
message("Drop Result", DropReturn)

Both of these work if DllCall or DllCallCdecl is used.

I do think the manufacturer has changed something with the dll as I am now using a new version of the dll and as Deana originally suggested the Dial_Drop originally errored on long.  However note the Dial_Dial must use lpstr for the NumC value to be dialed.

However I still cannot get the error data back from the buffer with the Dial_ErrorMessage. The manufacturer has sent the following Delphi code and a compiled test app which does bring back the error data from the dll, but only with the Delphi code. They have now included a function to insert a test message so the Dial_ErrorMessage can be tested, but I cannot as I say get this to work.

Here is the Delphi code:

{this function just inserts a test message into a global error string.}
procedure Dial_ErrorMessageTest; stdcall;

As far as usage is concerned...

procedure Dial_ErrorMessageTest; stdcall; external 'dial_lib.dll';
procedure Dial_ErrorMessage(ErrMsg: PChar); stdcall; external 'dial_lib.dll';

procedure TForm1.Button7Click(Sender: TObject);
var
  X: array[0..256] of char;
begin
  Dial_ErrorMessageTest;
  Dial_ErrorMessage(x);
  showmessage(x);
end;

So I have tried the following:

dllname = "C:\Path\dial_lib.dll"

X=BinaryAlloc(256)
   
DllCall(dllname,long:"Dial_ErrorMessageTest")
DllCall(dllname,long:"Dial_ErrorMessage",lpBinary:X )

; Set EOD point in buffer, just in case
BinaryEodSet(X, 256)
errorstring=BinaryPeekStr(X, 0, BinaryEodGet(X))
Message("Error Test Msg", errorstring)
BinaryFree(X)

But this always returns null?! So what is different about the Delphi code? is the  X: array[0..256] of char; different to X=BinaryAlloc(256)

Any idea?

Thanks JW

td

As a wild guess, if your Delphi dll is compiled using a Unicode version of Delphi, your binary buffer is way too small.  Based on the Delphi array declaration you provided, it would need to be 514 bytes in size.  More importantly, however, you would need to use the BinaryPeekStrW function instead of BinaryPeekStr to extract the message text.    As an aside, I think a Delphi array declared with a range of [0...256] actually has 257 elements and not 256 elements as is suggested by the allocation of a 256 byte binary buffer.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

ChuckC

Another thing to consider....

All of the WB example code shown so far that I can see are simply using DllCall(), which implies that the DLL is being loaded, a function within it is called, and then the DLL is unloaded again.  I would expect that any state/context information held in memory by the DLL regarding the error message associated with the most recent function call will be lost when the DLL is unloaded.  Recoding to use DllLoad() to obtain a handle to the loaded DLL, followed by DllCall() as many times as needed and finishing with DllFree() should remedy that problem.

td

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

Jeremy Whilde

OK the DllLoad() to obtain a handle first is exactly what seems to be needed, then use the DLLCall with this handle for each call and close with DllFree(). Also 256 seems to be sufficient and errorstring=BinaryPeekStr(X, 0, BinaryEodGet(X)) to retrieve all the error messages.

Thank you all your suggestions and help.

Thanks JW

td

Quote from: Jeremy Whilde on October 15, 2013, 10:10:58 AM
OK the DllLoad() to obtain a handle first is exactly what seems to be needed, then use the DLLCall with this handle for each call and close with DllFree().
Nice catch Chuck.
Quote from: Jeremy Whilde on October 15, 2013, 10:10:58 AM
Also 256 seems to be sufficient and errorstring=BinaryPeekStr(X, 0, BinaryEodGet(X)) to retrieve all the error messages.

That it seems to be sufficient is to some extent besides the point.  The Delphi declaration you provided definitely shows a 257 element array and not 256 element array.  Does the dll's documentation directly state or does an author suggest by example a minimum size for the array? If either is the  case, it would be in your best interest to use at a minimum that size and not something that 'seems' to be OK. It is the kind of thing that can otherwise come back to bite you at the most inopportune moment.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade