Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Is there a way to make public variables private to a session? 3

Status
Not open for further replies.

rlawrence

Programmer
Sep 14, 2000
182
0
0
US
I've just learned the hard way that public variables are REALLY PUBLIC!

I've developed an OLE Server which is the heart of my web application. In the server, I place system-wide and user-preference properties in a couple of public variables so they are available across calls to components within the server. I plan to use the same server, however, for multiple web sites. One of the properties is the location of the database. Guess what happens when two users try to access two different web sites on the same server.

Before I go tearing through my code to wrench out these public variables, I thought I would post this question here to see if anyone else has worked out a clever way to scope variables across components within a session, but not across sessions.

Thanks for your thoughts,

Ron Lawrence
 
The usual way is a "mothership" object that contains references to all participating components. Shared values would be properties of the mothership object.
 
Hi Dan,

Thanks for your thoughts; but, I have been thinking about this long and hard. Hanging the session object off from a "mothership" object means that I need to change the reference to it in every component throughout the application. That in itself would be so bad, except that these components are used in multiple applications--most of which are not bothered by the use of a public variable. One of my main objectives for developing to this architecture was the ability to use the same code in both the desktop and the web environment.

So, I'm going to ask again... Aren't there any alternatives people can think of to effectively scope a variable to a thread?

One alternative I would like to see some discussion on would be to build a single-threaded DLL rather than an MTDLL.

Another alternative--though I'm not convinced that the syntax will work--might be to use a compiler directive to refer to the session object. (i.e. #DEFINE mySession oSession) Then the components could refer to the session object in a consistant way (syntax unknown). I would just have to change the definition based on the application being built.

None of these alternatives seem great. Hasn't anyone else faced this issue and come up with a better solution?

Thanks,

Ron
 
Hi

I do not understand the complications you might have with your code.. and my suggestion could be stupid in that specific context.... but to clear the understanding.... test the following code..

Method1:
********
Code:
PUBLIC p1, p2, p3

p1 = [p1]
p2 = [p2]
p3 = [p3]

= MESSAGEBOX([called from main ...  ] + ;
     [p1=]+p1+[   p2=]+p2+[    p3=]+p3)

=proc1()
=MESSAGEBOX([called from main after proc1 call ...  ] + ;
     [p1=]+p1+[   p2=]+p2+[    p3=]+p3)

RETURN


PROCEDURE proc1

=MESSAGEBOX([called from proc1 before localizing...  ]+ ;
   [p1=]+p1+[   p2=]+p2+[    p3=]+p3)

private p1,p2,p3
p1 = [local1]
p2 = [local2]
p3 = [local3]

=MESSAGEBOX([called from proc1 after localizing ...  ]+ ;
       [p1=]+p1+[   p2=]+p2+[    p3=]+p3)

p1 = p1+[:]+p2+[:]+p3

=MESSAGEBOX([called from proc1 after locale variable ] + ;
   [manipulation ...  ]+ [p1=]+p1+[   p2=]+p2+[    p3=]+p3)

RETURN

Simply specifing ....

LOCAL myTempVar

myTempVar = Value of myPublicVarSymbol

PRIVATE myPublicVarSymbol

myPublicVarSYmbol = myTempVar ... to push the current value into it..

This changes the variables scope.


Method2:

At the beginning..
a. create a Custom Object to hold the current public variables values..
b. Push the current variables properties into it. (probably push the values while creating custom properties to save separate step.
c. Then call the said application .. in the application Localize or Privatize the var names...

d. Then at the end.. if you need to take back any of the private values into the public vales.. first update the custom object before clearing the applicatiuon
e. Then after returning back to original appllication, take back the values needed from custom object and release it..

While it may sound complicated.. the whole thing can be coded generic to handle all the public variables also in few lines of code... such as display memory to a file... read the file using alines and parsing to avoid system vars which start with underscore into a custom object............. then in the specific application called.. read the custom object and create a local var for every member of the custom object (thus it localizez the vars).. etc.. as needed.

cheers :)

____________________________________________
ramani - (Subramanian.G) :)
 
Hi Ramani,

Thanks for your thoughts. The concept you've presented is interesting, but I don't think it will work because of the boundaries around COM components. This is a web application with components that are instanced directly from ASP. There is no main procedure. If there was, then a PRIVATE variable would work.

