Using String value transfers from DLL Procedures

Just starting out? Need help? Post your questions and find answers here.
Blurryan
User
User
Posts: 32
Joined: Sat Oct 13, 2007 2:08 pm
Location: Kazakhstan

Using String value transfers from DLL Procedures

Post by Blurryan »

Dear All,

I am trying to experiment with DLL creation and usage. Have got stuck on the problem below. Code of DLL and Test code sample given below. Seems that I am not able to get the logic of string value transfer from DLL.

DLL code is as below:

Code: Select all

ProcedureDLL AttachProcess(Instance.l)
  Global HMS.s, mmm.i, nn.i, oo.d
EndProcedure
 
; Converts a decimal figure into the (hh:mm:ss.ss PM) mode in the 12 hour format. Output is in string format. 
; Need to declare a string variable "HMS"
ProcedureDLL.s ConvertDecimaltoHMS12(DValue.d)
  mm.i = Int(DValue)
  If mm > 12
    mmm.i = mm - 12
    strm.s = "PM"
  Else
    mmm.i = mm
    strm.s = "AM"
  EndIf
  dvm.d = (DValue - mm) * 60
  nn.i = Int(dvm)
  dvvm.d = (dvm - nn) * 60
  oo.d = Int(dvvm)
  HMS.s = Str(mmm) + ":" + Str(nn) + ":" + StrD(oo,2) + " " + strm
  ProcedureReturn HMS
EndProcedure
The testing program is as below:

Code: Select all

Global.s HMS
Global.d oo
Global.i mmm, nn

OpenLibrary(0, "CelestialCalculations.dll")
CallFunction(0, "ConvertDecimaltoHMS12", 17.3256)
Debug PeekS(*HMS)
Debug " "
CloseLibrary(0)

Repeat
  
Forever
I am getting a null value transferred to the test program. What am I doing wrong and why?

Will be very grateful for help on this. Thanks
infratec
Always Here
Always Here
Posts: 7662
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Using String value transfers from DLL Procedures

Post by infratec »

Hi,

strings are always a bit tricky.
Also your .d as parameter. I think this is not handled correct with CallFunction() (read the help).
This works:

DLL:

Code: Select all

EnableExplicit

Global HMS.s
 
; Converts a decimal figure into the (hh:mm:ss.ss PM) mode in the 12 hour format. Output is in string format.
; Need to declare a string variable "HMS"
ProcedureDLL.s ConvertDecimaltoHMS12(DValue.d)
  
  Protected mmm.i, mm.i, nn.i, oo.d, dvm.d, dvvm.d, strm.s
  
  mm = Int(DValue)
  If mm > 12
    mmm = mm - 12
    strm = "PM"
  Else
    mmm = mm
    strm = "AM"
  EndIf
  dvm = (DValue - mm) * 60
  nn = Int(dvm)
  dvvm = (dvm - nn) * 60
  oo = Int(dvvm)
  HMS = Str(mmm) + ":" + Str(nn) + ":" + StrD(oo, 2) + " " + strm
  
  ProcedureReturn HMS
  
EndProcedure


ProcedureDLL.f RetPiFloat()
  ProcedureReturn 3.14
EndProcedure


ProcedureDLL.d RetPiDouble()
  ProcedureReturn 3.141592653589793
EndProcedure
Test:

Code: Select all

Prototype.i ProtoConvertDecimaltoHMS12(DValue.d)
Prototype.f ProtoRetPiFloat()
Prototype.d ProtoRetPiDouble()


If OpenLibrary(0, "Test.dll")
  ConvertDecimaltoHMS12.ProtoConvertDecimaltoHMS12 = GetFunction(0, "ConvertDecimaltoHMS12")
  Debug PeekS(ConvertDecimaltoHMS12(17.3256))
  
  RetPiFloat.ProtoRetPiFloat = GetFunction(0, "RetPiFloat")
  Debug RetPiFloat()
  
  RetPiDouble.ProtoRetPiDouble = GetFunction(0, "RetPiDouble")
  Debug RetPiDouble()
  
  CloseLibrary(0)
EndIf
The prototype is declared as .i because not a string is returned, only the pointer to it.

Btw. using prototypes is always the better way.

