Has the behaviour of 'Shared' changed in version 4.0x?

Just starting out? Need help? Post your questions and find answers here.
User avatar
Lewis
User
User
Posts: 47
Joined: Fri Nov 25, 2005 1:12 pm

Has the behaviour of 'Shared' changed in version 4.0x?

Post by Lewis »

Consider the following skeleton program.

Code: Select all

Declare.l Fn1(V1.l, V2.l, V3.l)
Declare Proc(P1.l, P2.l, P3.l, P4.l)
Declare.s Fn2(P1.l)

If OpenConsole()
  Repeat 
    ...    ; Print & Input statements for variables V1, V2, V3

    P1 = Fn1(V1, V2, V3)
    Proc(P1, P2, P3, P4)  ; P1 is bound & P2, P3, P4 are free on entry to Proc
                          ; but P2, P3, P4 are bound on exit from Proc

    ...    ; Process using the now bound variables P2, P3, P4

    Print(Str(P2) + ", " + Str(P3) + " & " + Str(P4) + " evaluate to ")
    Print(Fn2(P1))
    PrintN("")

    Print("Another? (y/n) ") 
    Repeat] 
      K = Left(Inkey(),1) 
    Until K = "Y" Or K = "y" Or K = "N" Or K = "n" 
    Print(K) : PrintN("") : PrintN("")

  Until K = "N" Or K = "n"
EndIf
End

Procedure.l Fn1(V1, V2, V3)
  ; process V1, V2, V3 and store result in local long variable L
  ProcedureReturn = L
EndProcedure

Procedure Proc(P1, P2, P3, P4)
  Shared P2, P3, P4   ; allows us to change their values here
  ; local variables are defined with an .l suffix, none are named P2, P3, P4
  ...  ; Compute P2, P3, P4 based on value of P1
EndProcedure

Procedure.s Fn2(P1)
  ; process P1 and store result in local string variable S
  ProcedureReturn = S
EndProcedure
This works fine in v3.94. When I Compile/Run it in v4.0x, however, I get:

Code: Select all

Line 89: Local variable already declared: P2
Line 89 is the Shared statement in the Proc procedure, so I presume that P3 & P4 will get the same treatment.

The behaviour of Shared appears to have changed in v4.0x. If so, why?

Regards,
Lewis
Tranquil
Addict
Addict
Posts: 952
Joined: Mon Apr 28, 2003 2:22 pm
Location: Europe

Post by Tranquil »

You could not share a variable which you are using as a procedure argument.

Code: Select all

Procedure Test(a.b)
shared a.b
EndProcedure
This does not work cause you assign a value to a by calling the procedure. So it can never share a value.
Tranquil
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

It's not the shared statement which has changed, but I think that Procedure parameters are now protected.

You've declared P2 as a procedure parameter, which because of the above, is now made a local variable.
The shared statement in this case is thus a contradiction and an error.

Simply change your procedure declaration to rename P2 to something else.

**EDIT: too slow!
I may look like a mule, but I'm not a complete ass.
User avatar
Lewis
User
User
Posts: 47
Joined: Fri Nov 25, 2005 1:12 pm

Post by Lewis »

Thanks for your replies, chaps. As I understand it, there is need for just one parameter if I use Shared:

Code: Select all

Declare Proc(P1.l)
...
    P1 = Fn1(V1, V2, V3)
    Proc(P1)
    ...    ; Process using the now bound variables P2, P3, P4
...
Proc(P1.l)
  Shared P2, P3, P4   ; allows us to change their values here
  ; local variables are defined with an .l suffix, none are named P2, P3, P4
  ...  ; Compute P2, P3, P4 based on value of P1
EndProcedure
This works but, IMHO, is very untidy coding, which doesn't make for logical reading. So this leads to the question of how would one go about getting the traditional method of parameter passing to work? The traditional method being to pass variables into a procedure via parameters, manipulate/update their values, and then use these updated values in the calling program (usually, but not necessarily, the main loop). I simply cannot get this traditional method to work in v4.0x (nor v3.94 without the Shared statement)!