While any component can declare PUBLIC variables (that's what I do now), PRIVATE variables are scoped only to the current procedure and it's decendents. So, for example, if I set up the session object as PRIVATE in, say, the INIT() method of the parent component, it's scope would end at the end of the INIT() method.

Actually, your idea has similarities to another alternative that I have been warming up to: The Session object is a custom object that keeps a list of properties that are specific to each application. Rather than referring to these properties directly (i.e. oSession.cDataPath) I could set up another obect called a session manager with a method to search for the property using the application Thread. For example:

Code:
cDataPath = oSessionMgr.GetProperty("cDataPath", ;
                                   application.ThreadID)

The Session Manager would have to maintain a list of session objects--one for each active thread. The trick might be in releasing the session object for the thread. This would probably be done in the global.asa On_Session_End() method.

Again, this feels more complicated that should be necessary. Still, having this discussion is helpful in clarifying a direction.

Thanks again,

Ron
 
Hi Ron,

Are you having your COM class registerd as a COM+ application in component services?

Someone else reported a problem, that Public variables defined in one instance of that class can be seen then, in a different/later instance. That way users may see sessiondata of other users.

The solution was to use properties of the class on which the COM class was built, in this case the session class.

Properties will be private to each instance, so instead of gPublic you then would use This.gPublic. The only thing that needs to be done is define the properties you want scoped to the class, then #define gPublic This.gPublic and can reuse the code without modifications.

Bye, Olaf.
 
RLawrence,

I have heard of this server/public variable behavior in select environments and would like as well to hear if Olaf's suggestion works for you.

OlafDoschke,

The solution you propose sounds perfectly reasonable. Could you post a couple sample lines of code to illustrate it? I am not clear as to how to implement it (where would one define the # directives at INIT ?)

Thanks to both!

Kenneth

 
tamayok,

if a define is done in a method, it's scoped to that method in a visual class. When coding the com server as a prg it's sufficient to have the #define somewhere after the property is defined but before all mehtod code. In a visual class simply have it in the include file you can assign to the whole class via menu Class->Include file.

a sample server would be as simple as:
Code:
define class mysession as session olepublic
gPublicSessionscoped = 0
#define gPublicSessionscoped This.gPublicSessionscoped
procedure init()
   gPublicSessionscoped = _vfp.threadid
endproc
enddefine

compile as mtdll, then test:

Code:
oSess1 = createobject("mymtdll.mysession")
oSess2 = createobject("mymtdll.mysession")
? oSess1.gPublicSessionscoped
? oSess2.gPublicSessionscoped
oSess1.gPublicSessionscoped = 1
oSess2.gPublicSessionscoped = 2
? oSess1.gPublicSessionscoped
? oSess2.gPublicSessionscoped

Bye, Olaf.


 
Hi again,

just one small problem: #DEFINE gPublicSessionscoped THIS.gPublicSessionscoped is causing a recursive substitution and compile fails. So you need to define the property name different, so that it's not causing recursion, eg remove the leading g on the propertyname or introduce a m for member as first letter.

_vfp.threadid will be the same for both Sessionobjects, still setting the property to 1 and 2 respectively proves the second one is not interfering with the first one.

Bye, Olaf.
 
OlafDoschke,

I appreciate very much your response to clarify some of the details you have exposed here. I believe these findings are FAQ material and I will certainly keep these posts in mind for future reference.

Thanks!!
 
Olaf!

It looks like you've done it again! You can see that this was one alternative I mentioned above but I wasn't sure that it would work. It is reassuring to have it confirmed by someone who has provided me with good advise in the past.

My only variation, since the reference I'm having trouble with is in several different classes (and .PRG's) I used the parent class name in the #DEFINE, rather than using "This."

i.e. #DEFINE oSession = webParentClass.oMySession

I placed this #DEFINE statement in an include file that is used in each of the .PRG's.

I have run some preliminary tests and this method seems to be working just fine.

Thank you for your time and expertise,

Ron
 
Well, I'm afraid that my euhporia may have been a little premature. I am still having real trouble getting the compiler directive to work. The truth is that I'm a bit confused as to why I'm having trouble, so I'm writing my thoughts out here--as much to try and straighten out my own thinking as to share the issue with others.

As I mentioned above, I reference the session object in several components throughout the object model or hierarchy.

I'm currently getting the following error when I try to instantiate the parent object from ASP.

myServer.WebManager myServer.WebManager (0x800A0783)
logerror c:\program files\myServer\framework\genproc.prg Error in line 900 Object WEBMGR is not found. 1923
/myWebFolder/global.asa, line 12

Note that I don't get this error when I simply instance the object from within the VFP command window. I believe that's because the instance is immediately assigned to a public variable. But I get the same error when I execute the following code from WITHIN a .PRG in the VFP command window.

Code:
#DEFINE oSession webMgr.webSession

PRIVATE webMgr
webMgr = CREATEOBJECT("myServer.webManager")
?webMgr.InitSession("c:\inetpub\[URL unfurl="true"]wwwroot\myWebFolder")[/URL]
...

In both cases, it'f failing on the call to InitSession()


One significant issue I faced was making sure that my webMGR object was completely available before making references to the session object. That was work worth doing, but it means that all of the components that need a reference to the session object could NOT be created until the parent object is instanced. That's why I have a separate InitSession() method.

I've been staring at this so long that I'm sure I'm missing something obvious. Olaf? Care to take another stab at this?

Thanks again,

Ron
 
Hi Ron,

I don't see where that #Define comes to action. It's substituting each oSession with WebMgr.webSession at compile time, but only within that PRG. Where do you do something with oSession (which due to the Define will be done with WebMgr.webSession instead)?

I thought you had problems with a public var you created and used *within* the class code of your myServer.webManager COM server, because that is visible throughout thread barriers within the process holding multiple threads of "myServer.webManager".

A Define outside of that class will not work inside, it's having it's effect at compile time only. My idea was, that you exchanged that public var through a property of the olepublic class. And to be able to use the code as is substitute the public var usage by the usage of that new property. That's all within the COM server.

I think your trouble is, the WebMgr Object isn't created at all, perhaps because of some missing priviliges. Or, as you changed your class and added the InitSession() method perhaps, you need to stop IIS and restart, to have the changed COM class taking effect.

Bye, Olaf.
 
Hi Olaf,

Thanks again for your thoughts. I think I have a pretty good understanding of both your suggestion and the implications of using the #DEFINE directive. I have probably over-simplified my code above. What you don't see is that EACH of the components involved are in their own .PRG's. Each of those .PRG's #INCLUDE's a set of compiler directives that includes the #DEFINE oSession directive.

The offending action that uses "oSession" is actually buried in a generic routine that logs error messages to an error log. It's located in a procedure file, Genproc.PRG. This routine refers to the session object to know where the error log should be located.

After writing the above message, I played with the code a bit more. While the above procedure causes the "Object WEBMGR not found" error message, the following code does NOT!

Code:
#Include dnamodules.h
PRIVATE webMgr
DO pasetup  && This routine does SET PROCEDURE... ADDITIVE
            && for the modules of the application.
webMgr = CREATEOBJECT("webManager")
?webMgr.InitSession("c:\inetpub\[URL unfurl="true"]wwwroot\myWebFolder")[/URL]

So, there's probably no problem with the concept, but something is causing the application to behave differently from within the DLL vs. outside the DLL.

Ron
 
Hi Ron,

in your second example you create the class natively: CreateObject("webManager"), in the first example you create it as an OLE object. I think your problem is, the native "webManager" class is different from the "myServer.webManager" OLE class. You didn't reregister the new server.

To verify, please simply add a new property to the webManager class called Version and store a class version number there. Then check if you get the same version when instaciating "webManager" natively vs. "myServer.webManager" via OLE.

Bye, Olaf.
 
Nice idea, Olaf. I tried it but the newly built MTDLL returns the version just fine before it crashes in InitSession().

I also had a thought that COM+ might be getting in the way. The interface has changed slightly and I hadn't updated the COM+ component. But refreshing the COM+ component didn't change the behavior either.

Ironically, it's my error logging that is failing; but before it does, it tells me that it fails at the point where the session object attempts to make use of another class to bring data into the session. I already take pains to make sure that the session object is instantiated prior to using this other class. But even if that was the problem, why would it work "natively" but not through the DLL?

Ron
 
Uh Oh! There may actually be a problem with this concept.

I have a COM model that looks something like this:


webMgr
|
+- webSession
|
+- webCom1
|
+- webCom2
.
.
.

I am trying to use the compiler directive "oSession" to refer to the webMgr.webSession object from within the other components. I'm not sure that the reference is valid. In other words, I don't think that webCom2 can refer to webMgr. I think the reference will work within a single procedure (like my initial testing); but I'm not sure that a direct reference to a parent from within a child component does. [sad]

I think it works interactively, because the reference to the parent winds up being a variable that is scoped to the application--even if it's private. But no such variable exists to the child components in the DLL.

Can anyone help me verify and/or better explain this?

Ron
 
O.K. This comes under the heading of "For what it's worth..." As a thank you for the help I have received and to hopefully help others with a similar situation, here is some sample code that I have developed to remedy my situation.

I have implemented a "Session Manager". The Session Manager is loaded to a public variable, so it is available across threads. However, instead of referring directly to my session object, the session manager handles finding the appropriate session for the thread and returns requested properties.

I would appreciate hearing if any find this code useful, or if significant improvements can be made. Thanks again to many who support this forum.

Ron Lawrence

Here's the code:

Code:
#DEFINE eol CHR(13) + CHR(10)


***************************************************************************************
*  	The following class is the "MotherShip" class that might be used in a web application.
*  	All of my application specific code has been removed.  What remains is the 
*  	creation of the session manager, and the addition and destruction of a session node
* 	for the current thread.
*
*	PLEASE NOTE THAT THIS MODULE HAS NOT BEEN TESTED SINCE REMOVING APPLICATION SPECIFIC 
*	PROPERTIES AND CODE.
***************************************************************************************

DEFINE CLASS MyMotherShip AS CUSTOM OLEPUBLIC

	cVersion = 2

	PROCEDURE Destroy
		oSessionMgr.DeleteSession()
	ENDPROC


	PROCEDURE InitSession
	PARAMETERS cPath
	
		* If the session object is not available, create it now.
		IF !IsObject("oSessionMgr")
			PUBLIC oSessionMgr
			oSessionMgr = CREATEOBJECT("SessionMgr")
		ENDIF
		oSessionMgr.AddSession(cPath)

		* Now that we have a session object, initialize the remaining web components...		
	ENDPROC


	FUNCTION ShowSession
		RETURN oSessionMgr.ShowSession()
	ENDFUNC

ENDDEFINE


***************************************************************************************
*	The SessionMgr class is used to manage session objects for each thread in a multi-
*	threaded OLE Server.  A list of active sessions are maintained as a linked list.  
*
*	This class was implemented because the session object needs to be available throughout
*	the components of the n-tier architecture.  So the session object was originally 
*	instantiated to a PUBLIC variable.  However, in a multi-threaded server, this public
*	variable is seen across thread boundaries.  One thread would overwrite the session of
*	another.  The Session Manager will be referenced through a global variable, but will
* 	accept and return properties of the session belonging to the active thread.
***************************************************************************************

DEFINE CLASS SessionMgr AS Custom OLEPUBLIC

	oStart = .NULL.
	oEnd = .NULL.

	ADD OBJECT rmC AS rmContact NOINIT

	PROCEDURE AddSession
	PARAMETERS cPath
	LOCAL oSessionPtr
	
		* Create a new session object.
		oSessionPtr = CREATEOBJECT("SessionPtr")
		oSessionPtr.oSession = CREATEOBJECT("MySession", cPath)
		oSessionPtr.nThreadID = Application.ThreadID

		* Add the session to the list.
		IF ISNULL(This.oEnd)
			This.oStart = oSessionPtr
			This.oEnd = oSessionPtr
		ELSE
			oSessionPtr.oNext = This.oEnd
			This.oEnd = oSessionPtr
		ENDIF

	ENDPROC


	PROCEDURE DeleteSession
	LOCAL oSessionPtr
	
		* Remove a session from the list.
		oSessionPtr = This.FindSession()
		IF ISNULL(oSessionPtr)
			RETURN
		ENDIF
		IF ISNULL(oSessionPtr.oPrev)
			This.oStart = oSessionPtr.oNext
		ELSE 
			oSessionPtr.oPrev.oNext = oSessionPtr.oNext
		ENDIF
		IF ISNULL(oSessionPtr.oNext)
			This.oEnd = oSessionPtr.oPrev
		ELSE
			oSessionPtr.oNext.oPrev = oSessionPtr.oPrev
		ENDIF

		* Release the session object.
		oSessionPtr = .NULL.
	
	ENDPROC


	PROCEDURE Destroy
	LOCAL oSessionPtr
	
	*	Release all sessions on the list.
	
		IF ISNULL(This.oStart)
			RETURN
		ENDIF
	
		DO WHILE !ISNULL(This.oStart.oNext)
			oSessionPtr = This.oStart
			oSessionPtr.oSession = .NULL.
			This.oStart = oSessionPtr.oNext
			oSessionPtr = .NULL.
		ENDDO
	ENDPROC


	FUNCTION FindSession
	LOCAL oSessionPtr
	
		IF ISNULL(This.oStart)
			RETURN .NULL.
		ENDIF

		oSessionPtr = This.oStart
		DO WHILE !ISNULL(oSessionPtr.oNext)
			IF oSessionPtr.nThreadID = Application.ThreadId
				EXIT
			ENDIF
			oSessionPtr = oSessionPtr.oNext
		ENDDO
		
		RETURN oSessionPtr
	ENDFUNC


	PROCEDURE GetSessionProperty
	PARAMETERS cPropertyName
	LOCAL oSessionPtr, cPropertyRef, varPropertyValue
	
		oSessionPtr = This.FindSession()
		IF ISNULL(oSessionPtr)
			RETURN .F.
		ENDIF

		cPropertyRef = "oSessionPtr.oSession." + cPropertyName
		varPropertyValue = EVALUATE(cPropertyRef)
		RETURN varPropertyValue
	ENDPROC


	PROCEDURE Init
	PARAMETERS cPath
		IF !EMPTY(cPath)
			This.AddSession(cPath)
		ENDIF
	ENDPROC


	PROCEDURE SetSessionProperty
	PARAMETERS cPropertyName, varPropertyValue
	LOCAL oSessionPtr, cPropertyRef
	
		oSessionPtr = This.FindSession()
		IF ISNULL(oSessionPtr)
			RETURN
		ENDIF

		cPropertyRef = "oSessionPtr.oSession." + cPropertyName
		STORE varPropertyValue TO (cPropertyRef)
	ENDPROC


	FUNCTION ShowSession
	LOCAL oSessionPtr
	
		oSessionPtr = This.FindSession()
		IF ISNULL(oSessionPtr)
			RETURN ""
		ENDIF
		
		RETURN oSessionPtr.oSession.ShowProperties()
	ENDFUNC

ENDDEFINE


DEFINE CLASS SessionPtr AS Custom 

*	This class is a node in the linked list managed by the Session Manager.

	nThreadID = 0
	oSession = .NULL.
	oNext = .NULL.
	oPrev = .NULL.

ENDDEFINE

***************************************************************************************************
*	The mySession class sets up a session object for the application.  The purpose is to have a 
*	single place where all application options and environment settings can be made available to
*	all components of the architecture.  Since the session properties are loaded from an INI file,
*	this scenario also avoids having to reload the INI file multiple times throughout the application.
***************************************************************************************************

***************************************************************************************************
*	This class can be replaced completely with something that is more appropriate for your OLE server
*	application.  I've already ripped out most things that are specific to my application.  But I've
*	left this code here as an example of what the session object might look like.  
*
*	PLEASE NOTE THAT THIS MODULE HAS NOT BEEN TESTED SINCE REMOVING APPLICATION SPECIFIC PROPERTIES AND CODE.
***************************************************************************************************

DEFINE CLASS mySession AS CUSTOM OLEPUBLIC
	cDataPath = "Data"
	cErrorLog = JUSTSTEM(Application.ServerName) + [Error.LOG]		&& Where to write out error messages.
	cINIFile = JUSTSTEM(Application.ServerName) + [.INI]			&& The default INI file to use.
	cPath = SET("PATH")												&& The path setting to be used by the server.
	cSavePath = SET("PATH")											&& The current path to be restored when exiting.
	cSaveStartDir = Application.DefaultFilePath						&& The default directory for the server.
	cServerName = Application.ServerName							&& The server application's name.
	cStartPath = Application.DefaultFilePath						&& The target directory where the server should run.
	cTmpPath = "Temp"												&& Directory where temporary files should be written.
	lDebug = .F.													&& Whether or not debugging messages should be logged.

	PROCEDURE Destroy
	
		SET DEFAULT TO (This.cSaveStartDir)
		SET PATH TO (This.cSavePath)
	
	ENDPROC


	PROCEDURE Error
	LPARAMETERS nError, cMethod, nLine, cMessage
	LOCAL cErrorLog
	
		cMethod = This.Name + [.] + cMethod
		This.LogError(nError, cMethod, nLine, cMessage)

	ENDPROC


	FUNCTION FindINI
	LOCAL cMessage
	
		* Attempt to locate an INI file based on the server name.
		This.cINIFile = JUSTSTEM(This.cServerName) + [.INI]

		* Use myApplication.INI as a fallback.
		IF !FILE(This.cINIFile)
			This.cINIFile = [myApplication.INI]
		ENDIF

		* If no INI is found, then the client application will have to 
		* depend on the default properties.
		IF !FILE(This.cINIFile)
			cMessage = [No Initialization file was found.]
			This.LogError(-1, PROGRAM(), LINENO(), cMessage)
			This.SetDefaults()
			RETURN .F.
		ENDIF
		
		* Save the INI File path.
		This.cINIFile = FULLPATH(This.cINIFile)
		RETURN .T.

	ENDFUNC


	PROCEDURE Init
	PARAMETERS cStartPath AS String, lDebug AS Boolean
	LOCAL cPath, cServerName, cServerPath, orm, ors, oreg 
	
		* Capture the thread ID for multi-thread processing.
		This.nThreadID = Application.ThreadID
	
		IF !EMPTY(lDebug)
			This.lDebug = lDebug
			cMessage = [Debugging messages turned on from parameters.]
			This.LogError(0, PROGRAM(), LINENO(), cMessage)
		ENDIF

		* If a starting path was submitted, set the default directory to it.
		IF !EMPTY(cStartPath)
			SET DEFAULT TO (cStartPath)
		ENDIF
		This.cStartPath = SYS(5) + SYS(2003)

		* Add the location of the server to the path.
		cServerPath = JUSTPATH(This.cServerName)
		IF NOT UPPER(cServerPath) $ This.cPath
			IF EMPTY(This.cPath)
				This.cPath = cServerPath
			ELSE
				This.cPath = This.cPath + [;] + cServerPath
			ENDIF
		ENDIF
		cPath = This.cPath
		SET PATH TO &cPath.

		* Process through the INI file.
		This.ProcessINI()
	
	ENDPROC


	PROCEDURE LogError
	PARAMETERS nError, cMethod, nLine, cMessage
	LOCAL cErrorLog, cServerName, cLogString, cSessionProperty

	*	Logs errors to the error log.

		* Log only if a real error unless the debug flag is on.
		IF nError = 0 AND !This.lDebug
			RETURN
		ENDIF

		IF EMPTY(cMessage)
			cMessage = MESSAGE()
		ENDIF

		* Get the error log file name.  The session object may not be initialized yet.
		* Write the error log to the temp path.
		cErrorLog = ADDBS(This.cTmpPath) + ALLTRIM(This.cErrorLog)

		cLogString = eol
		cLogString = cLogString + [Time: ] + TTOC(DATETIME())
		cLogString = cLogString + [ Error: ] + PADL(ALLTRIM(STR(nError)), 5)
		cLogString = cLogString + [ Procedure: ] + PADR(cMethod, 50)
		cLogString = cLogString + [ Line: ] + STR(nLine, 4)
		cLogString = cLogString + [ Message: ] + cMessage
		STRTOFILE( cLogString, cErrorLog, 1)

	ENDPROC


	HIDDEN PROCEDURE ProcessINI
	LOCAL cMessage, nFileHandle

	* Reads the contents of the INI file to set properties.

		* Locate the INI file.
		IF !This.FindINI()
			RETURN
		ENDIF

		cMessage = [Initialization file used:  ] + This.cINIFile 
		This.LogError(0, PROGRAM(), LINENO(), cMessage)

		* Look for initialization properties in the INI file...
		STORE FOPEN(This.cINIFile) TO nFileHandle    && Open the file
		IF nFileHandle > 0
			STORE FSEEK(nFileHandle, 0, 2) TO nEnd    && Move pointer to EOF
			STORE FSEEK(nFileHandle, 0) TO nTop     && Move pointer to BOF
			DO WHILE !FEOF(nFileHandle)
				cString = ALLTRIM(FGETS(nFileHandle, nEnd))  && Store contents
				DO CASE
				CASE cString = "*"
					&& Ignore the comment line.
				CASE [DATAPATH] $ UPPER(cString)
					This.cDataPath = ADDBS(ALLTRIM(SUBSTR(cString, AT("=", cString)+1)))
					cMessage = [Data Path from ] + This.cINIFile + [ is ] + UPPER(This.cDataPath)
					This.LogError(0, PROGRAM(), LINENO(), cMessage)

				CASE [DEBUG] $ UPPER(cString)
					* Send debugging messages to the error log.
					IF "OFF" $ UPPER(cString)
						This.lDebug = .F.
					ELSE
						This.lDebug = .T.
					ENDIF

				CASE [STARTPATH] $ UPPER(cString)
					cPath = ADDBS(ALLTRIM(SUBSTR(cString, AT("=", cString)+1)))
					SET DEFAULT TO (cPath)
					This.cStartPath = cPath
					cMessage = [Default Path from ] + This.cINIFile + [ is ] + UPPER(This.cStartPath)
					This.LogError(0, PROGRAM(), LINENO(), cMessage)

				CASE [TEMPPATH] $ UPPER(cString)
					This.cTmpPath = ADDBS(ALLTRIM(SUBSTR(cString, AT("=", cString)+1)))
					cMessage = [Temp Path from ] + This.cINIFile + [ is ] + UPPER(This.cTmpPath)
					This.LogError(0, PROGRAM(), LINENO(), cMessage)
					
			ENDDO
			= FCLOSE(nFileHandle)  && Close the .INI file
		ELSE
			This.LogError(0, PROGRAM(), LINENO(), This.cINIFile + [ file could not be opened.])
		ENDIF
	ENDPROC


	HIDDEN PROCEDURE SetDefaults
	
		* If properties are not provided, use these defaults.		
		This.cDataPath = Application.DefaultFilePath + [\Data\]
		This.cTmpPath = Application.DefaultFilePath + [\Temp\]
	
	ENDPROC


	PROCEDURE ShowProperties
	LOCAL cProperties
	
	*	Returns a string with the session properties.
	
		cProperties = ;
			[Thread ID:  ] + ALLTRIM(STR(This.nThreadID)) + eol + ;
			[Data Path:  ] + This.cDataPath + eol + ;
			[Error Log:  ] + This.cErrorLog + eol + ; 
			[INI File:  ] + This.cINIFile + eol + ;
			[Current Path:  ] + This.cPath + eol + ;
			[Server Name:  ] + This.cServerName + eol + ;
			[Start Path:  ] + This.cStartPath + eol + ;
			[Saved Path:  ] + This.cSavePath + eol + ;
			[Saved Default Directory:  ] + This.cSaveStartDir + eol + ;
			[Temp Path:  ] + This.cTmpPath + eol + ;
			[Debug:  ] + IIF(This.lDebug, "True", "False") + eol

		RETURN cProperties

	ENDPROC
ENDDEFINE



*!*	***************************************************************************************************
*!*	*	The following is sample contents for an INI file.  Remove commenting before trying to use.
*!*	*
*!*	*	Hmmm... Not myuch left after removing application specific settings!
*!*	***************************************************************************************************
*!*	DEBUG OFF
*!*	*[Paths]
*!*	DATAPATH = Data
*!*	TEMPPATH = Temp
*!*	STARTPATH = C:\inetpub\[URL unfurl="true"]wwwroot\myWebApplication\[/URL]
*!*	*[User Properties]
 
Thanks, Ron.

That might come in handy for me. I'll let you know if it works for me or if I have any improvements or ideas when using it...

Star awarded anyway, sharing code should be rewarded...

Bye, Olaf.
 
By the way, I had an idea of a connection handler, I didn't worked out in detail, but made some simple tests:

a) build a COM server connection handler as an EXE, not an mtdll.
b) Have that exe alive all the time. (The webserver or even some other process then should have an eye on that handler being kept alive or restarted).
b) use it via GetObject(,"my.comserver") in a script (whatever that is in the special scripting language).
c) let that connection handler create a session Com object from an mtdll and pass requests on to that object.
d) Work with handles. So the connection handler would know which session is meant for which handle and would give out that handle to the scripting language.

That way this handler would be kept alive even with no session and could watch over the sessions, make some simple load balancing or whatever. And it would even work outside of the webserver process, maybe making it easier to update COM server DLLs.

Bye, Olaf.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top