Bernd
Last edited by infratec on Thu May 07, 2015 1:13 pm, edited 2 times in total.
Blurryan
User
User
Posts: 32
Joined: Sat Oct 13, 2007 2:08 pm
Location: Kazakhstan

Re: Using String value transfers from DLL Procedures

Post by Blurryan »

Thanks Bernd,

Works.
So I need to study the concept of Prototypes now.

Regards
infratec
Always Here
Always Here
Posts: 7662
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Using String value transfers from DLL Procedures

Post by infratec »

That's easy:

The prototype itself is only a declaration (same as the Procedure itself)
Then you define a variable with the type of that prototype.
Then you give this variable the address of the real procedure.
Then you call it. That's all :mrgreen:

So you can also check if the procdure is available in the dll:
if the variable is #NULL after GetFunction() it was not found.

Bernd
Blurryan
User
User
Posts: 32
Joined: Sat Oct 13, 2007 2:08 pm
Location: Kazakhstan

Re: Using String value transfers from DLL Procedures

Post by Blurryan »

Thanks Bernd,
So as I understand, the return variable need not necessarily be a string. It can be any variable type.

Regards
infratec
Always Here
Always Here
Posts: 7662
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Using String value transfers from DLL Procedures

Post by infratec »

Hi,

I extended the listing above.

Bernd
Blurryan
User
User
Posts: 32
Joined: Sat Oct 13, 2007 2:08 pm
Location: Kazakhstan

Re: Using String value transfers from DLL Procedures

Post by Blurryan »

Thanks Bernd (infratec)

Just to put into perspective my understanding of the way DLL calls can be made using Prototypes, my learning is as follows:

DLL File:
1. In the "ProcedureDLL AttachProcess(Instance.l).....EndProcedure" area we need to define the variables those will be "passed on" to the calling program. In my version we have "Global DLLString.s = "", Hr12.i, Min.i, Sec.d, SecD.d"
2. The ProcedureDLL must be defined as .s or.i or.d etc to signify the type of variable that needs to be passed from the DLL.
3. In case we do not define as 2 above, we can pass from a single ProcedureDLL multiple types of variable types.
4. Bytes, integers, words, longs are handled internally by the "CallFunction" command and we need to only define the variables in the ProcedureReturn command.
5. Doubles, Floats and Strings can be passed through pointers to the variable ONLY provided they are mentioned as in 1 above.

Test file:
1. The Library file (*.dll) needs to be opened BEFORE any function in the DLL is used and as good practice MUST be CLOSED before program ends.
2. A prototype definition needs to be done as an "integer" variable as the addresses to the function in the DLL shall be passed to it.
3. The name of this Prototype can be anything, BUT need to ensure that they contain the required arguments and their variable types are as per the Function definition in the DLL. In the case below I have given the name "AnyPrototypeName(D.d, i.i)"
4. Now a name to the Calling Function as a variable type of the above Prototype needs to be given which in this case is "AnyCallingName".
5. This calling name of type "prototype" as defined now takes the value of the DLL Function through the "GetFunction" command.
6. For bytes, integers, words, longs variable type, the above named "Calling function" needs to be called with the Function arguments in brackets.
7. For doubles, floats and strings, see point 5 under "DLL File" above, we need to "Peek" at the above calling function (which carries the function address) for the particular variable type that is required, hence PeekD for double and PeekS for strings.

Please see the test files as below for understanding.

DLL FILE:

Code: Select all

ProcedureDLL AttachProcess(Instance.l)
  Global DLLString.s = "", Hr12.i, Min.i, Sec.d, SecD.d
EndProcedure

ProcedureDLL ConvertDecimaltoHMS12(DecimalValue.d, index.i)
  Hr.d = Int(DecimalValue)
  If Hr > 12
    Hr12 = Hr - 12
    AmPm.s = "PM"
  Else
    Hr12 = Hr
    AmPm = "AM"
  EndIf
  MinD.d = (DecimalValue - Hr) * 60
  Min = Int(MinD)
  SecD.d = (MinD - Min) * 60
  Sec = Int(SecD)
  DLLString = Str(Hr12) + ":" + Str(Min) + ":" + StrD(SecD,2) + " " + AmPm
  
  Select index
    Case 1
      ProcedureReturn Hr12        ; notice no @ sign as variable is an integer
    Case 2
      ProcedureReturn Min         ; notice no @ sign as variable is an integer
    Case 3
      ProcedureReturn @SecD    ; notice @ sign as variable is a double
    Case 4
      ProcedureReturn @DLLString        ; notice @ sign as variable is a string
  EndSelect
