Announcement

Collapse
No announcement yet.

When does a Global Change?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • When does a Global Change?

    I know this has been of some debate of Globals vs Locals, but I may have run into a situation where I have a Global Variable, and I set that variable depending on the answer from a function.

    In this case, my global is not getting set, unless I set the Global directly in the function being used instead of Function = setting, and Global = Function.

    I am attempting to come up with a example (as we are all fond of) of my much larger program, but in the meanwhile I hoped someone would know exactly WHEN a global is set? or some timeframe I need to wait for it to be set? (the second option does not seem likely since GlobalFlag = Function is one line and MSGBOX Global Flag is the next line, Unless threads are what is doing it to me?)
    Engineer's Motto: If it aint broke take it apart and fix it

    "If at 1st you don't succeed... call it version 1.0"

    "Half of Programming is coding"....."The other 90% is DEBUGGING"

    "Document my code????" .... "WHYYY??? do you think they call it CODE? "

  • #2
    When it is assigned a value, that's when it changes. There is no delay or 'cooling off period.'

    Show allegedly failing code. (In this case a few lines will suffice).
    Michael Mattias
    Tal Systems (retired)
    Port Washington WI USA
    [email protected]
    http://www.talsystems.com

    Comment


    • #3
      Working on it MCM, (since you replied much faster than I expected )
      (and of course working on it, I am thinking ideas of causes to why the question was asked in case of CNS
      Engineer's Motto: If it aint broke take it apart and fix it

      "If at 1st you don't succeed... call it version 1.0"

      "Half of Programming is coding"....."The other 90% is DEBUGGING"

      "Document my code????" .... "WHYYY??? do you think they call it CODE? "

      Comment


      • #4
        Must be in your code ... what are you doing different than this simplistic example?

        Code:
        #COMPILE EXE
        #DIM ALL
        GLOBAL msg  AS STRING
        GLOBAL rslt AS LONG
        
        FUNCTION setheresult() AS LONG
            FUNCTION = 1234567890
        END FUNCTION
        
        FUNCTION setthemsg() AS STRING
            FUNCTION = "Hello World"
        END FUNCTION
        
        FUNCTION PBMAIN () AS LONG
            rslt = setheresult()
            msg = setthemsg()
            ? msg + $CRLF + FORMAT$(rslt)
        END FUNCTION
        Rick Angell

        Comment


        • #5
          WOW a lot learned

          MCM, I now see why the debate of GLOBALS vs LOCALS kind of thing.
          (although I will admit, I am BARELY scratching the surface and scratching my head. (like I have any hair left after all the years of wondering why or how something worked instead of "Just Accept it and move on")

          I was able to delve deeper, but fell into a few traps.
          1.) Compiles fine under PB9, but wondered why I did not have to declare the functions 1st??? (back of my mind is the ability to "Forward-Reference"???
          Code:
          #COMPILE EXE
          #DIM ALL
          #INCLUDE "Win32Api.inc"
          
          GLOBAL DeviceFound AS LONG              'Flag for if device found
          GLOBAL Buffer AS STRING                 'String for whatever the device has sent
          GLOBAL ThreadNumber AS LONG             'Thread Number
          GLOBAL HwndThread AS LONG               'Handle to the thread
          GLOBAL HwndThreadDone AS LONG           'Handle to if the thread is done running
          
          FUNCTION PBMAIN () AS LONG
               OpenListenThread                   'Create a listening thread
               DeviceFound = VerifyIfDevice       'If flag is set, then I found my device      '<---Note to self, should this function not work if I have not declared it yet?
               CloseListenThread                  'Never leave a thread open
          MSGBOX STR$(DeviceFound)
          
          END FUNCTION
          
          FUNCTION VerifyIfDevice() AS LONG
          'MSGBOX FUNCNAME$                        'Just to see if the function fired
          '**** 1 ms is so tight, that its hard to tell if the below is true
          SLEEP 1                                  '<--- Without a sleep to release my timeslice, my reply is 0, with a sleep it is 1
          'sleep 0                                  '<--- Pb9 fixed the sleep problems of PB8
               SELECT CASE RIGHT$(Buffer,1)
                    CASE "R", "J"
                         FUNCTION = %True
                         DeviceFound = %True     'Purposely set the flag
                    CASE "B", "b"
                         FUNCTION = %True
          '               DeviceSendToPort "K"    'Error Handling for real device
          '               DeviceFound = %True     'Purposely set the flag
                    CASE "^"
                         FUNCTION = %True
          '               DeviceFound = %True     'Purposely set the flag
                    CASE ELSE
                         SELECT CASE INSTR(Buffer, "Ë")     'Device Error
                              CASE 0
                                   FUNCTION = %False
                              CASE ELSE
                                   FUNCTION = %TRUE         'No Error that can not be recoverd from
          '                         DeviceSendToPort "K"    'Recover
          '               DeviceFound = %True               'Purposely set the flag
                         END SELECT
               END SELECT
          '     function = %TRUE                            'Purposely set the flag
          END FUNCTION
          
          FUNCTION OpenListenThread ALIAS "OpenListenThread"() AS LONG       'Added to re-enable the listen thread
               LOCAL lResult AS LONG
               THREAD CREATE ReceiveData(ThreadNumber) TO HwndThread  'Create a "listen" thread to monitor input from device
               FUNCTION = %True
          END FUNCTION
          
          FUNCTION CloseListenThread ALIAS "CloseListenThread"() AS LONG
               LOCAL lResult AS LONG
               SELECT CASE HwndThread
                    CASE %INVALID_HANDLE_VALUE, -1                    '%INVALID_HANDLE_VALUE used to be -1 and re-used as (&HFFFFFFFF??? = 4,294,967,295)
                         'Do NOTHING because closing an invalid handle raises an exception
                    CASE 0
                         'Do NOTHING because already closed
                    CASE ELSE
                         THREAD CLOSE HwndThread TO HwndThreadDone
                         SELECT CASE HwndThread
                              CASE %INVALID_HANDLE_VALUE, -1          '%INVALID_HANDLE_VALUE used to be -1 and re-used as (&HFFFFFFFF??? = 4,294,967,295)
                                   'Do NOTHING because closing an invalid handle raises an exception
                              CASE 0
                                   'Do NOTHING because already closed
                              CASE ELSE
                                   IF HwndThread THEN WaitForSingleObject HwndThread, %INFINITE     'Wait for it to finish, or risk GPF
                         END SELECT
               END SELECT
               IF HwndThread THEN HwndThread = 0
               FUNCTION = HwndThread
          END FUNCTION
          
          FUNCTION ReceiveData(BYVAL HwndTerminal AS DWORD) AS LONG
               Buffer = "R"                  'Simulate reply
               SLEEP 100                     '<--- Remember Sleep does not mean (wait 100ms, it means "Wait 100ms and give another thread a chance, if timeslice is returned and time is up, then continue...if not, let another slice have it)
          END FUNCTION
          PB8....Yep, my thought was clear and I was right. (without the declares, or unless I move the functions to the top before the main, forget it)
          Code:
          #COMPILE EXE
          #DIM ALL
          #INCLUDE "Win32Api.inc"
          
          GLOBAL DeviceFound AS LONG              'Flag for if device found
          GLOBAL Buffer AS STRING                 'String for whatever the device has sent
          GLOBAL ThreadNumber AS LONG             'Thread Number
          GLOBAL HwndThread AS LONG               'Handle to the thread
          GLOBAL HwndThreadDone AS LONG           'Handle to if the thread is done running
          
          DECLARE FUNCTION PBMAIN () AS LONG
          DECLARE FUNCTION VerifyIfDevice() AS LONG
          DECLARE FUNCTION OpenListenThread ALIAS "OpenListenThread"() AS LONG       'Added to re-enable the listen thread
          DECLARE FUNCTION CloseListenThread ALIAS "CloseListenThread"() AS LONG
          DECLARE FUNCTION ReceiveData(BYVAL HwndTerminal AS DWORD) AS LONG
          
          FUNCTION PBMAIN () AS LONG
               OpenListenThread                   'Create a listening thread
               DeviceFound = VerifyIfDevice       'If flag is set, then I found my device      '<---Note to self, should this function not work if I have not declared it yet?
               CloseListenThread                  'Never leave a thread open
          MSGBOX STR$(DeviceFound)
          
          END FUNCTION
          
          FUNCTION VerifyIfDevice() AS LONG
          'MSGBOX FUNCNAME$                        'Just to see if the function fired
          '**** 1 ms is so tight, that its hard to tell if the below is true
          'SLEEP 1                                  '<--- Without a sleep to release my timeslice, my reply is 0, with a sleep it is 1
          SLEEP 0                                  '<--- Pb9 fixed the sleep problems of PB8
               SELECT CASE RIGHT$(Buffer,1)
                    CASE "R", "J"
                         FUNCTION = %True
                         DeviceFound = %True     'Purposely set the flag
                    CASE "B", "b"
                         FUNCTION = %True
          '               DeviceSendToPort "K"    'Error Handling for real device
          '               DeviceFound = %True     'Purposely set the flag
                    CASE "^"
                         FUNCTION = %True
          '               DeviceFound = %True     'Purposely set the flag
                    CASE ELSE
                         SELECT CASE INSTR(Buffer, "Ë")     'Device Error
                              CASE 0
                                   FUNCTION = %False
                              CASE ELSE
                                   FUNCTION = %TRUE         'No Error that can not be recoverd from
          '                         DeviceSendToPort "K"    'Recover
          '               DeviceFound = %True               'Purposely set the flag
                         END SELECT
               END SELECT
          '     function = %TRUE                            'Purposely set the flag
          END FUNCTION
          
          FUNCTION OpenListenThread ALIAS "OpenListenThread"() AS LONG       'Added to re-enable the listen thread
               LOCAL lResult AS LONG
               THREAD CREATE ReceiveData(ThreadNumber) TO HwndThread  'Create a "listen" thread to monitor input from device
               FUNCTION = %True
          END FUNCTION
          
          FUNCTION CloseListenThread ALIAS "CloseListenThread"() AS LONG
               LOCAL lResult AS LONG
               SELECT CASE HwndThread
                    CASE %INVALID_HANDLE_VALUE, -1                    '%INVALID_HANDLE_VALUE used to be -1 and re-used as (&HFFFFFFFF??? = 4,294,967,295)
                         'Do NOTHING because closing an invalid handle raises an exception
                    CASE 0
                         'Do NOTHING because already closed
                    CASE ELSE
                         THREAD CLOSE HwndThread TO HwndThreadDone
                         SELECT CASE HwndThread
                              CASE %INVALID_HANDLE_VALUE, -1          '%INVALID_HANDLE_VALUE used to be -1 and re-used as (&HFFFFFFFF??? = 4,294,967,295)
                                   'Do NOTHING because closing an invalid handle raises an exception
                              CASE 0
                                   'Do NOTHING because already closed
                              CASE ELSE
                                   IF HwndThread THEN WaitForSingleObject HwndThread, %INFINITE     'Wait for it to finish, or risk GPF
                         END SELECT
               END SELECT
               IF HwndThread THEN HwndThread = 0
               FUNCTION = HwndThread
          END FUNCTION
          
          FUNCTION ReceiveData(BYVAL HwndTerminal AS DWORD) AS LONG
               Buffer = "R"                  'Simulate reply
               SLEEP 100                     '<--- Remember Sleep does not mean (wait 100ms, it means "Wait 100ms and give another thread a chance, if timeslice is returned and time is up, then continue...if not, let another slice have it)
          END FUNCTION
          Another thing I forgot was SLEEP 0 vs SLEEP 1, (but I believe this a case of the processor), that SLEEP 0 may have been a bug in 8 but in 9 still remains of sorts)

          On the lines that state
          Code:
          'sleep 1                                  '<--- Without a sleep to release my timeslice, my reply is 0, with a sleep it is 1
          SLEEP 0                                  '<--- Pb9 fixed the sleep problems of PB8
          if you compile and run over and over in PB8, you will get a messagebox of 0
          if you compile and run over and over in PB8, you will get a messagebox of 0 or 1 (depending on your computer and how fast you can do it)

          The only sure fire difference I have found, is to compile once and get 0 as the messagebox, and then add a comment, and the next compile shows a 1

          (maybe its nit-picking time-slices or something else, but could be the cause for my unknown results??)
          Engineer's Motto: If it aint broke take it apart and fix it

          "If at 1st you don't succeed... call it version 1.0"

          "Half of Programming is coding"....."The other 90% is DEBUGGING"

          "Document my code????" .... "WHYYY??? do you think they call it CODE? "

          Comment


          • #6
            Richard,
            Your post reminded me of the 1st thing I thought of and worked on because its typically the 1 thing that is wrong
            Must be in your code ... what are you doing different than this simplistic example?
            I thought I was doing nothing too much to change things, but like my last post shows, I think I am doing a WHOLE lot more than I thought I was doing


            And it may be as simple as my confusion of LoadLibrary a dll into my "Parent" Exe, but maybe not.

            <Jimminey cricket to myself>
            My VB background tells me nothing (but delve deeper, from mistakes of the past), my PB background tells me that there is a reason, the only problem is not knowing the reason, or how to research it, and my API side tells me that ("Hey its up to the OS" and the programmers before you, so it may not be answerable)

            Sad part, that brings me back to 1. cause I had to have done something that breaks the rules of either the OS, or the compiler, or something....(but if I did...then what is that rule, and how did I break it??)

            Fun part of understanding whats under the hood
            Engineer's Motto: If it aint broke take it apart and fix it

            "If at 1st you don't succeed... call it version 1.0"

            "Half of Programming is coding"....."The other 90% is DEBUGGING"

            "Document my code????" .... "WHYYY??? do you think they call it CODE? "

            Comment


            • #7
              Of course the 'amount of sleep' and 'your computer' change your results.

              You are reading and writing the GLOBAL variable 'buffer' from two different threads. That's your problem.

              When you start your program, you need to wait until you have obtained 'buffer' in "createlistenthread" (which launches function ReceiveData, which sets 'buffer') BEFORE you call VerifyIfDevice, which uses Buffer.


              [added]

              Which is so easy to do you are going to give us a
              Code:
              FUNCTION OpenListenThread ALIAS "OpenListenThread"() AS LONG       'Added to re-enable the listen thread
                   LOCAL lResult AS LONG
                   THREAD CREATE ReceiveData(ThreadNumber) TO HwndThread  'Create a "listen" thread to monitor input from device
                    ' **** Wait for the thread function to comple before returning  ****** ' 
                    '  *** otherwise we can't know the GLOBAL variable 'buffer' has been set ****
                    WaitForSingleObject  hwnThread, %INFINITE   
                   FUNCTION = %True
              END FUNCTION
              But of course this design makes no sense: why launch a separate thread of execution if your program can't continue until the thread function completes?

              (OK, so this is a fabricated example; in Real Life your program may in fact need to do these things simultaneously).


              MCM
              Last edited by Michael Mattias; 25 Oct 2008, 09:29 AM.
              Michael Mattias
              Tal Systems (retired)
              Port Washington WI USA
              [email protected]
              http://www.talsystems.com

              Comment


              • #8
                Cliff,
                a global, like any other variable, changes immediately.
                Your problem is that, as soon as you create threads, each thread will run asynchronously with respect to all the others unless you do something in your code to to synchronise them.
                It's possible that one thread will churn though millions of statements before another gets to do its bit of processing and there is no guarantee at all when which thread will run if you just leave it up to the OS to sort it out.

                In general, it is a bad idea to use SLEEP <any amount of time> in order to get threads to catch up with each other as SLEEP only guarantees that the current thread (the one executing the SLEEP statement) will give up its current time slice and wait .. there's no guarantee that the thread you want to run will get to take advantage of that time to catch up. It's quite possible the sleeping thread will resume before the other thread does anything.

                It's much better to use handshaking of some sort between the threads to guarantee they are synchronised.

                Paul.

                Comment


                • #9
                  Which is so easy to do you are going to give us a
                  nope not gonna do it cause like you said

                  why launch a separate thread of execution if your program can't continue until the thread function completes?
                  In this case, the other thread runs through-out the time the program is in action...aka....If the port is open, and something just happens to appear regardless of what function I am doing at the moment. Then read it so I see it.

                  Waiting for a thread to complete, would be the same to me as forget the thread just keep reading till the port gets my answer, and everything else must wait till I am done finding it....(INCLUDING any flags to abort looking for my answer)

                  This may be an argument for why to not use Globals vs Locals (or at least the 1st one that I could understand that is), but when I read it the way I THINK M$ works, then it makes more sense....

                  I will see if my other idea has more merit, and proves a point...if at least in my own mind.
                  Engineer's Motto: If it aint broke take it apart and fix it

                  "If at 1st you don't succeed... call it version 1.0"

                  "Half of Programming is coding"....."The other 90% is DEBUGGING"

                  "Document my code????" .... "WHYYY??? do you think they call it CODE? "

                  Comment


                  • #10
                    the other thread runs through-out the time the program is in action...aka....If the port is open, and something just happens to appear regardless of what function I am doing at the moment. Then read it so I see it.
                    This is the nearly classic case for using a separate thread of execution; IMNSHO you are absolutely on the right path.

                    However, you don't have to READ it, you have to REACT to it having occurred... just like ALL your Windows programs... your window/dialog procedure is not ASKING if the user clicked a button, it is REACTING to that click.

                    And how do you set that up to work that way?

                    How about the FAMILIAR? A Window?
                    Code:
                    %PWM_SOMETHING_HAPPENED = %WM_USER + 1 
                    FUNCTION WinMAin 
                       DIALOG  NEW... 
                    
                       THREAD CREATE   MyFunction (hDlg) .........
                    
                       DIALOG SHOW ...
                    
                    END FUNCTON
                    
                    CALLBACK FUNCTION MyDialogProc 
                      SELECT CASE CBMSG 
                           CASE %PWM_SOMETHING_HAPPENED 
                                  Read your data here because you know it's available
                    
                    
                    ...
                    
                    FUNCTION myThreadFunction (BYVAL hDlg) 
                    
                        DO 
                          wait for data 
                          PostMessage hDlg, %PWM__SOMETHING_HAPPENED, wParam, lparam 
                       LOOP 
                    END FUNCTION
                    That's a crude picture. However, this is pretty much the way all the demos I've created for using multiple threads of execution work: Worker thread waits for event to occur and does a notification.

                    MCM
                    Michael Mattias
                    Tal Systems (retired)
                    Port Washington WI USA
                    [email protected]
                    http://www.talsystems.com

                    Comment


                    • #11
                      MCM,
                      Thats exactly what I am doing, with the exception that I still have Globals in my code
                      I am working on improving my code that I did not quite understand in the past (it just worked) to speed things up, and document what I know now what I did not know when I 1st wrote the code years ago. And clean up functions that could really have done more neatly, and cleanly.

                      The whole thing about when Globals getting set was when I sped things up from my "Safe" time of 100ms to the r"Ridiculous"? time of 1ms. So I thought I would ask so I got a better idea how things really work.
                      Engineer's Motto: If it aint broke take it apart and fix it

                      "If at 1st you don't succeed... call it version 1.0"

                      "Half of Programming is coding"....."The other 90% is DEBUGGING"

                      "Document my code????" .... "WHYYY??? do you think they call it CODE? "

                      Comment


                      • #12
                        Well, for sure you can eliminate the GLOBAL "buffer"

                        Win/9 you have?

                        Code:
                        FUNCTION MyThreadFunction.....
                        
                         LOCAL hMem AS LONG, pAZ AS ASCIIZ PTR , buffer as STRING
                          DO 
                              wait for data 
                              Put into 'buffer' 
                              GLOBALMEM ALLOC   LEN(Buffer) + 1  to hMem
                              GLOBALMEM LOCK     hMem TO pAZ   
                              @pAZ                  =  Buffer 
                              GLOBALMEM UNLOCK  hMem 
                              PostMessage hDlg, %PWM__SOMETHING_HAPPENED, %NULL, [B][COLOR="Red"]hMem[/COLOR][/B]
                           LOOP 
                        
                        .....
                        CALLBACK FUNCTION MyDialogProc 
                          LOCAL Buffer as STRING, pAZ AS ASCIIZ PTR, hMem AS LONG 
                          SELECT CASE CBMSG 
                               CASE %PWM_SOMETHING_HAPPENED 
                                      Read your data here because you know it's available:
                                      hMem = CBLPARAM    ' handle was passed as lparam when message was posted
                                      GLOBALMEM LOCK   hMEm TO pAZ 
                                      Buffer  =  @pAZ 
                                      GLOBALMEM UNLOCK hMEm
                                      GLOBALMEM FREE     hMem 
                                    ' ---------------------------------------------------------------------------
                                    '  Oh, look ma! Buffer is a LOCAL string, so my dialog procedure 
                                    '  is fully re-entrant and can support multiple simultaneous "threadfunctions"
                                    ' ---------------------------------------------------------------------------
                        
                        ....

                        MCM
                        Michael Mattias
                        Tal Systems (retired)
                        Port Washington WI USA
                        [email protected]
                        http://www.talsystems.com

                        Comment


                        • #13
                          That's one thing Michael schooled me on in threads - not using globals that will be affected by a thread - cuz if one thread affects it an identical thread might undo it...

                          I sitll use them, and will use a global flag or in one case I have an array of 10 flags to notify when all ten threads are complete....it works for me and is still 6 hrs faster than a similiar VB app.

                          But ya, I use globasl as a set value, only to be changed by ONE options screen or an ini file....ie g_RunInSystemTray yes/no ....
                          Scott Turchin
                          MCSE, MCP+I
                          http://www.tngbbs.com
                          ----------------------
                          True Karate-do is this: that in daily life, one's mind and body be trained and developed in a spirit of humility; and that in critical times, one be devoted utterly to the cause of justice. -Gichin Funakoshi

                          Comment


                          • #14
                            I have an array of 10 flags to notify when all ten threads are complete....
                            Code:
                              WaitforMultipleObjects  nThread, VARPTR(hThread(0)), [COLOR="Red"]%TRUE[/COLOR], %INFINITE
                              ' when WFMO returns, [COLOR="Red"]all [/COLOR]thead functions are completed.
                            Michael Mattias
                            Tal Systems (retired)
                            Port Washington WI USA
                            [email protected]
                            http://www.talsystems.com

                            Comment


                            • #15
                              That's one thing Michael schooled me on in threads - not using globals that will be affected by a thread - cuz if one thread affects it an identical thread might undo it...
                              I got that too...but much like my mistakes of the past (aka: not understanding that LoadLibrary maps the dll into my exe) I always thought that the only way to confuse a global (being my flag) was to have multiple threads. Now I know that technically my main app, and just 1 thread, should be considered as 2 threads (and hence the ability for the main to not contain what I tried to achieve in vain )

                              (ok bad poetry there, but hey it works )

                              Now that I have that straight in my head, on to the next hurdle (although I hate to do it, but have to for my purposes later (and quickly finding that thoughts of the past should be TOTALLY re-written)
                              Engineer's Motto: If it aint broke take it apart and fix it

                              "If at 1st you don't succeed... call it version 1.0"

                              "Half of Programming is coding"....."The other 90% is DEBUGGING"

                              "Document my code????" .... "WHYYY??? do you think they call it CODE? "

                              Comment


                              • #16
                                Originally posted by Michael Mattias View Post
                                Code:
                                  WaitforMultipleObjects  nThread, VARPTR(hThread(0)), [COLOR="Red"]%TRUE[/COLOR], %INFINITE
                                  ' when WFMO returns, [COLOR="Red"]all [/COLOR]thead functions are completed.
                                I know, I know....never have successfully gotten that to work but still...I'd LIKE to! LOL

                                THis one app copies 10 sets of files at one time, it's an 8 processor machine with 32 gigs of ram in it....10 threads is like no big deal to it...
                                Scott Turchin
                                MCSE, MCP+I
                                http://www.tngbbs.com
                                ----------------------
                                True Karate-do is this: that in daily life, one's mind and body be trained and developed in a spirit of humility; and that in critical times, one be devoted utterly to the cause of justice. -Gichin Funakoshi

                                Comment


                                • #17
                                  >never have successfully gotten that [WFMO on array of thread handles] to work

                                  You haven't tried very hard, have you?
                                  Michael Mattias
                                  Tal Systems (retired)
                                  Port Washington WI USA
                                  [email protected]
                                  http://www.talsystems.com

                                  Comment


                                  • #18
                                    I am thinking about posting this to the source code forum, but before I do, can anyone spot any fundamental mistakes I may have made in eliminating GLOBALS?

                                    Code:
                                    '*** Compiler Directives
                                    #COMPILE EXE
                                    #DIM ALL
                                    #INCLUDE "Win32Api.inc"
                                    '*** Equates
                                    '***      Although not GLOBALS they are known to all functions/subs involved that need them
                                    %UNKNOWN = -1
                                    '*** Declares
                                    '***      Most code shows without, I am more a believer of "THOU SHALT DECLARE, WHAT THOU SHALT USE"
                                    DECLARE FUNCTION PBMAIN () AS LONG                                         'Main Program
                                    DECLARE FUNCTION VerifyIfDevice() AS LONG                                  'Used to verify if the device attached is the one you are looking for
                                    DECLARE FUNCTION OpenListenThread ALIAS "OpenListenThread"() AS LONG       'Thread to "Listen" for an event on the serial port
                                    DECLARE FUNCTION CloseListenThread ALIAS "CloseListenThread"() AS LONG     'Close the thread before exiting to avoid crashes
                                    DECLARE FUNCTION ReceiveData(BYVAL HwndTerminal AS DWORD) AS LONG          'Does the work of checking if some event (aka character is ready to be read)
                                    '*** Global Replacement functions
                                    '***      Use replacement functions to store variables so correct value is valid between threads
                                    '***           Return value is DWORD to indicate if the function had an error (%TRUE = NON-ZERO, or %FALSE = ZERO, to work with most API Functions)
                                    DECLARE FUNCTION SetGetBuffer(Buffer AS STRING, ReturnBuffer AS STRING, ResetBuffer AS LONG) AS DWORD                   'Replacement for global variable
                                    DECLARE FUNCTION SetGetDeviceFound(DeviceFound AS LONG, ReturnDeviceFound AS LONG, ResetDeviceFound AS LONG) AS DWORD   'Replacement for global variable
                                    DECLARE FUNCTION SetGetHwndThread(HwndThread AS LONG, ReturnHwndThread AS LONG, ResetHwndThread AS LONG) AS DWORD       'Replacement for global variable
                                    '**************************************************************************************************************
                                    '*** Globals
                                    '***      Globals replaced by functions to avoid invalid values, and speed up processes
                                    'GLOBAL Buffer AS STRING                                                   'String for whatever the device has sent
                                    'GLOBAL DeviceFound AS LONG                                                 'Flag for if device found
                                    'GLOBAL ThreadNumber AS LONG                                                'Thread Number (No longer needed whatsoever)
                                    'GLOBAL HwndThread AS LONG               'Handle to the thread
                                    'GLOBAL HwndThreadDone AS LONG           'Handle to if the thread is done running
                                    '**************************************************************************************************************
                                    
                                    FUNCTION PBMAIN () AS LONG
                                         LOCAL Msg AS STRING                                                   'Local variable for MSGBOX demonstration
                                         LOCAL Buffer AS STRING                                                '<--- Was a Global, now a function (being local, is more understandable to read in larger projects)
                                         LOCAL DeviceFoundBefore AS LONG                                       '<--- Was a Global, now a function (being local, is more understandable to read in larger projects)
                                         LOCAL DeviceFoundAfter AS LONG                                       '<--- Was a Global, now a function (being local, is more understandable to read in larger projects)
                                    '*** Demo variables kept local
                                         LOCAL PurposeSetGetBuffer AS STRING                                   'For Demo Purpose
                                         LOCAL PurposeError AS LONG                                            'For Demo Purpose (If I had left error handling in)
                                    
                                         OpenListenThread                                                      'Create a listening thread
                                    '     SLEEP 100                                             '<--- Remember Sleep does not mean (wait 100ms, it means "Wait 100ms and give another thread a chance, if timeslice is returned and time is up, then continue...if not, let another slice have it)
                                         SLEEP 1                                                     '<--- Remember Sleep does not mean (wait Xms, it means "Wait Xms and give another thread a chance, if timeslice is returned and time is up, then continue...if not, let another slice have it)
                                    '*** For Demo Purposees
                                         SetGetDeviceFound(0, DeviceFoundBefore, %UNKNOWN)                           'Call the function just to see if the variable is set
                                    '     DeviceFound = VerifyIfDevice                                         'If flag is set, then I found my device      '<---Note to self, should this function not work if I have not declared it yet?
                                         CloseListenThread                                                     'Never leave a thread open
                                         SetGetDeviceFound(%FALSE, DeviceFoundAfter, %TRUE)                         'Release Device found
                                         SetGetDeviceFound(0, DeviceFoundAfter, %UNKNOWN)                           'Call the function just to see if the variable is set
                                    '*** Demonstrate
                                         SetGetBuffer("", Buffer, %UNKNOWN)                                    'Check if anything in the buffer
                                    '*** Build the MSGBOX string
                                         Msg = Msg + FUNCNAME$ + $CR                                           'Function Name
                                         Msg = Msg + "Buffer = " + Buffer + $CR                                'What would be in the buffer
                                         Msg = Msg + "Error in DeviceFound = " + STR$(DeviceFoundBefore)             'If error handling then what would be in the error
                                         Msg = Msg + $CR
                                         Msg = Msg + "Device Handle before release = " + STR$(DeviceFoundBefore) + $CR               'If DeviceFound (even simulated)
                                         Msg = Msg + "Device Handle after release = " + STR$(DeviceFoundAfter) + $CR 'If DeviceFound (even simulated)
                                    '*** Show the messagebox
                                         MSGBOX Msg
                                    END FUNCTION
                                    
                                    FUNCTION VerifyIfDevice() AS LONG
                                         LOCAL Buffer AS STRING                                                '<--- Was a Global, now a function (being local, is more understandable to read in larger projects)
                                         LOCAL DeviceFound AS LONG                                             '<--- Was a Global, now a function (being local, is more understandable to read in larger projects)
                                         SetGetBuffer("", Buffer, %UNKNOWN)                                    'Call the function just to see if the variable is set
                                         SELECT CASE RIGHT$(Buffer,1)
                                              CASE "R", "J"                                                    'Purposely set the flag
                                                   SetGetDeviceFound(%TRUE, DeviceFound, %TRUE)
                                    '               DeviceFound = %True
                                              CASE "B", "b"                                                    'Purposely set the flag
                                              CASE "^"                                                         'Purposely set the flag
                                              CASE ELSE                                                        'Purposely set the flag
                                         END SELECT
                                    '     function = %TRUE                                                     'Purposely set the flag for an error if there was an error
                                    END FUNCTION
                                    
                                    FUNCTION OpenListenThread ALIAS "OpenListenThread"() AS LONG
                                         STATIC ThreadNumber AS LONG
                                         LOCAL HwndThread AS LONG
                                         IF ThreadNumber = 0 THEN ThreadNumber = ThreadNumber + 1              '*** Although Hardcoded, it demonstrates creating a thread
                                         SetGetDeviceFound(ThreadNumber, HwndThread, %TRUE)                    'Call the function and set it
                                         THREAD CREATE ReceiveData(ThreadNumber) TO HwndThread                 'Create a "listen" thread to monitor input from device
                                         FUNCTION = %True
                                    END FUNCTION
                                    
                                    FUNCTION CloseListenThread ALIAS "CloseListenThread"() AS LONG
                                         STATIC ThreadNumber AS LONG
                                         LOCAL HwndThread AS LONG
                                         SetGetHwndThread(0, HwndThread, %UNKNOWN)                             'Call the function to get the HwndThread
                                         IF ThreadNumber <> 0 THEN ThreadNumber = ThreadNumber - 1
                                         SetGetDeviceFound(0, ThreadNumber, %UNKNOWN)                            'Call the function just to see if the variable is set
                                         SELECT CASE HwndThread
                                              CASE %INVALID_HANDLE_VALUE, -1                                   '%INVALID_HANDLE_VALUE used to be -1 and re-used as (&HFFFFFFFF??? = 4,294,967,295)
                                                   'Do NOTHING because closing an invalid handle raises an exception
                                              CASE 0
                                                   'Do NOTHING because already closed
                                              CASE ELSE
                                                   THREAD CLOSE HwndThread TO HwndThread
                                                   SELECT CASE HwndThread
                                                        CASE %INVALID_HANDLE_VALUE, -1                         '%INVALID_HANDLE_VALUE used to be -1 and re-used as (&HFFFFFFFF??? = 4,294,967,295)
                                                             'Do NOTHING because closing an invalid handle raises an exception
                                                        CASE 0
                                                             'Do NOTHING because already closed
                                                        CASE ELSE
                                                             IF HwndThread THEN WaitForSingleObject HwndThread, %INFINITE     'Wait for it to finish, or risk GPF
                                                   END SELECT
                                         END SELECT
                                         IF HwndThread THEN HwndThread = 0
                                         SetGetHwndThread(HwndThread, HwndThread, %True)                       'Call the function to reset the HwndThread
                                         FUNCTION = HwndThread
                                    END FUNCTION
                                    
                                    FUNCTION ReceiveData(BYVAL HwndTerminal AS DWORD) AS LONG
                                         LOCAL TempBuffer AS STRING                                            'Copy of what would be in the "GLOBAL" buffer
                                    '     Buffer = "R"                                                         'Simulate reply
                                         SetGetBuffer("R", TempBuffer, %True)                                  'Simulate reply
                                    END FUNCTION
                                    
                                    FUNCTION SetGetBuffer(Buffer AS STRING, ReturnBuffer AS STRING, ResetBuffer AS LONG) AS DWORD
                                         STATIC TempBuffer AS STRING                                           'Copy of what would be in the "GLOBAL" buffer
                                         SELECT CASE ResetBuffer                                               'Depending on Reset, either Set, or check the current value
                                              CASE %FALSE, %UNKNOWN
                                                   ReturnBuffer = TempBuffer
                                              CASE = %TRUE
                                                   TempBuffer = Buffer
                                                   Buffer = TempBuffer
                                         END SELECT
                                    END FUNCTION
                                    
                                    FUNCTION SetGetDeviceFound(DeviceFound AS LONG, ReturnDeviceFound AS LONG, ResetDeviceFound AS LONG) AS DWORD
                                         STATIC TempDeviceFound AS LONG                                        'Copy of what would be in the "GLOBAL" buffer
                                         SELECT CASE ResetDeviceFound                                          'Depending on Reset, either Set, or check the current value
                                              CASE %FALSE, %UNKNOWN
                                                   ReturnDeviceFound = TempDeviceFound
                                              CASE = %TRUE
                                                   TempDeviceFound = DeviceFound
                                                   DeviceFound = TempDeviceFound
                                         END SELECT
                                    END FUNCTION
                                    
                                    FUNCTION SetGetHwndThread (HwndThread  AS LONG, ReturnHwndThread  AS LONG, ResetHwndThread  AS LONG) AS DWORD
                                         STATIC TempHwndThread  AS LONG                                        'Copy of what would be in the "GLOBAL" buffer
                                         SELECT CASE ResetHwndThread                                           'Depending on Reset, either Set, or check the current value
                                              CASE %FALSE, %UNKNOWN
                                                   ReturnHwndThread  = TempHwndThread
                                              CASE = %TRUE
                                                   TempHwndThread  = HwndThread
                                                   HwndThread  = TempHwndThread
                                         END SELECT
                                    END FUNCTION
                                    Engineer's Motto: If it aint broke take it apart and fix it

                                    "If at 1st you don't succeed... call it version 1.0"

                                    "Half of Programming is coding"....."The other 90% is DEBUGGING"

                                    "Document my code????" .... "WHYYY??? do you think they call it CODE? "

                                    Comment

                                    Working...
                                    X