Process Pool Demo: Difference between revisions
From DataFlex Wiki
Jump to navigationJump to search
m Adding link |
Hsymington (talk | contribs) m Added to category Process Pooling |
||
(8 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
In an [https://www.unicorninterglobal.com/company/blog/dataflex-webapp-process-pooling-demo article] on the [https://www.unicorninterglobal.com Unicorn InterGlobal web site] I describe my Process Pooling Demo web view. | In an [https://www.unicorninterglobal.com/company/blog/dataflex-webapp-process-pooling-demo article] on the [https://www.unicorninterglobal.com Unicorn InterGlobal web site] I describe my Process Pooling Demo web view. | ||
The code for it is as follows and you can download the zip file from [[File:ProcPoolDemo.zip | The code for it is as follows and you can download the zip file from this link: [[File:ProcPoolDemo.zip]]. | ||
(Note: now modified to work with DataFlex 20.0 as well as DataFlex 19.1... and possibly prior versions as well - no guarantees though. I have only tested with 19.1 and 20.0.) | |||
<source lang="dataflex"> | <source lang="dataflex"> | ||
Line 12: | Line 14: | ||
Use cWebSpacer.pkg | Use cWebSpacer.pkg | ||
Use cWebList.pkg | Use cWebList.pkg | ||
Use cWebColumn.pkg | |||
Use cWebLabel.pkg | Use cWebLabel.pkg | ||
Use cRegistry.pkg | Use cRegistry.pkg | ||
Use | Use Flexml.pkg | ||
Define PROCESS_VM_READ for |CI$0010 | |||
Define PROCESS_QUERY_INFORMATION for |CI$0400 | |||
Define | Define KEY_WOW64_64KEY for |CI$0100 | ||
// Wrap all these in #IFNDEF/#ENDIF blocks to avoid any conflicts | // Wrap all these in #IFNDEF/#ENDIF blocks to avoid any conflicts | ||
// with the same things already defined in the application | // with the same things already defined in the application | ||
#IFNDEF get_GetLastError | #IFNDEF get_GetLastError | ||
External_Function GetLastError "GetLastError" Kernal32.DLL Returns DWord | External_Function GetLastError "GetLastError" Kernal32.DLL Returns DWord | ||
#ENDIF | #ENDIF | ||
#IFNDEF get_GetCurrentProcessId | #IFNDEF get_GetCurrentProcessId | ||
External_Function GetCurrentProcessId "GetCurrentProcessId" Kernel32.Dll Returns Integer | External_Function GetCurrentProcessId "GetCurrentProcessId" Kernel32.Dll Returns Integer | ||
#ENDIF | #ENDIF | ||
#IFNDEF get_EnumProcesses | #IFNDEF get_EnumProcesses | ||
External_Function EnumProcesses "K32EnumProcesses" Kernel32.DLL ; | External_Function EnumProcesses "K32EnumProcesses" Kernel32.DLL ; | ||
Line 37: | Line 42: | ||
Returns Integer | Returns Integer | ||
#ENDIF | #ENDIF | ||
#IFNDEF get_OpenProcess | #IFNDEF get_OpenProcess | ||
External_Function OpenProcess "OpenProcess" Kernel32.Dll ; | External_Function OpenProcess "OpenProcess" Kernel32.Dll ; | ||
Line 45: | Line 50: | ||
Returns Handle | Returns Handle | ||
#ENDIF | #ENDIF | ||
#IFNDEF get_ProcessHandle | #IFNDEF get_ProcessHandle | ||
External_Function ProcessHandle "OpenProcess" Kernel32.DLL ; | External_Function ProcessHandle "OpenProcess" Kernel32.DLL ; | ||
Line 53: | Line 58: | ||
Returns Handle | Returns Handle | ||
#ENDIF | #ENDIF | ||
#IFNDEF get_ProcessImageFileName | #IFNDEF get_ProcessImageFileName | ||
External_Function ProcessImageFileName "K32GetProcessImageFileNameA" Kernel32.DLL ; | External_Function ProcessImageFileName "K32GetProcessImageFileNameA" Kernel32.DLL ; | ||
Line 61: | Line 66: | ||
Returns DWord | Returns DWord | ||
#ENDIF | #ENDIF | ||
#IFNDEF _struct_stAllMyState | #IFNDEF _struct_stAllMyState | ||
// Struct to hold state | // Struct to hold state | ||
Line 75: | Line 80: | ||
End_Struct | End_Struct | ||
#ENDIF | #ENDIF | ||
#IFNDEF C_CRLF | #IFNDEF C_CRLF | ||
// Just for formatting the result of the Yes/No cascade in this case: | // Just for formatting the result of the Yes/No cascade in this case: | ||
Define C_CRLF for (Character(13) + Character(10)) | Define C_CRLF for (Character(13) + Character(10)) | ||
#ENDIF | #ENDIF | ||
// The following items will (almost always) have different values in | // The following items will (almost always) have different values in | ||
// different processes in the WebApp process pool, but will remain static | // different processes in the WebApp process pool, but will remain static | ||
// in any given process. | // in any given process. | ||
Global_Variable Integer giRandom | Global_Variable Integer giRandom | ||
Move (Random(10000)) to giRandom | Move (Random(10000)) to giRandom | ||
// This will be a property of oWabApp: | // This will be a property of oWabApp: | ||
Property Integer piRandom (Random(10000) + 10000) | Property Integer piRandom (Random(10000) + 10000) | ||
// Will be opened in the WebApp | // Will be opened in the WebApp | ||
Open Flexerrs | Open Flexerrs | ||
Line 96: | Line 101: | ||
Move (Random(99) + 1) to Flexerrs.Recnum | Move (Random(99) + 1) to Flexerrs.Recnum | ||
Find EQ FlexErrs by Recnum | Find EQ FlexErrs by Recnum | ||
//============================================================================== | //============================================================================== | ||
// This is a Modal Dialog which will be called from the View, | // This is a Modal Dialog which will be called from the View, | ||
Line 108: | Line 113: | ||
Set pbShowClose to False // is to click "OK" | Set pbShowClose to False // is to click "OK" | ||
Set pbServerOnSubmit to True // enable the OnSubmit event | Set pbServerOnSubmit to True // enable the OnSubmit event | ||
Object oMainPanel is a cWebPanel | Object oMainPanel is a cWebPanel | ||
Set piColumnCount to 12 | Set piColumnCount to 12 | ||
Object oProcess is a cWebForm | Object oProcess is a cWebForm | ||
Set piColumnSpan to 0 | Set piColumnSpan to 0 | ||
Line 119: | Line 124: | ||
Set piLabelOffset to 210 | Set piLabelOffset to 210 | ||
End_Object | End_Object | ||
Object oCaller is a cWebForm | Object oCaller is a cWebForm | ||
Set piColumnSpan to 0 | Set piColumnSpan to 0 | ||
Line 127: | Line 132: | ||
Set piLabelOffset to 210 | Set piLabelOffset to 210 | ||
End_Object | End_Object | ||
Object oGlobal is a cWebForm | Object oGlobal is a cWebForm | ||
Set piColumnSpan to 0 | Set piColumnSpan to 0 | ||
Line 135: | Line 140: | ||
Set piLabelOffset to 210 | Set piLabelOffset to 210 | ||
End_Object | End_Object | ||
Object oRegProp is a cWebForm | Object oRegProp is a cWebForm | ||
Set piColumnSpan to 0 | Set piColumnSpan to 0 | ||
Line 143: | Line 148: | ||
Set piLabelOffset to 210 | Set piLabelOffset to 210 | ||
End_Object | End_Object | ||
End_Object | End_Object | ||
Object oBottomPanel is a cWebPanel | Object oBottomPanel is a cWebPanel | ||
Set piColumnCount to 4 | Set piColumnCount to 4 | ||
Set peRegion to prBottom | Set peRegion to prBottom | ||
Object oOkButton is a cWebButton | Object oOkButton is a cWebButton | ||
Set psCaption to C_$OK | Set psCaption to C_$OK | ||
Set piColumnSpan to 1 | Set piColumnSpan to 1 | ||
Set piColumnIndex to 3 | Set piColumnIndex to 3 | ||
Procedure OnClick | Procedure OnClick | ||
Send Ok | Send Ok | ||
End_Procedure | End_Procedure | ||
End_Object | End_Object | ||
End_Object | End_Object | ||
Procedure OnSubmit | Procedure OnSubmit | ||
Send Ok | Send Ok | ||
End_Procedure | End_Procedure | ||
Procedure PopupTheDialog Handle hReturnObj Integer iCaller | Procedure PopupTheDialog Handle hReturnObj Integer iCaller | ||
Send Popup hReturnObj | Send Popup hReturnObj | ||
WebSet psValue of oProcess to (GetCurrentProcessId()) | WebSet psValue of oProcess to (GetCurrentProcessId()) | ||
WebSet psValue of oCaller to iCaller | WebSet psValue of oCaller to iCaller | ||
Line 175: | Line 180: | ||
WebSet psValue of oRegProp to (piRandom(Self)) | WebSet psValue of oRegProp to (piRandom(Self)) | ||
End_Procedure | End_Procedure | ||
Function DialogResult Returns String | Function DialogResult Returns String | ||
String sResult | String sResult | ||
WebGet psValue of oProcess to sResult | WebGet psValue of oProcess to sResult | ||
Function_Return sResult | Function_Return sResult | ||
End_Function | End_Function | ||
End_Object | End_Object | ||
//============================================================================== | //============================================================================== | ||
// This is the actual view | // This is the actual view | ||
Line 192: | Line 197: | ||
Set psCaption to "Process Pooling Effects Demo" | Set psCaption to "Process Pooling Effects Demo" | ||
Set pbServerOnShow to True | Set pbServerOnShow to True | ||
Property Integer[] paiWebAppProcs | Property Integer[] paiWebAppProcs | ||
// Display this view at start up: | // Display this view at start up: | ||
Delegate Set phoDefaultView to Self | Delegate Set phoDefaultView to Self | ||
// Work out the web application name from the | // Work out the web application name from the web.config | ||
// file in AppHTML, which we | // file in AppHTML, which we read into an XML object | ||
Function WebAppName Returns String | Function WebAppName Returns String | ||
Handle | Handle hoXml hoElem | ||
String sName | String sName | ||
Boolean bOK | |||
Get Create (RefClass( | |||
Set | Get Create (RefClass(cXMLDOMDocument)) to hoXml | ||
(psAppHtmlPath(phoWorkspace(ghoApplication)) + | Set psDocumentName of hoXml to ; | ||
(psAppHtmlPath(phoWorkspace(ghoApplication)) + "\web.config") | |||
Get | Get LoadXMLDocument of hoXml to bOK | ||
Send Destroy of | Get FindNode of hoXml "configuration/location/system.webServer/dataflexHttpModule" to hoElem | ||
Get AttributeValue of hoElem "application" to sName | |||
Send Destroy of hoElem | |||
Send Destroy of hoXml | |||
Function_Return sName | Function_Return sName | ||
End_Function | End_Function | ||
// Will be set to return value of above function at start-up. | // Will be set to return value of above function at start-up. | ||
// This is a regular property because the web app name can't | // This is a regular property because the web app name can't | ||
Line 232: | Line 239: | ||
// Finally it calls ProcessImageFileName to see if that is one of the | // Finally it calls ProcessImageFileName to see if that is one of the | ||
// processes running for THIS WebApp. However ProcessImageFileName | // processes running for THIS WebApp. However ProcessImageFileName | ||
// returns a file-path | // returns a file-path starting with the disk identifer in a slightly odd | ||
// | // form, i.e. "\DEVICE\HARDDISKVOLUMEn" (n is "1" for my C: drive) and even | ||
// | // spookier things for mapped drives, so we strip off the "C:\", or whatever, | ||
// application file name DataFlex returns and uppercase both | // from the application file name DataFlex returns and uppercase both, then | ||
// | // compare THAT to the same length of the right-portion of the image name. | ||
// This may screw up if you have more than one identically pathed web-apps | |||
// on different drives - just so you know. <g> | |||
// | // | ||
// Note: that although the docs (and the declarations) use DWords, | // Note: that although the docs (and the declarations) use DWords, | ||
// on Vincent's advice we are using UIntegers in our code (the same | // on Vincent's advice we are using UIntegers in our code (the same | ||
// thing in reality) because you can use SizeOfType(UInteger) in the | // thing in reality) because you can use SizeOfType(UInteger) in the | ||
// | // Watches window when debugging, which you can't for DWord. | ||
Procedure FindWebAppProcs | Procedure FindWebAppProcs | ||
Integer[] aiWebAppProcs | Integer[] aiWebAppProcs | ||
UInteger[] auiProcs | UInteger[] auiProcs | ||
UInteger uiCb uiNeeded uiSize uiErr | UInteger uiCb uiNeeded uiSize uiErr | ||
Integer iOK i iLast iPos iSize | Integer iOK i iLast iPos iSize iPathLen | ||
Handle hProc | Handle hProc | ||
UChar[] ucaFile | UChar[] ucaFile | ||
String sPath sImage | String sPath sImage | ||
Move (Uppercase(GetApplicationFileName(ghoApplication))) | Move (Uppercase(GetApplicationFileName(ghoApplication))) to sPath | ||
// Strip off drive designation: | // Strip off drive designation: | ||
Move (Pos("\", sPath)) | Move (Pos("\", sPath)) to iPos | ||
Move (Right(sPath, (Length(sPath) - iPos))) | Move (Right(sPath, (Length(sPath) - iPos))) to sPath | ||
Move 4096 | Move (Length(sPath)) to iPathLen | ||
Move 4096 to iSize | |||
Move (ResizeArray(auiProcs, iSize)) | |||
Move (iSize * SizeOfType(UInteger)) | Move (ResizeArray(auiProcs, iSize)) to auiProcs | ||
Move 0 | Move (iSize * SizeOfType(UInteger)) to uiCb | ||
Move 0 to uiNeeded | |||
Move (EnumProcesses(AddressOf(auiProcs), ; | Move (EnumProcesses(AddressOf(auiProcs), ; | ||
uiCb, ; | uiCb, ; | ||
AddressOf(uiNeeded))) | AddressOf(uiNeeded))) to iOK | ||
// Just for debugging: | // Just for debugging: | ||
If not iOK ; | If not iOK ; | ||
Move (GetLastError()) | Move (GetLastError()) to uiErr | ||
Move (uiNeeded / SizeOfType(UInteger)) | Move (uiNeeded / SizeOfType(UInteger)) to iSize | ||
Move (ResizeArray(auiProcs, iSize)) | Move (ResizeArray(auiProcs, iSize)) to auiProcs | ||
Decrement iSize | Decrement iSize | ||
For i from 0 to iSize | For i from 0 to iSize | ||
Move (OpenProcess(PROCESS_VM_READ + PROCESS_QUERY_INFORMATION, ; | Move (OpenProcess(PROCESS_VM_READ + PROCESS_QUERY_INFORMATION, ; | ||
True, auiProcs[i])) to hProc | True, auiProcs[i])) to hProc | ||
If (hProc <> 0) Begin // We DID get a handle to the process | If (hProc <> 0) Begin // We DID get a handle to the process | ||
Move (ResizeArray(ucaFile, 0)) | Move (ResizeArray(ucaFile, 0)) to ucaFile | ||
Move (ResizeArray(ucaFile, 2048)) | Move (ResizeArray(ucaFile, 2048)) to ucaFile | ||
Move (ProcessImageFileName(hProc, ; | Move (ProcessImageFileName(hProc, ; | ||
AddressOf(ucaFile), 2048)) | AddressOf(ucaFile), 2048)) to uiSize | ||
Move (ResizeArray(ucaFile, uiSize)) | Move (ResizeArray(ucaFile, uiSize)) to ucaFile | ||
Move (Uppercase(UCharArrayToString(ucaFile))) | Move (Uppercase(UCharArrayToString(ucaFile))) to sImage | ||
Move (Right(sImage, iPathLen)) to sImage | |||
Move (Right(sImage, | |||
If (sImage = sPath) ; | If (sImage = sPath) ; | ||
Move auiProcs[i] to aiWebAppProcs[SizeOfArray(aiWebAppProcs)] | Move auiProcs[i] to aiWebAppProcs[SizeOfArray(aiWebAppProcs)] | ||
End | End | ||
Loop | Loop | ||
Set paiWebAppProcs to aiWebAppProcs | |||
End_Procedure | End_Procedure | ||
Procedure OnShow | Procedure OnShow | ||
Send UpdateProcInfo | Send UpdateProcInfo | ||
End_Procedure | End_Procedure | ||
// Registry object for getting the WebApp info from the registry: | // Registry object for getting the WebApp info from the registry: | ||
Object oReg is a cRegistry | Object oReg is a cRegistry | ||
Set phRootKey to HKEY_LOCAL_MACHINE | Set phRootKey to HKEY_LOCAL_MACHINE | ||
Set pfAccessRights to | Set pfAccessRights to (KEY_WOW64_64KEY ior KEY_READ) | ||
Function BaseKey Returns String | Function BaseKey Returns String | ||
Line 315: | Line 323: | ||
Move "WebApp Server" to asParts[4] | Move "WebApp Server" to asParts[4] | ||
Move "Web Applications" to asParts[5] | Move "Web Applications" to asParts[5] | ||
// If it is a 64-bit machine: | // If it is a 64-bit machine and running on a DF version PRIOR to 20 | ||
If (KeyExists(Self, "SOFTWARE\Wow6432Node")) ; | // Insert "Wow6432Node just after "SOFTWARE": | ||
If (KeyExists(Self, "SOFTWARE\Wow6432Node") and ; | |||
(Number(C_DFVersion) < 20)) ; | |||
Move (InsertInArray(asParts, 1, "Wow6432Node")) to asParts | Move (InsertInArray(asParts, 1, "Wow6432Node")) to asParts | ||
Function_Return (StrJoinFromArray(asParts, "\")) | Function_Return (StrJoinFromArray(asParts, "\")) | ||
End_Function | End_Function | ||
Function DWKeyValue String sApp String sVal Returns Integer | Function DWKeyValue String sApp String sVal Returns Integer | ||
Boolean bOK | Boolean bOK | ||
Integer iVal | Integer iVal | ||
String sKey | String sKey | ||
Move (BaseKey(Self) + "\" + sApp) to sKey | |||
Get KeyExists sKey to bOK | Get KeyExists sKey to bOK | ||
If not bOK ; | If not bOK ; | ||
Function_Return 0 | Function_Return 0 | ||
Get OpenKey sKey to bOK | Get OpenKey sKey to bOK | ||
If not bOK ; | If not bOK ; | ||
Function_Return 0 | Function_Return 0 | ||
Move (ReadDWord(Self, sVal)) to iVal | Move (ReadDWord(Self, sVal)) to iVal | ||
Send CloseKey | Send CloseKey | ||
Function_Return iVal | Function_Return iVal | ||
End_Function | End_Function | ||
End_Object | End_Object | ||
// Refresh all the displayed information | // Refresh all the displayed information | ||
Procedure UpdateProcInfo | Procedure UpdateProcInfo | ||
String sApp | String sApp | ||
Integer iMin iMax | Integer iMin iMax | ||
Get psWebAppName to sApp | Get psWebAppName to sApp | ||
WebSet psValue of oMinProc to (DWKeyValue(oReg(Self), sApp, "MinPool")) | WebSet psValue of oMinProc to (DWKeyValue(oReg(Self), sApp, "MinPool")) | ||
WebSet psValue of oMaxProc to (DWKeyValue(oReg(Self), sApp, "MaxPool")) | WebSet psValue of oMaxProc to (DWKeyValue(oReg(Self), sApp, "MaxPool")) | ||
WebSet psValue of oCurrProcs to "Unknown" | WebSet psValue of oCurrProcs to "Unknown" | ||
WebSet psValue of oCurrProcess to (GetCurrentProcessId()) | WebSet psValue of oCurrProcess to (GetCurrentProcessId()) | ||
WebSet psValue of oGlobalVal to giRandom | WebSet psValue of oGlobalVal to giRandom | ||
WebSet psValue of oRegProp to (piRandom(Self)) | WebSet psValue of oRegProp to (piRandom(Self)) | ||
WebSet psValue of oDBRec to FlexErrs.Recnum | WebSet psValue of oDBRec to FlexErrs.Recnum | ||
Send FindWebAppProcs | Send FindWebAppProcs | ||
Send GridRefresh of oProcList | Send GridRefresh of oProcList | ||
Send Focus of oCallServer | Send Focus of oCallServer | ||
End_Procedure | End_Procedure | ||
Object oWebMainPanel is a cWebPanel | Object oWebMainPanel is a cWebPanel | ||
Set piColumnCount to 8 | Set piColumnCount to 8 | ||
Object oExplanation is a cWebLabel | Object oExplanation is a cWebLabel | ||
Set piColumnSpan to 0 | Set piColumnSpan to 0 | ||
Line 384: | Line 393: | ||
' MUST test Web Apps OUTSIDE the debugger.') | ' MUST test Web Apps OUTSIDE the debugger.') | ||
End_Object | End_Object | ||
Object oProcPoolGrp is a cWebGroup | Object oProcPoolGrp is a cWebGroup | ||
Set piColumnCount to 8 | Set piColumnCount to 8 | ||
Set piColumnSpan to 0 | Set piColumnSpan to 0 | ||
Set psCaption to "Process Pool Information:" | Set psCaption to "Process Pool Information:" | ||
Object oPoolInfo is a cWebGroup | Object oPoolInfo is a cWebGroup | ||
Set pbShowBorder to False | Set pbShowBorder to False | ||
Line 395: | Line 404: | ||
Set piColumnCount to 12 | Set piColumnCount to 12 | ||
Set piColumnSpan to 6 | Set piColumnSpan to 6 | ||
Object oAppName is a cWebForm | Object oAppName is a cWebForm | ||
Set piColumnSpan to 0 | Set piColumnSpan to 0 | ||
Line 403: | Line 412: | ||
Set psValue to (psWebAppName(Self)) | Set psValue to (psWebAppName(Self)) | ||
End_Object | End_Object | ||
Object oMinProc is a cWebForm | Object oMinProc is a cWebForm | ||
Set piColumnSpan to 5 | Set piColumnSpan to 5 | ||
Line 410: | Line 419: | ||
Set psLabel to "Minimum Pool:" | Set psLabel to "Minimum Pool:" | ||
End_Object | End_Object | ||
Object oMaxProc is a cWebForm | Object oMaxProc is a cWebForm | ||
Set piColumnSpan to 5 | Set piColumnSpan to 5 | ||
Line 417: | Line 426: | ||
Set psLabel to "Maximum Pool:" | Set psLabel to "Maximum Pool:" | ||
End_Object | End_Object | ||
Object oCurrProcs is a cWebForm | Object oCurrProcs is a cWebForm | ||
Set piColumnSpan to 5 | Set piColumnSpan to 5 | ||
Line 426: | Line 435: | ||
End_Object | End_Object | ||
Object oProcList is a cWebList | Object oProcList is a cWebList | ||
Set piColumnIndex to 6 | Set piColumnIndex to 6 | ||
Line 434: | Line 443: | ||
// Adjust this to see more/less process numbers without scrolling: | // Adjust this to see more/less process numbers without scrolling: | ||
Set piHeight to 200 | Set piHeight to 200 | ||
Object oProcsCol is a cWebColumn | Object oProcsCol is a cWebColumn | ||
Set psCaption to "Pool Process IDs" | Set psCaption to "Pool Process IDs" | ||
Set piWidth to 100 | Set piWidth to 100 | ||
End_Object | End_Object | ||
Procedure OnManualLoadData tWebRow[] ByRef aTheRows String ByRef sCurrentRowID | Procedure OnManualLoadData tWebRow[] ByRef aTheRows String ByRef sCurrentRowID | ||
Integer[] aiWebProcs | Integer[] aiWebProcs | ||
Integer i iLast iThis | Integer i iLast iThis | ||
Get paiWebAppProcs of oProcPoolDemo to aiWebProcs | |||
Move (GetCurrentProcessId()) | Move (GetCurrentProcessId()) to iThis | ||
Move (SizeOfArray(aiWebProcs)) | Move (SizeOfArray(aiWebProcs)) to iLast | ||
WebSet psValue of oCurrProcs | WebSet psValue of oCurrProcs to iLast | ||
Decrement iLast | Decrement iLast | ||
For i from 0 to iLast | For i from 0 to iLast | ||
Move aiWebProcs[i] to aTheRows[i].sRowID | Move aiWebProcs[i] to aTheRows[i].sRowID | ||
Move aiWebProcs[i] to aTheRows[i].aCells[0].sValue | Move aiWebProcs[i] to aTheRows[i].aCells[0].sValue | ||
If (aiWebProcs[i] = iThis) Begin | If (aiWebProcs[i] = iThis) Begin | ||
Move aiWebProcs[i] to sCurrentRowID | Move aiWebProcs[i] to sCurrentRowID | ||
End | End | ||
Loop | Loop | ||
Forward Send OnManualLoadData (&aTheRows) (&sCurrentRowID) | Forward Send OnManualLoadData (&aTheRows) (&sCurrentRowID) | ||
End_Procedure | End_Procedure | ||
End_Object | End_Object | ||
End_Object | End_Object | ||
Object oCurrProcess is a cWebForm | Object oCurrProcess is a cWebForm | ||
Set piColumnIndex to 0 | Set piColumnIndex to 0 | ||
Line 476: | Line 485: | ||
Set piLabelOffset to 250 | Set piLabelOffset to 250 | ||
End_Object | End_Object | ||
Object oGlobalVal is a cWebForm | Object oGlobalVal is a cWebForm | ||
Set piColumnIndex to 0 | Set piColumnIndex to 0 | ||
Line 485: | Line 494: | ||
Set piLabelOffset to 250 | Set piLabelOffset to 250 | ||
End_Object | End_Object | ||
Object oRegProp is a cWebForm | Object oRegProp is a cWebForm | ||
Set piColumnIndex to 0 | Set piColumnIndex to 0 | ||
Line 494: | Line 503: | ||
Set piLabelOffset to 250 | Set piLabelOffset to 250 | ||
End_Object | End_Object | ||
Object oDBRec is a cWebForm | Object oDBRec is a cWebForm | ||
Set piColumnIndex to 0 | Set piColumnIndex to 0 | ||
Line 503: | Line 512: | ||
Set piLabelOffset to 250 | Set piLabelOffset to 250 | ||
End_Object | End_Object | ||
Object oWebValue is a cWebForm | |||
Set piColumnIndex to 0 | |||
Set piColumnSpan to 4 | |||
Set psLabel to "Your entered value:" | |||
Set psValue to "Hello!" | |||
Set peLabelAlign to alignRight | |||
Set piLabelOffset to 250 | |||
End_Object | |||
Object oNote is a cWebLabel | |||
Set psCaption to "(This is a web property - psValue of oWebValue - so will not change unless YOU change it)" | |||
Set piColumnIndex to 4 | |||
Set piColumnSpan to 4 | |||
End_Object | |||
Object oButtonSpacer is a cWebSpacer | Object oButtonSpacer is a cWebSpacer | ||
Set piHeight to 20 | Set piHeight to 20 | ||
End_Object | End_Object | ||
Object oCallServer is a cWebButton | Object oCallServer is a cWebButton | ||
Set piColumnIndex to 0 | Set piColumnIndex to 0 | ||
Set piColumnSpan to 2 | Set piColumnSpan to 2 | ||
Set psCaption to "Call Server" | Set psCaption to "Call Server" | ||
Procedure OnClick | Procedure OnClick | ||
Send UpdateProcInfo | Send UpdateProcInfo | ||
End_Procedure | End_Procedure | ||
End_Object | End_Object | ||
Object oInfo is a cWebButton | Object oInfo is a cWebButton | ||
Set piColumnIndex to 2 | Set piColumnIndex to 2 | ||
Set piColumnSpan to 2 | Set piColumnSpan to 2 | ||
Set psCaption to "Info Box" | Set psCaption to "Info Box" | ||
Procedure OnClick | Procedure OnClick | ||
Integer iProc | Integer iProc | ||
Move (GetCurrentProcessId()) to iProc | Move (GetCurrentProcessId()) to iProc | ||
Send ShowInfoBox ("InfoBox in process" * String(iProc)) | Send ShowInfoBox ("InfoBox in process" * String(iProc)) | ||
Send UpdateProcInfo | Send UpdateProcInfo | ||
End_Procedure | End_Procedure | ||
End_Object | End_Object | ||
Object oYesNo is a cWebButton | Object oYesNo is a cWebButton | ||
Set piColumnIndex to 4 | Set piColumnIndex to 4 | ||
Set piColumnSpan to 2 | Set piColumnSpan to 2 | ||
Set psCaption to "Yes/No" | Set psCaption to "Yes/No" | ||
// Web Property to hold state between browser/server round-trips | // Web Property to hold state between browser/server round-trips | ||
{ WebProperty=Client } | { WebProperty=Client } | ||
Property stAllMyState ptState | Property stAllMyState ptState | ||
// Is called in response to user's second answer: | // Is called in response to user's second answer: | ||
Procedure ProcessSecondAnswer Integer eAnswer | Procedure ProcessSecondAnswer Integer eAnswer | ||
stAllMyState tState | stAllMyState tState | ||
String[] asInfo | String[] asInfo | ||
WebGet ptState to tState | WebGet ptState to tState | ||
Move (GetCurrentProcessId()) to tState.iProc3 | Move (GetCurrentProcessId()) to tState.iProc3 | ||
Move (CurrentDateTime()) to tState.tmAnswered2 | Move (CurrentDateTime()) to tState.tmAnswered2 | ||
Move (If((eAnswer = cmYes), "Yes", "No")) to tState.SecondAnswer | Move (If((eAnswer = cmYes), "Yes", "No")) to tState.SecondAnswer | ||
// Assemble results: | // Assemble results: | ||
Move ("You clicked the 'Yes/No' button at" * String(tState.tmClicked) * ; | Move ("You clicked the 'Yes/No' button at" * String(tState.tmClicked) * ; | ||
Line 565: | Line 589: | ||
"in process" * String(tState.iProc3)) ; | "in process" * String(tState.iProc3)) ; | ||
to asInfo[SizeOfArray(asInfo)] | to asInfo[SizeOfArray(asInfo)] | ||
Send ShowInfoBox (StrJoinFromArray(asInfo, C_CRLF)) "Results" | Send ShowInfoBox (StrJoinFromArray(asInfo, C_CRLF)) "Results" | ||
Send UpdateProcInfo | Send UpdateProcInfo | ||
End_Procedure | End_Procedure | ||
WebPublishProcedure ProcessSecondAnswer // Publish the proc to receive control after second answer | WebPublishProcedure ProcessSecondAnswer // Publish the proc to receive control after second answer | ||
// Is called in response to user's first answer: | // Is called in response to user's first answer: | ||
Procedure ProcessFirstAnswer Integer eAnswer | Procedure ProcessFirstAnswer Integer eAnswer | ||
stAllMyState tState | stAllMyState tState | ||
WebGet ptState to tState | WebGet ptState to tState | ||
Move (GetCurrentProcessId()) to tState.iProc2 | Move (GetCurrentProcessId()) to tState.iProc2 | ||
Move (CurrentDateTime()) to tState.tmAnswered1 | Move (CurrentDateTime()) to tState.tmAnswered1 | ||
Move (If((eAnswer = cmYes), "Yes", "No")) to tState.sFirstAswer | Move (If((eAnswer = cmYes), "Yes", "No")) to tState.sFirstAswer | ||
WebSet ptState to tState | WebSet ptState to tState | ||
Send ShowYesNo Self (RefProc(ProcessSecondAnswer)) ; | Send ShowYesNo Self (RefProc(ProcessSecondAnswer)) ; | ||
("Do you REALLY want to do this? (Proc:" * String(tState.iProc2) + ")") ; | ("Do you REALLY want to do this? (Proc:" * String(tState.iProc2) + ")") ; | ||
Line 588: | Line 612: | ||
End_Procedure | End_Procedure | ||
WebPublishProcedure ProcessFirstAnswer // Publish the proc to receive control after first answer | WebPublishProcedure ProcessFirstAnswer // Publish the proc to receive control after first answer | ||
// Triggers the question cascade: | // Triggers the question cascade: | ||
Procedure OnClick | Procedure OnClick | ||
stAllMyState tState | stAllMyState tState | ||
Move (CurrentDateTime()) to tState.tmClicked | Move (CurrentDateTime()) to tState.tmClicked | ||
Move (GetCurrentProcessId()) to tState.iProc1 | Move (GetCurrentProcessId()) to tState.iProc1 | ||
WebSet ptState to tState | WebSet ptState to tState | ||
Send ShowYesNo Self (RefProc(ProcessFirstAnswer)) ; | Send ShowYesNo Self (RefProc(ProcessFirstAnswer)) ; | ||
("Do you want to do this? (Proc:" * String(tState.iProc1) + ")") ; | ("Do you want to do this? (Proc:" * String(tState.iProc1) + ")") ; | ||
"First question" | "First question" | ||
End_Procedure | End_Procedure | ||
End_Object | End_Object | ||
Object oDialog is a cWebButton | Object oDialog is a cWebButton | ||
Set piColumnIndex to 6 | Set piColumnIndex to 6 | ||
Set piColumnSpan to 2 | Set piColumnSpan to 2 | ||
Set psCaption to "Popup Dialog" | Set psCaption to "Popup Dialog" | ||
Procedure OnCloseModalDialog Handle hoModalDialog | Procedure OnCloseModalDialog Handle hoModalDialog | ||
Integer iProc1 iProc2 | Integer iProc1 iProc2 | ||
If (hoModalDialog = oTestDialog) Begin | If (hoModalDialog = oTestDialog) Begin | ||
Get DialogResult of oTestDialog to iProc1 | Get DialogResult of oTestDialog to iProc1 | ||
Line 618: | Line 642: | ||
("Dialog in process" * String(iProc1) + C_CRLF + ; | ("Dialog in process" * String(iProc1) + C_CRLF + ; | ||
"Returned to process" * String(iProc2)) "Result" | "Returned to process" * String(iProc2)) "Result" | ||
Send UpdateProcInfo | Send UpdateProcInfo | ||
End | End | ||
End_Procedure | End_Procedure | ||
Procedure OnClick | Procedure OnClick | ||
Send PopupTheDialog of oTestDialog Self (GetCurrentProcessId()) | Send PopupTheDialog of oTestDialog Self (GetCurrentProcessId()) | ||
End_Procedure | End_Procedure | ||
End_Object | End_Object | ||
End_Object | End_Object | ||
End_Object | End_Object | ||
</source> | </source> | ||
[[Category: Process Pooling]] | |||
[[Category: Web Applications]] |
Latest revision as of 11:47, 28 September 2020
In an article on the Unicorn InterGlobal web site I describe my Process Pooling Demo web view.
The code for it is as follows and you can download the zip file from this link: File:ProcPoolDemo.zip.
(Note: now modified to work with DataFlex 20.0 as well as DataFlex 19.1... and possibly prior versions as well - no guarantees though. I have only tested with 19.1 and 20.0.)
Use cWebView.pkg Use cWebModalDialog.pkg Use cWebPanel.pkg Use cWebGroup.pkg Use cWebForm.pkg Use cWebButton.pkg Use cWebSpacer.pkg Use cWebList.pkg Use cWebColumn.pkg Use cWebLabel.pkg Use cRegistry.pkg Use Flexml.pkg Define PROCESS_VM_READ for |CI$0010 Define PROCESS_QUERY_INFORMATION for |CI$0400 Define KEY_WOW64_64KEY for |CI$0100 // Wrap all these in #IFNDEF/#ENDIF blocks to avoid any conflicts // with the same things already defined in the application #IFNDEF get_GetLastError External_Function GetLastError "GetLastError" Kernal32.DLL Returns DWord #ENDIF #IFNDEF get_GetCurrentProcessId External_Function GetCurrentProcessId "GetCurrentProcessId" Kernel32.Dll Returns Integer #ENDIF #IFNDEF get_EnumProcesses External_Function EnumProcesses "K32EnumProcesses" Kernel32.DLL ; Pointer lpidProcess ; DWord cb ; Pointer lpcbNeeded ; Returns Integer #ENDIF #IFNDEF get_OpenProcess External_Function OpenProcess "OpenProcess" Kernel32.Dll ; DWord dwDesiredAccess ; Boolean bInheritHandle ; DWord dwProcessId ; Returns Handle #ENDIF #IFNDEF get_ProcessHandle External_Function ProcessHandle "OpenProcess" Kernel32.DLL ; DWord dwDesiredAccess ; Boolean bInheritHandle ; DWord dwProcessID ; Returns Handle #ENDIF #IFNDEF get_ProcessImageFileName External_Function ProcessImageFileName "K32GetProcessImageFileNameA" Kernel32.DLL ; Handle hProcess ; Pointer lpImageFileName ; DWord nSize ; Returns DWord #ENDIF #IFNDEF _struct_stAllMyState // Struct to hold state Struct stAllMyState Time tmClicked String sFirstAswer Time tmAnswered1 String SecondAnswer Time tmAnswered2 Integer iProc1 Integer iProc2 Integer iProc3 End_Struct #ENDIF #IFNDEF C_CRLF // Just for formatting the result of the Yes/No cascade in this case: Define C_CRLF for (Character(13) + Character(10)) #ENDIF // The following items will (almost always) have different values in // different processes in the WebApp process pool, but will remain static // in any given process. Global_Variable Integer giRandom Move (Random(10000)) to giRandom // This will be a property of oWabApp: Property Integer piRandom (Random(10000) + 10000) // Will be opened in the WebApp Open Flexerrs Clear Flexerrs Move (Random(99) + 1) to Flexerrs.Recnum Find EQ FlexErrs by Recnum //============================================================================== // This is a Modal Dialog which will be called from the View, // included directly in-line here for simplicity: //============================================================================== Object oTestDialog is a cWebModalDialog Set psCaption to "Test Dialog" Set piMinWidth to 300 Set piMinHeight to 200 Set pbServerOnEscape to False // The only way out Set pbShowClose to False // is to click "OK" Set pbServerOnSubmit to True // enable the OnSubmit event Object oMainPanel is a cWebPanel Set piColumnCount to 12 Object oProcess is a cWebForm Set piColumnSpan to 0 Set peLabelAlign to alignRight Set pbReadOnly to True Set psLabel to "This Process ID:" Set piLabelOffset to 210 End_Object Object oCaller is a cWebForm Set piColumnSpan to 0 Set peLabelAlign to alignRight Set pbReadOnly to True Set psLabel to "Called from Process ID:" Set piLabelOffset to 210 End_Object Object oGlobal is a cWebForm Set piColumnSpan to 0 Set peLabelAlign to alignRight Set pbReadOnly to True Set psLabel to "Global variable giRandom was:" Set piLabelOffset to 210 End_Object Object oRegProp is a cWebForm Set piColumnSpan to 0 Set peLabelAlign to alignRight Set pbReadOnly to True Set psLabel to "Regular property piRandom was:" Set piLabelOffset to 210 End_Object End_Object Object oBottomPanel is a cWebPanel Set piColumnCount to 4 Set peRegion to prBottom Object oOkButton is a cWebButton Set psCaption to C_$OK Set piColumnSpan to 1 Set piColumnIndex to 3 Procedure OnClick Send Ok End_Procedure End_Object End_Object Procedure OnSubmit Send Ok End_Procedure Procedure PopupTheDialog Handle hReturnObj Integer iCaller Send Popup hReturnObj WebSet psValue of oProcess to (GetCurrentProcessId()) WebSet psValue of oCaller to iCaller WebSet psValue of oGlobal to giRandom WebSet psValue of oRegProp to (piRandom(Self)) End_Procedure Function DialogResult Returns String String sResult WebGet psValue of oProcess to sResult Function_Return sResult End_Function End_Object //============================================================================== // This is the actual view //============================================================================== Object oProcPoolDemo is a cWebView Set piWidth to 700 Set psCaption to "Process Pooling Effects Demo" Set pbServerOnShow to True Property Integer[] paiWebAppProcs // Display this view at start up: Delegate Set phoDefaultView to Self // Work out the web application name from the web.config // file in AppHTML, which we read into an XML object Function WebAppName Returns String Handle hoXml hoElem String sName Boolean bOK Get Create (RefClass(cXMLDOMDocument)) to hoXml Set psDocumentName of hoXml to ; (psAppHtmlPath(phoWorkspace(ghoApplication)) + "\web.config") Get LoadXMLDocument of hoXml to bOK Get FindNode of hoXml "configuration/location/system.webServer/dataflexHttpModule" to hoElem Get AttributeValue of hoElem "application" to sName Send Destroy of hoElem Send Destroy of hoXml Function_Return sName End_Function // Will be set to return value of above function at start-up. // This is a regular property because the web app name can't // change while the program is running. Property String psWebAppName (WebAppName(Self)) // This uses three external functions to find all the processes in the // current WebApp's process pool - it will get updated on every refresh // so if you change the number of processes in the pool while the WebApp is // running you will see it in the view. // // It first uses EnumProcesses to find all the running processes on the // machine. // // It then iterates through that list and tries OpenProcess to get a handle // to it (for some permissions will not allow that, so those are skipped). // // Finally it calls ProcessImageFileName to see if that is one of the // processes running for THIS WebApp. However ProcessImageFileName // returns a file-path starting with the disk identifer in a slightly odd // form, i.e. "\DEVICE\HARDDISKVOLUMEn" (n is "1" for my C: drive) and even // spookier things for mapped drives, so we strip off the "C:\", or whatever, // from the application file name DataFlex returns and uppercase both, then // compare THAT to the same length of the right-portion of the image name. // This may screw up if you have more than one identically pathed web-apps // on different drives - just so you know. <g> // // Note: that although the docs (and the declarations) use DWords, // on Vincent's advice we are using UIntegers in our code (the same // thing in reality) because you can use SizeOfType(UInteger) in the // Watches window when debugging, which you can't for DWord. Procedure FindWebAppProcs Integer[] aiWebAppProcs UInteger[] auiProcs UInteger uiCb uiNeeded uiSize uiErr Integer iOK i iLast iPos iSize iPathLen Handle hProc UChar[] ucaFile String sPath sImage Move (Uppercase(GetApplicationFileName(ghoApplication))) to sPath // Strip off drive designation: Move (Pos("\", sPath)) to iPos Move (Right(sPath, (Length(sPath) - iPos))) to sPath Move (Length(sPath)) to iPathLen Move 4096 to iSize Move (ResizeArray(auiProcs, iSize)) to auiProcs Move (iSize * SizeOfType(UInteger)) to uiCb Move 0 to uiNeeded Move (EnumProcesses(AddressOf(auiProcs), ; uiCb, ; AddressOf(uiNeeded))) to iOK // Just for debugging: If not iOK ; Move (GetLastError()) to uiErr Move (uiNeeded / SizeOfType(UInteger)) to iSize Move (ResizeArray(auiProcs, iSize)) to auiProcs Decrement iSize For i from 0 to iSize Move (OpenProcess(PROCESS_VM_READ + PROCESS_QUERY_INFORMATION, ; True, auiProcs[i])) to hProc If (hProc <> 0) Begin // We DID get a handle to the process Move (ResizeArray(ucaFile, 0)) to ucaFile Move (ResizeArray(ucaFile, 2048)) to ucaFile Move (ProcessImageFileName(hProc, ; AddressOf(ucaFile), 2048)) to uiSize Move (ResizeArray(ucaFile, uiSize)) to ucaFile Move (Uppercase(UCharArrayToString(ucaFile))) to sImage Move (Right(sImage, iPathLen)) to sImage If (sImage = sPath) ; Move auiProcs[i] to aiWebAppProcs[SizeOfArray(aiWebAppProcs)] End Loop Set paiWebAppProcs to aiWebAppProcs End_Procedure Procedure OnShow Send UpdateProcInfo End_Procedure // Registry object for getting the WebApp info from the registry: Object oReg is a cRegistry Set phRootKey to HKEY_LOCAL_MACHINE Set pfAccessRights to (KEY_WOW64_64KEY ior KEY_READ) Function BaseKey Returns String String[] asParts Move "SOFTWARE" to asParts[0] Move "Data Access Worldwide" to asParts[1] Move "DataFlex" to asParts[2] Move C_DFVersion to asParts[3] Move "WebApp Server" to asParts[4] Move "Web Applications" to asParts[5] // If it is a 64-bit machine and running on a DF version PRIOR to 20 // Insert "Wow6432Node just after "SOFTWARE": If (KeyExists(Self, "SOFTWARE\Wow6432Node") and ; (Number(C_DFVersion) < 20)) ; Move (InsertInArray(asParts, 1, "Wow6432Node")) to asParts Function_Return (StrJoinFromArray(asParts, "\")) End_Function Function DWKeyValue String sApp String sVal Returns Integer Boolean bOK Integer iVal String sKey Move (BaseKey(Self) + "\" + sApp) to sKey Get KeyExists sKey to bOK If not bOK ; Function_Return 0 Get OpenKey sKey to bOK If not bOK ; Function_Return 0 Move (ReadDWord(Self, sVal)) to iVal Send CloseKey Function_Return iVal End_Function End_Object // Refresh all the displayed information Procedure UpdateProcInfo String sApp Integer iMin iMax Get psWebAppName to sApp WebSet psValue of oMinProc to (DWKeyValue(oReg(Self), sApp, "MinPool")) WebSet psValue of oMaxProc to (DWKeyValue(oReg(Self), sApp, "MaxPool")) WebSet psValue of oCurrProcs to "Unknown" WebSet psValue of oCurrProcess to (GetCurrentProcessId()) WebSet psValue of oGlobalVal to giRandom WebSet psValue of oRegProp to (piRandom(Self)) WebSet psValue of oDBRec to FlexErrs.Recnum Send FindWebAppProcs Send GridRefresh of oProcList Send Focus of oCallServer End_Procedure Object oWebMainPanel is a cWebPanel Set piColumnCount to 8 Object oExplanation is a cWebLabel Set piColumnSpan to 0 Set psCaption to ; ('This view demonstrates the fact that in a Process' + ; ' Pooled WebApp (which all modern WebApps generally' + ; ' are) you CANNOT rely on the values in global variables,' + ; ' regular properties and database buffers. They change' + ; ' from one server round-trip to the next. ONLY web' + ; ' properties and Data Dictionary values can be relied on.' + ; ' If you run this under the debugger you will see that' + ; ' only a single process is "in the pool" and the values' + ; ' of the things below do not change, which is why you' + ; ' MUST test Web Apps OUTSIDE the debugger.') End_Object Object oProcPoolGrp is a cWebGroup Set piColumnCount to 8 Set piColumnSpan to 0 Set psCaption to "Process Pool Information:" Object oPoolInfo is a cWebGroup Set pbShowBorder to False Set pbShowCaption to False Set piColumnCount to 12 Set piColumnSpan to 6 Object oAppName is a cWebForm Set piColumnSpan to 0 Set pbReadOnly to True Set peLabelAlign to alignRight Set psLabel to "Web Application:" Set psValue to (psWebAppName(Self)) End_Object Object oMinProc is a cWebForm Set piColumnSpan to 5 Set pbReadOnly to True Set peLabelAlign to alignRight Set psLabel to "Minimum Pool:" End_Object Object oMaxProc is a cWebForm Set piColumnSpan to 5 Set pbReadOnly to True Set peLabelAlign to alignRight Set psLabel to "Maximum Pool:" End_Object Object oCurrProcs is a cWebForm Set piColumnSpan to 5 Set pbReadOnly to True Set peLabelAlign to alignRight Set psLabel to "Current Pool:" End_Object End_Object Object oProcList is a cWebList Set piColumnIndex to 6 Set piColumnSpan to 2 Set pbDataAware to False Set pbOfflineEditing to True // DON'T call the server on RowChange // Adjust this to see more/less process numbers without scrolling: Set piHeight to 200 Object oProcsCol is a cWebColumn Set psCaption to "Pool Process IDs" Set piWidth to 100 End_Object Procedure OnManualLoadData tWebRow[] ByRef aTheRows String ByRef sCurrentRowID Integer[] aiWebProcs Integer i iLast iThis Get paiWebAppProcs of oProcPoolDemo to aiWebProcs Move (GetCurrentProcessId()) to iThis Move (SizeOfArray(aiWebProcs)) to iLast WebSet psValue of oCurrProcs to iLast Decrement iLast For i from 0 to iLast Move aiWebProcs[i] to aTheRows[i].sRowID Move aiWebProcs[i] to aTheRows[i].aCells[0].sValue If (aiWebProcs[i] = iThis) Begin Move aiWebProcs[i] to sCurrentRowID End Loop Forward Send OnManualLoadData (&aTheRows) (&sCurrentRowID) End_Procedure End_Object End_Object Object oCurrProcess is a cWebForm Set piColumnIndex to 0 Set piColumnSpan to 4 Set pbReadOnly to True Set psLabel to "Last invoked server process was:" Set peLabelAlign to alignRight Set piLabelOffset to 250 End_Object Object oGlobalVal is a cWebForm Set piColumnIndex to 0 Set piColumnSpan to 4 Set pbReadOnly to True Set psLabel to "Global variable giRandom in that was:" Set peLabelAlign to alignRight Set piLabelOffset to 250 End_Object Object oRegProp is a cWebForm Set piColumnIndex to 0 Set piColumnSpan to 4 Set pbReadOnly to True Set psLabel to "Regular property piRandom in that was:" Set peLabelAlign to alignRight Set piLabelOffset to 250 End_Object Object oDBRec is a cWebForm Set piColumnIndex to 0 Set piColumnSpan to 4 Set pbReadOnly to True Set psLabel to "Flexerrs recnum in that was:" Set peLabelAlign to alignRight Set piLabelOffset to 250 End_Object Object oWebValue is a cWebForm Set piColumnIndex to 0 Set piColumnSpan to 4 Set psLabel to "Your entered value:" Set psValue to "Hello!" Set peLabelAlign to alignRight Set piLabelOffset to 250 End_Object Object oNote is a cWebLabel Set psCaption to "(This is a web property - psValue of oWebValue - so will not change unless YOU change it)" Set piColumnIndex to 4 Set piColumnSpan to 4 End_Object Object oButtonSpacer is a cWebSpacer Set piHeight to 20 End_Object Object oCallServer is a cWebButton Set piColumnIndex to 0 Set piColumnSpan to 2 Set psCaption to "Call Server" Procedure OnClick Send UpdateProcInfo End_Procedure End_Object Object oInfo is a cWebButton Set piColumnIndex to 2 Set piColumnSpan to 2 Set psCaption to "Info Box" Procedure OnClick Integer iProc Move (GetCurrentProcessId()) to iProc Send ShowInfoBox ("InfoBox in process" * String(iProc)) Send UpdateProcInfo End_Procedure End_Object Object oYesNo is a cWebButton Set piColumnIndex to 4 Set piColumnSpan to 2 Set psCaption to "Yes/No" // Web Property to hold state between browser/server round-trips { WebProperty=Client } Property stAllMyState ptState // Is called in response to user's second answer: Procedure ProcessSecondAnswer Integer eAnswer stAllMyState tState String[] asInfo WebGet ptState to tState Move (GetCurrentProcessId()) to tState.iProc3 Move (CurrentDateTime()) to tState.tmAnswered2 Move (If((eAnswer = cmYes), "Yes", "No")) to tState.SecondAnswer // Assemble results: Move ("You clicked the 'Yes/No' button at" * String(tState.tmClicked) * ; "in process" * String(tState.iProc1)) ; to asInfo[SizeOfArray(asInfo)] Move ("Your first answer was: '" + ; tState.sFirstAswer + "' at" * String(tState.tmAnswered1) * ; "in process" * String(tState.iProc2)) ; to asInfo[SizeOfArray(asInfo)] Move ("Your second answer was: '" + ; tState.SecondAnswer + "' at" * String(tState.tmAnswered2) * ; "in process" * String(tState.iProc3)) ; to asInfo[SizeOfArray(asInfo)] Send ShowInfoBox (StrJoinFromArray(asInfo, C_CRLF)) "Results" Send UpdateProcInfo End_Procedure WebPublishProcedure ProcessSecondAnswer // Publish the proc to receive control after second answer // Is called in response to user's first answer: Procedure ProcessFirstAnswer Integer eAnswer stAllMyState tState WebGet ptState to tState Move (GetCurrentProcessId()) to tState.iProc2 Move (CurrentDateTime()) to tState.tmAnswered1 Move (If((eAnswer = cmYes), "Yes", "No")) to tState.sFirstAswer WebSet ptState to tState Send ShowYesNo Self (RefProc(ProcessSecondAnswer)) ; ("Do you REALLY want to do this? (Proc:" * String(tState.iProc2) + ")") ; "Second question" End_Procedure WebPublishProcedure ProcessFirstAnswer // Publish the proc to receive control after first answer // Triggers the question cascade: Procedure OnClick stAllMyState tState Move (CurrentDateTime()) to tState.tmClicked Move (GetCurrentProcessId()) to tState.iProc1 WebSet ptState to tState Send ShowYesNo Self (RefProc(ProcessFirstAnswer)) ; ("Do you want to do this? (Proc:" * String(tState.iProc1) + ")") ; "First question" End_Procedure End_Object Object oDialog is a cWebButton Set piColumnIndex to 6 Set piColumnSpan to 2 Set psCaption to "Popup Dialog" Procedure OnCloseModalDialog Handle hoModalDialog Integer iProc1 iProc2 If (hoModalDialog = oTestDialog) Begin Get DialogResult of oTestDialog to iProc1 Move (GetCurrentProcessId()) to iProc2 Send ShowInfoBox ; ("Dialog in process" * String(iProc1) + C_CRLF + ; "Returned to process" * String(iProc2)) "Result" Send UpdateProcInfo End End_Procedure Procedure OnClick Send PopupTheDialog of oTestDialog Self (GetCurrentProcessId()) End_Procedure End_Object End_Object End_Object