EndProcedure
And the Test Code to test the above DLL File:

Code: Select all

If OpenLibrary(0, "mydll.dll")
  Prototype.i AnyPrototypeName(D.d, i.i)
  jj.d = 17.5236
  AnyCallingName.AnyPrototypeName = GetFunction(0, "ConvertDecimaltoHMS12")
    Debug AnyCallingName(jj, 1)
    Debug AnyCallingName(jj, 2)
    Debug PeekD(AnyCallingName(jj, 3))
    Debug PeekS(AnyCallingName(jj, 4))
    Debug " "
Else
  Debug "Error Opening Library"
EndIf
CloseLibrary(0)
I hope my understanding is correct and if not please let me know where my concept differs.

Thanks Bernd once again for explaining the concept as above. Hope my note helps newbies like me to understand DLL procedure functionality.

Regards
infratec
Always Here
Always Here
Posts: 7662
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Using String value transfers from DLL Procedures

Post by infratec »

Hi,

one thing is not correct.
You don't need AttachProcess().

Global variables can be written outside of a procedure.
local variables should be written in the procedure where they are used. (protected)

AttachProcess() is only needed if you want to initialize something when the dll is loaded.
The opposite is DetachProcess(). It is used if you have to free something.

You only have to define strings global when you return them.
Other return variable types don't need this since they are returned in registers.
Strings are returned by a pointer pointing at the string, therefore the string has to be global,
since it has to be still valid when the procedure is already returned.
(or you have to allocate memory in AttachProcess() and free it in DetachProcess())

One procedure, in general, can only return one type of variable!
Or... it returns a pointer to a variable.
But then all the used variables needs to be global and you always have to use @ before the variablename,
since you have to return the address of the variable.


So your example shoul look like this:
DLL:

Code: Select all

EnableExplicit

Global DLLString.s, Hr12.i, Min.i, SecD.d

ProcedureDLL ConvertDecimaltoHMS12(DecimalValue.d, index.i)
  
  Protected Hr.i, AmPm.s, MinD.d, Sec.i
  
  
  Hr = Int(DecimalValue)
  If Hr > 12
    Hr12 = Hr - 12
    AmPm = "PM"
  Else
    Hr12 = Hr
    AmPm = "AM"
  EndIf
  MinD = (DecimalValue - Hr) * 60
  Min = Int(MinD)
  SecD = (MinD - Min) * 60
  Sec = Int(SecD)
  DLLString = Str(Hr12) + ":" + Str(Min) + ":" + StrD(SecD,2) + " " + AmPm
  
  Select index
    Case 1
      ProcedureReturn @Hr12
    Case 2
      ProcedureReturn @Min
    Case 3
      ProcedureReturn @SecD
    Case 4
      ProcedureReturn @DLLString  ; notice @ not really needed, because a string 'is' already a pointer
  EndSelect
  
EndProcedure
Test:

Code: Select all

Prototype.i AnyPrototypeName(D.d, i.i)

Global AnyCallingName.AnyPrototypeName

If OpenLibrary(0, "test2.dll")  
  jj.d = 17.5236
  AnyCallingName = GetFunction(0, "ConvertDecimaltoHMS12")
    Debug PeekI(AnyCallingName(jj, 1))
    Debug PeekI(AnyCallingName(jj, 2))
    Debug PeekD(AnyCallingName(jj, 3))
    Debug PeekS(AnyCallingName(jj, 4))
    Debug " "
    CloseLibrary(0)
Else
  Debug "Error Opening Library"
EndIf
But it makes no sense to name it AnyCallingName() :wink:
And your CloseLibrary() should only be used when the library was open.

Bernd
Blurryan
User
User
Posts: 32
Joined: Sat Oct 13, 2007 2:08 pm
Location: Kazakhstan

Re: Using String value transfers from DLL Procedures

Post by Blurryan »

Understood.
Thanks again.
Post Reply