Announcement

Collapse
No announcement yet.

When does a Global Change?

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

  • Cliff Nichols
    replied
    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

    Leave a comment:


  • Michael Mattias
    replied
    >never have successfully gotten that [WFMO on array of thread handles] to work

    You haven't tried very hard, have you?

    Leave a comment:


  • Scott Turchin
    replied
    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...

    Leave a comment:


  • Cliff Nichols
    replied
    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)

    Leave a comment:


  • Michael Mattias
    replied
    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.

    Leave a comment:


  • Scott Turchin
    replied
    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 ....

    Leave a comment:


  • Michael Mattias
    replied
    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

    Leave a comment:


  • Cliff Nichols
    replied
    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.

    Leave a comment:


  • Michael Mattias
    replied
    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

    Leave a comment:


  • Cliff Nichols
    replied
    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.

    Leave a comment:


  • Paul Dixon
    replied
    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.

    Leave a comment:


  • Michael Mattias
    replied
    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, 10:29 AM.

    Leave a comment:


  • Cliff Nichols
    replied
    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

    Leave a comment:


  • Cliff Nichols
    replied
    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??)

    Leave a comment:


  • Richard Angell
    replied
    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

    Leave a comment:


  • Cliff Nichols
    replied
    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

    Leave a comment:


  • Michael Mattias
    replied
    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).

    Leave a comment:


  • Cliff Nichols
    started a topic When does a Global Change?

    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?)
Working...
X