/*--------------------------------------------------------------------------* * File Name: ThreadSubObj.cpp * * Purpose: Demostrate realtime update on a graph * * Creation: April 04, 2000 * * Copyright (c) 2000 Microcal Software, Inc. * * * * Modification Log: * *--------------------------------------------------------------------------*/ #include "ThreadSubObj.h" int random_int(int iMin, int iMax); static UINT GenericThreadRun(LPVOID lpParam); //--------------------------------------------------------------------------- // Property Map: // // The Property Map is for declaring the properties of your LabTalk object. // // A property is mapped to Get and Set function. These functions allow // you to do error checking or any necessary conversions. A read-only // property can be declared by using the _GET macro. // // MOCA_PROP_INT( ,, ) // MOCA_PROP_REAL( ,, ) // MOCA_PROP_STR( ,, ) // MOCA_PROP_INT_GET( , ) // MOCA_PROP_REAL_GET( , ) // MOCA_PROP_STR_GET( , ) // // A Simple Property is mapped to a data member. There are no Get/Set // functions for a Simple Property. MOCA will take care of the assignment. // A simple read-only property can be declared by using the _GET macro. // // MOCA_SIMPLE_PROP_INT( , ) // MOCA_SIMPLE_PROP_REAL( , ) // MOCA_SIMPLE_PROP_STR( , ) // MOCA_SIMPLE_PROP_INT_GET( , ) // MOCA_SIMPLE_PROP_REAL_GET( , ) // MOCA_SIMPLE_PROP_STR_GET( , ) //--------------------------------------------------------------------------- MOCA_BEGIN_PROP_MAP(CThreadSubObj, CMOCAObjBase) MOCA_PROP_INT(GetPriority, SetPriority, "Priority") MOCA_SIMPLE_PROP_INT_GET(m_iStatus, "Status") MOCA_PROP_STR_GET(GetStatusStr, "StatusDescrip") MOCA_END_PROP_MAP(CThreadSubObj, CMOCAObjBase) //--------------------------------------------------------------------------- // Method Map: // // INPORTANT: To have a Method Map or a SubObject Map, you must // have a property map. // // MOCA_METHOD( , ) // // must be declared as "BOOL foo(double &, CStringArray &);" // The double is used for storing LabTalk's return value. // The CStringArray contains the arguments passed from LabTalk. //--------------------------------------------------------------------------- MOCA_BEGIN_METH_MAP(CThreadSubObj, CMOCAObjBase) MOCA_METH_ENTRY(Meth_Start, "Start") MOCA_METH_ENTRY(Meth_Suspend, "Suspend") MOCA_METH_ENTRY(Meth_Resume, "Resume") MOCA_METH_ENTRY(Meth_Kill, "Kill") MOCA_END_METH_MAP(CThreadSubObj, CMOCAObjBase) //--------------------------------------------------------------------------- CThreadSubObj::CThreadSubObj() { m_iNumPoints = 150; // obj.NumPoints m_iPriority = DOTHREAD_PRIORITY_NORMAL; // obj.Thread.Priority m_iStatus = DOTHREAD_STATUS_NOT_PRESENT; // obj.Thread.Status m_pThread = NULL; m_bKill = FALSE; } CThreadSubObj::~CThreadSubObj() { } //--------------------------------------------------------------------------- BOOL CThreadSubObj::GetPriority(int &iValue) { iValue = m_iPriority; return TRUE; } BOOL CThreadSubObj::SetPriority(int iValue) { if( iValue >= 0 && iValue <= DOTHREAD_PRIORITY_MAXIMUM ) { m_iPriority = iValue; return TRUE; } return FALSE; } //--------------------------------------------------------------------------- // CThreadSubObj::GetStatusStr // //--------------------------------------------------------------------------- BOOL CThreadSubObj::GetStatusStr(LPSTR lpstr) { switch( m_iStatus ) { case DOTHREAD_STATUS_RUNNING: lstrcpy(lpstr, "Running"); break; case DOTHREAD_STATUS_SUSPENDED: lstrcpy(lpstr, "Suspended"); break; case DOTHREAD_STATUS_NOT_PRESENT: lstrcpy(lpstr, "Not Present"); break; default: lstrcpy(lpstr, "Unknown"); break; } return TRUE; } //--------------------------------------------------------------------------- // CThreadSubObj::Meth_Start // // LabTalk will return 1 on success and 0 on failure. //--------------------------------------------------------------------------- BOOL CThreadSubObj::Meth_Start(double &dValue, CStringArray &strArgArray) { if( strArgArray.GetSize() == 0 ) // method expects no arguments { if( IS_THREAD_STARTED(m_iStatus) ) { dValue = 1.0; // already started, return success to LabTalk } else { static const int l_nPriority[] = { THREAD_PRIORITY_IDLE, THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST, // computer might freeze with this priority THREAD_PRIORITY_TIME_CRITICAL, // computer might freeze with this priority }; ASSERT(NULL == m_pThread); ASSERT(sizeof(l_nPriority)/sizeof(l_nPriority[0]) == DOTHREAD_PRIORITY_MAXIMUM + 1); // Convert LabTalk priority value to Windows API priority value int iPriority = l_nPriority[m_iPriority]; // We are passing the pointer to the method object // as lpParam, so that we can use it inside 'GenericThreadRun'. m_pThread = AfxBeginThread(GenericThreadRun, this, iPriority, 0); if( m_pThread ) { m_iStatus = DOTHREAD_STATUS_RUNNING; dValue = 1.0; } else dValue = 0.0; } return TRUE; } return FALSE; } //--------------------------------------------------------------------------- // CThreadSubObj::Meth_Kill // // LabTalk will return 1 on success and 0 on failure. //--------------------------------------------------------------------------- BOOL CThreadSubObj::Meth_Kill(double &dValue, CStringArray &strArgArray) { if( strArgArray.GetSize() == 0 ) // method expects no arguments { if( IS_THREAD_STARTED(m_iStatus) ) { ASSERT_VALID(m_pThread); // If the thread is not suspended then suspend it if( !IS_THREAD_SUSPENDED(m_iStatus) ) VERIFY(0 == m_pThread->SuspendThread()); // We do not set 'm_iStatus' here. It will be set by the thread. // Here we simply set the Kill flag. m_bKill = TRUE; // Resume thread and allow it to end gracefully VERIFY(1 == m_pThread->ResumeThread()); } dValue = 1.0; // return success to LabTalk return TRUE; } return FALSE; } //--------------------------------------------------------------------------- // CThreadSubObj::Meth_Suspend // // LabTalk will return 1 on success and 0 on failure. //--------------------------------------------------------------------------- BOOL CThreadSubObj::Meth_Suspend(double &dValue, CStringArray &strArgArray) { if( strArgArray.GetSize() == 0 ) { if( IS_THREAD_RUNNING(m_iStatus) ) { ASSERT_VALID(m_pThread); // Check the return value (must be 0 if the thread was running) VERIFY(0 == m_pThread->SuspendThread()); // Update status property m_iStatus = DOTHREAD_STATUS_SUSPENDED; } dValue = 1.0; // return success to LabTalk return TRUE; } return FALSE; } //--------------------------------------------------------------------------- // CThreadSubObj::Meth_Resume // // LabTalk will return 1 on success and 0 on failure. //--------------------------------------------------------------------------- BOOL CThreadSubObj::Meth_Resume(double &dValue, CStringArray &strArgArray) { if( strArgArray.GetSize() == 0 ) { if( IS_THREAD_SUSPENDED(m_iStatus) ) { ASSERT_VALID(m_pThread); DWORD dw = m_pThread->ResumeThread(); ASSERT(dw != 0); // should not happen here // ResumeThread returns the follwing: // 0 = thread was not suspended // 1 = thread was suspended, but now restarted // >1 = thread is still suspended // 0xFFFFFFFF = failure if( dw == 0xFFFFFFFF ) { dValue = 0.0; // return failure to LabTalk } else { dValue = 1.0; // return success to LabTalk if( dw == 1 ) m_iStatus = DOTHREAD_STATUS_RUNNING; } } return TRUE; } return FALSE; } //--------------------------------------------------------------------------- // CThreadSubObj::Run // // This is where the thread spends most of it's time. //--------------------------------------------------------------------------- UINT CThreadSubObj::Run() { ASSERT_VALID(m_pThread); MOINDEX ii; double dd; BOOL bDone = FALSE; // Get number of points to update int iNumPoints = m_iNumPoints; // Update status property m_iStatus = DOTHREAD_STATUS_RUNNING; MoResultData dataY(m_strWksName, 2); // second column if( !dataY.IsValid() ) goto finish_thread; // Make sure redraw is done in real time: dataY.set_redraw_mode(MoData::REALTIME_ON_IDLE | MoData::REDRAW_REALTIME); // Modata.iRange2 is the actual range already in wks // Modata.i2 is the active/selected range that math calculation should use // Loop forever (the thread will be killed from outside) do { MOINDEX FirstRow = dataY.i2(); MOINDEX LastRow = FirstRow + iNumPoints; for( ii = FirstRow; ii <= LastRow; ii++ ) { /// we only do SetValue if within range as otherwise /// SetValue will attemp to expand the COKData memory which is /// not recommended if( ii + 1 >= m_iMaxNumPoints) { m_bKill = TRUE; break; } // The following if/else is used to create some random data // based on the existing data. In a "real" DLL this data would // come from another source such as a data aquisition board. dd = 0.0; if( random_int(0,1) ) { dd += random_int(0,20); if( dd > 200 ) dd = 200; } else { dd -= random_int(0,20); if( dd < 0 ) dd = 0; } // Put the data into the Y column. dataY.SetValue(ii, dd); } // Force idle update dataY.UpdateRange(); Sleep(500); // 500 miliseconds // Check if user wants to kill the thread if ( m_bKill ) { bDone = TRUE; break; } } while( !bDone ); finish_thread: // Clear the Kill flag. m_bKill = FALSE; // We do not delete the thread here. MFC will delete it when it exits. m_pThread = NULL; // Update status property. m_iStatus = DOTHREAD_STATUS_NOT_PRESENT; return 0; } //--------------------------------------------------------------------------- // GenericThreadRun // // This is the entry point into the running action of the thread. // The thread will terminate either when this function returns // or when it is killed from outside. //--------------------------------------------------------------------------- static UINT GenericThreadRun(LPVOID lpParam) { CThreadSubObj *pDoThread = (CThreadSubObj*)lpParam; return pDoThread->Run(); } //--------------------------------------------------------------------------- // random_int // //--------------------------------------------------------------------------- int random_int(int iMin, int iMax) { int i = iMax - iMin + 1; double d; d = (double)RAND_MAX / (double)i; d = (double)rand() / d; return (iMin + (int)d); }