Regards,
Lewis
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

If you're talking about 'passing by reference' then PB doesn't support this directly. All paramaters (except array's and linked lists) are passed by value.

One way around this is as follows:

Code: Select all

Procedure test(*var.LONG) ;*var is a pointer to a structure of type LONG. This has a single field of type .l (long).
  *var\l=200
EndProcedure

a = 100
Debug a ;Output's 100.
test(@a) ;Call the procedure 'test' with the address of the variable a.
Debug a ;Output's 200.
Another way would be the following slight adjustment to the above procedure:

Code: Select all

Procedure test(addvar) ;addvar receives the address of the variable being passed.
  PokeL(addvar, 200)
EndProcedure

a = 100
Debug a ;Output's 100.
test(@a) ;Call the procedure 'test' with the address of the variable a.
Debug a ;Output's 200.
The first method should be quicker however.

imho, passing by value (as PB does) is a better (and safer) way of handling function parameters.
I may look like a mule, but I'm not a complete ass.
User avatar
Lewis
User
User
Posts: 47
Joined: Fri Nov 25, 2005 1:12 pm

Post by Lewis »

Thanks, srod. However, I want to manipulate/change those passed parameter values in the called procedure, such that these "updated" values can be handled in the lines following the call. In other words, the procedure called, say, UpdateTheseValues(P1, V1, K1, ...) is primarily used to do just what its name says.
Dare
Addict
Addict
Posts: 1965
Joined: Mon May 29, 2006 1:01 am
Location: Outback

Post by Dare »

Hi Lewis,

Took me a while to understand what you were asking, but I think I do now. Basically you want to declare an incoming variable as shared and have resulting manipulations of that variable reflected in another variable? Eg:

Code: Select all

Procedure Invalid_JustAnExample( Shared p1.l )
  p1 + 1
EndProcedure

p2.l = 2
Invalid_JustAnExample( p2 ) 
Debug p2                                 ; should be 3
If so, then using pointers will do it, without adding additional keywords and/or rules to the language.

And also (if such is your requirement) then srod has provided the solution. So, reinforcing srod's suggestion:

Code: Select all

Procedure JustAnExample( *p1.long )
  *p1\l + 1
EndProcedure

p2.l = 2
JustAnExample( @p2 ) 
Debug p2                                 ; should be and is 3
Another way:

Code: Select all

Procedure JustAnExample()
  Shared p2.l
  p2+ 1
EndProcedure

p2.l = 2
JustAnExample( ) 
Debug p2                                 ; should be and is 3
But that limited and not very flexible.

If you're asking for something different could you do another very short bit of code and explain your expectations. Thanks.
Dare2 cut down to size
User avatar
Lewis
User
User
Posts: 47
Joined: Fri Nov 25, 2005 1:12 pm

Post by Lewis »

Dare wrote:Hi Lewis,

Took me a while to understand what you were asking, but I think I do now. Basically you want to declare an incoming variable as shared and have resulting manipulations of that variable reflected in another variable? Eg:

Code: Select all

Procedure Invalid_JustAnExample( Shared p1.l )
  p1 + 1
EndProcedure

p2.l = 2
Invalid_JustAnExample( p2 ) 
Debug p2                                 ; should be 3
If so, then using pointers will do it, without adding additional keywords and/or rules to the language.

And also (if such is your requirement) then srod has provided the solution. So, reinforcing srod's suggestion:

Code: Select all

Procedure JustAnExample( *p1.long )
  *p1\l + 1
EndProcedure

p2.l = 2
JustAnExample( @p2 ) 
Debug p2                                 ; should be and is 3
Another way:

Code: Select all

Procedure JustAnExample()
  Shared p2.l
  p2+ 1
EndProcedure

p2.l = 2
JustAnExample( ) 
Debug p2                                 ; should be and is 3
But that limited and not very flexible.

If you're asking for something different could you do another very short bit of code and explain your expectations. Thanks.
Hi to you too, Dare!

Well, yes and no to your opening question. Basically I would want the result of the manipulations to the incoming variable/parameter to be reflected in that same variable/parameter. That is, the parameter is both an incoming and an outgoing one. But, as you said, I might also want to have resulting manipulations of an incoming variable/parameter reflected in another outgoing variable/parameter. In both case I'm referring to simple, atomic variables/parameters, but there is no reason why they couldn't be structure pointers. Note: a function will return just one value, albeit also structure pointers.

If this is still not understandable, then I'll try to write one or more code examples. However, I do believe that your first two (valid) examples reflect what I'm trying to achieve, so let me give them a try. :) Your last example is exactly what I'm trying to avoid, since it leads to spagetti code.

I think the problem lies with my logic programming background. I'm used to writing and reading logically structured code, not spagetti code. :wink:

Thanks to both you and srod for your interest and help, I'll let you know how things turn out.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

If I read you correctly Lewis, you might be interested in using a structured pointer:

Code: Select all

Structure complexvar
  a.l
  b.l
  c.l
  d.s
EndStructure

Procedure Test(*var.complexvar) 
  With *var 
    \a+1
    \b+1
    \c+1
    \d+"Basic"
  EndWith
EndProcedure

;Execution entry point

a.complexvar
With a
  \a=1
  \b=1
  \c=1
  \d="Pure"
EndWith

Test(a)

With a
  Debug \a
  Debug \b
  Debug \c
  Debug \d
EndWith
Many Windows API's use this approach, for example:

Code: Select all

hti.LVHITTESTINFO
hti\pt\x = WindowMouseX() ; Partially fill the structure
hti\pt\y = WindowMouseY() ; with these three values
hti\flags = #LVHT_ONITEM  ;

SendMessage_(GadgetID(#listview), #LVM_HITTEST, 0, hti) ; The API will fill in the remaining 
                                                        ; elements of the structure. In this 
                                                        ; way several variables can be
                                                        ; shared between your prog and the API
                                                        ; with only one pointer being passed.
User avatar
Lewis
User
User
Posts: 47
Joined: Fri Nov 25, 2005 1:12 pm

Post by Lewis »

Okay, I've slept on it and woken with a refreshed set of grey cells. :lol: Most importantly, I'm happy to say that I believe I can live with your suggestion. Consider the following program, which was inspired by a recent message in comp.lang.basic.misc:

Code: Select all

; DOW.PB 
  
; Program to find the day of the week for a given Gregorian AD date.

Declare.l Days(Year.l, Month.l, Day.l)
Declare DateParts(JD.l, *Year.long, *Month.long, *Day.long)
Declare.s DoW(JD.l)

If OpenConsole()
  Repeat 

    Print("Enter the Year (YYYY): ") : Y.l = Val(Input()) : PrintN("")
    Print("Enter the Month (MM): ") : M.l = Val(Input()) : PrintN("")
    Print("Enter the Day (DD): ") : D.l = Val(Input()) : PrintN("")
    JD.l = Days(Y, M, D)
    DateParts(JD, @Year, @Month, @Day)
    Print(Str(Year) + "-" + Str(Month) + "-" + Str(Day) + " is/was a ")
    Print(DoW(JD)) : PrintN("")

    Print("Another? (y/n) ") 
    Repeat 
      K$ = Left(Inkey(),1) 
    Until K$ = "Y" Or K$ = "y" Or K$ = "N" Or K$ = "n" 
    Print(K$)
    PrintN("") : PrintN("")

  Until K$ = "N" Or K$ =  "n"
EndIf

End 


Procedure.l Days(Year.l, Month.l, Day.l)
  ; CONVERT DATE PARTS TO JULIAN DAY (SERIAL) NUMBER:
  A.l = (14 - Month.l) / 12
  Y.l = Year.l + 4800 - A.l
  M.l = Month.l + (12 * A.l) - 3
  D.l = Day.l + Int((153 * M.l + 2) / 5) + (365 * Y.l) + Int(Y.l / 4)
  ProcedureReturn = D.l - Int(Y.l / 100) + Int(Y.l / 400) - 32045
EndProcedure


Procedure DateParts(JD.l, *Year.long, *Month.long, *Day.long)
  ; CONVERT JULIAN DAY (SERIAL) NUMBER TO DATE PARTS:
  A.l = JD.l + 32045
  B.l = (4 * (A + 36524)) / 146097 - 1
  C.l = A.l - ((146097 * B) / 4)
  D.l = ((4 * (C + 365)) / 1461) - 1
  E.l = C - ((1461 * D) / 4)
  M.l = ((5 * (E - 1)) + 2) / 153
  *Year\l = ((100 * B) + D - 4800 + (M / 10))
  *Month\l = M + (3 - (12 * (M / 10)))
  *Day\l = E - (((153 * M) + 2) / 5)
EndProcedure


Procedure.s DoW(JD.l) 
  ; COMPUTE DAY OF WEEK NUMBER:
  DayOfWeek.l = (JD.l % 7)
  ; CONVERT DAY OF WEEK NUMBER TO DAY OF WEEK NAME:
  N.s = "Monday   Tuesday  WednesdayThursday Friday   Saturday Sunday" 
  ProcedureReturn = RTrim(Mid(N.s, (DayOfWeek.l * 9 + 1), 9)) 
EndProcedure
If you Compile/Run this program and enter any AD date, you will be given the day of the week for that date. "So what?" you may ask, "...PureBasic already has a DayOfWeek() function." "Sure it does, and it is convenient for all those people who are younger that 37 years of age who would like to know on which day of the week they were born," I would reply (probably with a little sarcasm in my voice), adding, "...and it cannot tell you that the First World War Armistace was signed on Monday, 11 November, 1918." But I digress...

The purpose of the DateParts procedure is to correlate the user's input date. Try, for example, 2006-2-29 and 2005-14-32. The use of pointers here has resulted in compact, readable code, although it does seem to carry some baggage around with it, for example, having to remember to write .long or \l after variable names. I dare say that I'll quickly get used to that. :wink:

Thanks again to all of you -- including the latecomer, netmaestro -- for your interest and help.

Here's to reuseable, economical, fast, efficient and readable (REFER) PureBasic code,
Cheers!
Lewis
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re:

Post by Kwai chang caine »

netmaestro wrote:If I read you correctly Lewis, you might be interested in using a structured pointer:

Code: Select all

Structure complexvar
  a.l
  b.l
  c.l
  d.s
EndStructure

Procedure Test(*var.complexvar) 
  With *var 
    \a+1
    \b+1
    \c+1
    \d+"Basic"
  EndWith
EndProcedure

;Execution entry point

a.complexvar
With a
  \a=1
  \b=1
  \c=1
  \d="Pure"
EndWith

Test(a)

With a
  Debug \a
  Debug \b
  Debug \c
  Debug \d
EndWith
Many Windows API's use this approach, for example:

Code: Select all

hti.LVHITTESTINFO
hti\pt\x = WindowMouseX() ; Partially fill the structure
hti\pt\y = WindowMouseY() ; with these three values
hti\flags = #LVHT_ONITEM  ;
The code of Netmaestro is really cool, i try to use it in a DLL and that works fine 8)
So the question i ask to me, there is never problem, if the DLL put a too big value or other ???
The EXE manage all this things or there is some restrictions ???

DLL code

Code: Select all

Structure complexvar
  a.l
  b.l
  c.l
  d.s
EndStructure

ProcedureDLL Test(*var.complexvar) 

  With *var 
    \a+1
    \b+1
    \c+100
    \d+"PureBasic is the better language of the world, and i love it very much"
  EndWith
EndProcedure
EXE code

Code: Select all

Structure complexvar
  a.l
  b.l
  c.l
  d.s
 EndStructure
     
 a.complexvar
 
With a
  \a=1
  \b=1
  \c=1
  \d="Pure"
EndWith

If OpenLibrary(0, "PureBasic.dll")
 CallFunction(0, "Test", a)
 CloseLibrary(0)
EndIf

With a
  Debug \a
  Debug \b
  Debug \c
  Debug \d
EndWith
ImageThe happiness is a road...
Not a destination
Post Reply