5454
How multi-threaded COM component under MS MTS/Component Services "works"?

 

The contents:

The description of a problem

> Has created in VFP 6.0 (SP5/VS6) single-threaded COM server (dll), - works normally.
> However, at failed one of the clients (Win98)
> The work renews only after hand-operated shutdown of a package.
> Is way to avoid it?

How to solve this problem remaining under single-threaded I do not know... it's possible to reduce idle timeout of a package/application. But exist cardinality solution of this problem - by create a multi-threaded COM server (dll) (VFP 6.0 SP3VS6 and above). It is necessary also to have in view: that the components in model multi-threaded COM can be as stateful and stateless. Using stateless components, you can considerably increase performance of your system.

Differences between stateful and stateless components
Go top

To understand: bу what miracle the performance can be increased, we shall try, as passing remark, define distinctions between stateful and stateless by components (in my free-translation from the book Roger Jennings' Database workshop: Microsoft Transaction Server 2.0, Sams publishing).

Problems of a stateful component.
Go top

Stateful the components maximum coincide with a traditional sight on object-oriented programming, however their realization have some problems. Let's consider it in a light of such concepts as: livetime of component, ownership component and performance of system.

The livetime. Any instance of component, being created continues "to live" so long, how many time in it is a necessity of the user, and is destroyed only after complete end of work with it. The problem that each of such instances grasps resources, in particular memory of server machine. All this can result in a fast exhaustion of its resources.

Ownership component. As each instance of a component has the unique information of data (as values of its properties), it can not be used as shared by several users. Actually only one client created similar instance of a component uses this information. In result between each client and such instances of components the indestructible connection during all operating time of the client is established. It can and not cause the special problem, if the amount of instances of objects, used by the user, is not rather great. However, pay attention, that first - it is resources of server, and secondly - these resources are spent proportionally as to amount of component used by one user, and amount of the clients. Besides, for access to a database each user receives connection. The establish one connection, for example to MS SQL Server, can occupy about two seconds of real time of the client, while grasp 50К memory, and it is resource of server, with which each client for performance of the work should own.

Performance of system. Even the brief review of stateful a component problems above, as results thinking, that it will not promote at high efficiency, and faster on the contrary: the performance will be decline to decrease with growth of amount of the clients and increase volume of the information necessary to them. The problem also is aggravated by work with properties of such components. Reading/Writing of each property of instance components as results call to network protocol (RPC Call). Pay attention: proportionally both amount of used instances of objects and amount of their properties. All this will result in the large expenses of network resources even in comparison with systems based on traditional two-tier architecture.

Advantages a stateless component.
Go top

Stateless components have a lot of advantages opposite stateful components at creation the large and scalable information systems... And if them also to consider from the point of view of concepts mentioned above, we have the following.

The livetime. As each instance of components on server does not contain any unique information for the client (i.e. there are no properties, keeping data between calls to various methods of a component instnce), it can be used at once by several clients. The instance of components occurs only then, when any method requires the fulfilment... Also can be destroyed, as soon as anybody does not require any more execution of its methods. Certainly, such behaviour promotes keeping only minimum quantity of instances of objects on server for service of a lot of the clients.

Performance of system. The components "live" on server, and the absence of attributes (properties) and as results bring to a simple way of their call (Hm, if in told above I did not doubt, and it was almost self-evident, here I shall allow myself to not agree, for the absence of properties/attributes automatic conducts to necessity for use of the large number of parameters for methods... in other view, that the interdiction on transfer of references as results bring to the elementary methods network marshaling. Besides, the storage of all data is directly on the client and transfer them through parameters methods of the server's a component, most likely will result in necessity to write wrap-coverings for stateless component, that to use them without suffer torments on the party of the client. In other words, there will be necessity of creation of one more tier, but "living" already on the party of the client only :-) Since stateless the components are very undemanding to network resources, can be easily redistributed on servers, instead of being concentrated only on one machine.

As we know, that advantages does not happen without lacks, so below we shall consider briefly features of use a stateless component. :-)

The explanation to work of a stateless-component.
Go top

How it works I shall try explain on a simple example. Let's assume, on the client you try to create instance of the server's object:

   LOCAL loRefToServerObject
   loRefToServerObject = CreateObject("MyLib.MyClass")

in this point, on the client was created Proxy-stub instance, while on server the concrete instance of object was created/activated... Pay attention, that it does not mean, what exactly just this created/activated server's instance of object will be "participant" of operations, discussed below. As I understand, use real server's object is caused only by necessity of control for the client access to object. Here it's necessary underline, that the client has the reference, but not to the real instance of server's object, but only to the real instance of Proxy-stub, capable to address to any real instance server's object. Let further having the reference, you address to one of methods of the instance of object:

   loRefToServerObject.MyAnyMethod1(...)

here just now server, by found at itself a suitable the instance of real object (or by creating new), as reaction to inquiry through Proxy-stub, began call of your method, but as soon as the method was finished (inside a method was executed: IObjectContext::SetComplete or IObjectContext::SetAbort), server here has disconnected own Stub-stub from a just used instance of object at itself on server... Let's assume, that you here do the new manipulation with component:

   loRefToServerObject.MyAnyMethod2(...)

Now again, as reaction on inquiry through Proxy-stub, server should satisfy it through any instance of real object on server... Probably new or it's possible through any before existing, but it's completely not necessary, that it will be exactly the instance of object, for which the method MyAnyMethod1 was executed above... At the client is really the reference to the same Proxy-stub only, but not the reference to the real instance of server's object...

As I hope, it is a simple explanation can open eyes on mysterious behaviour of yours server's multi-threaded COM object in stateless mode. I am not sure, that in details it works just as is written by me above, however as the scheme it work similar it.

Besides, by using a method IObjectContext::CreateInstance, you can create instance of objects, required to you "on fly", directly on server, i.e. not use any network marshaling.

Example for experiments

Code of component
Go top

To give a trivial example for your experiments, a code of the elementary component, using MTS/COM+ server's feature is shown below

*////////////////////////////////////////////////////////
*// Class:       longrun
*// Description: The simple test for vfp-com-stateless object
*// Author:      Michael Drozdov 
*// Home page:   http://vfpdev.narod.ru/ 
*// e-mail:      Mailto:Drozdov@ics.perm.su 
*// Date:        01/11/2003 
*////////////////////////////////////////////////////////
*//
#DEFINE MTXAPPSRV	"MTxAS.AppServer.1"
#DEFINE STATELESS	.T.

DEFINE CLASS longrun AS SESSION OLEPUBLIC
	PROTECTED cObjID 	&& unique ObjID

	*//////////////////////////////////////
	*// Init on create new object
	PROCEDURE Init
		=SYS(2339,0) && see Q258736 in MSDN
		*
		*-- Generate unique ObjID for instance object
		this.cObjID = SYS(2015)
	ENDPROC

	*//////////////////////////////////////
	*// Get unique ObjID for object
	FUNCTION GetObjID
#IF STATELESS
		*
		*-- Create COMSVCSLib.IMTxAS object
		LOCAL loMtx && As COMSVCSLib.IMTxAS (see Q193295 in MSDN)
		loMtx = CreateObject(MTXAPPSRV)		
		*
		*-- Get ObjectContext		
		LOCAL loCtx && as COMSVCSLib.ObjectContext   
		IF VARTYPE(loMtx) = 'O'
			loCtx = loMtx.GetObjectContext()
		ENDIF	
#ENDIF
		*
		*-- Set result
		LOCAL lcRetVal
		lcRetVal = this.cObjID
#IF STATELESS 		
		*
		*-- SetComplete
		IF VARTYPE(loCtx) = 'O'
			loCtx.SetComplete()
			lcRetVal = lcRetVal+" Ctx"
		ENDIF	
#ENDIF
		RETURN lcRetVal
	ENDFUNC	

	*//////////////////////////////////////
	*// Long-run method
	FUNCTION RunTest(tnSecTimeOut)
		IF VARTYPE(tnSecTimeOut) # 'N'
			tnSecTimeOut = 60
		ENDIF
		LOCAL lcStatus
		lcStatus = ""
#IF STATELESS 		
		*
		*-- Create COMSVCSLib.IMTxAS object
		LOCAL loMtx && As COMSVCSLib.IMTxAS (see Q193295 in MSDN)
		loMtx = CreateObject(MTXAPPSRV)
		*		
		*-- GetObjectContext
		LOCAL loCtx && as COMSVCSLib.ObjectContext   
		IF VARTYPE(loMtx) = 'O'
			loCtx = loMtx.GetObjectContext()
			lcStatus = " Mtx"			&& state is MTxAS.AppServer object
		ENDIF	
#ENDIF
		*
		*-- Do long-run actions
		LOCAL lnStart
		lnStart = SECOND()
		*
		*-- Wait tnSecTimeOut
		DO WHILE (SECOND()-lnStart) < tnSecTimeOut
		ENDDO
#IF STATELESS 		
		IF VARTYPE(loCtx) = 'O'
			*
			*-- SetComplete
			lcStatus = lcStatus+"/Ctx" 		&& state is ObjectContext
			IF loCtx.IsSecurityEnabled()
				lcStatus = lcStatus+"/Sec" 	&& state is IsSecurityEnabled
			ENDIF
			loCtx.SetComplete()
		ENDIF	
#ENDIF
		RETURN 'Successful completed interval: '+LTRIM(STR(tnSecTimeOut))+lcStatus
	ENDFUNC	
ENDDEFINE
*////////////////////////////////////////////////////////

As it is visible from the given listing, the class longrun has, as custom: one property and two methods. The protected property cObjID in event Init() is initialized by unique value by using VFP-function SYS(2015), and the method GetObjID returns value of this property. As the event Init() for each instance of a class comes only once, we shall assume, that to various value returned by method GetObjID() there correspond to various instances of classes objects. Pay attention, that at end of a method GetObjID() the attempt call ObjectContext.SetComplete() method is made. At last, method longrun.RunTest(tnSecTimeOut) simulates long-continu process, by use "an empty cycle" during the time specified as parameter. On end of a cycle the attempt call ObjectContext.SetComplete() is made also. The returns string as result from RunTest(), allowing determine: how ObjectContext has "worked" (if worked) for the instance of your object on server.

Can seem, that if we shall add to a class longrun one more protected property (as oMtx), in which we shall keep the reference to the instance of "MTxAS.AppServer.1" object and then, by adding one more method for initialization of this property, for example:

PROCEDURE CreateMtx
#IF STATELESS
	IF VARTYPE(this.oMtx) # 'O'
		this.oMtx = CreateObject(MTXAPPSRV)
	ENDIF
#ENDIF
	RETURN (VARTYPE(this.oMtx) = 'O')
ENDPROC

(call to which we shall do in the beginning of any method longrun of a class in a stateless mode [as though aspiration to have analogue of direct presence of function GetObjectContext() from COM+ Services Type Library DLL (comsvcs.dll)]), thus we can support some "stability" value of cObjID for instance of ours longrun class. However, this impression is deceptive, after anyone of ObjectContext.SetComplete() value of property oMtx will be reset to nonexistent object... In other words, these actions will not result - that in a stateless mode we will receive identical values on the sequence call method longrun.GetObjID().

The description of the interface of client
Go top

For use this component I write the elementary client on VFP/VB 6.0 SP5VS6, allowing to check work of this component on server. In figure its window interface is shown below.

here assignment of buttons the following:

Name Description
Edit SOAP options - show Dialog "Edit SOAP protocol options". Enabled if Use SOAP-protocol checked. 
Create object - Creates instance of object, if it is not created yet.
Start longRun - Creates instance of object, if it is not created yet, and call its method RunTest(tnSecTimeOut), where as parameter the time (in seconds) entered in the field TimeOut is used, thus in ListBox is displayed occurring events. On completion of a method the returned result is displayed in the field Return value.
Get objectID - Creates instance of object, if it is not created yet, and call its method GetObjID() displaying result in ListBox.
Clear log - Clears contents of ListBox.
Release object - Destroys the reference to the instance of the created object.
Cancel - Closes the window of dialogue.

SOAP-protocol options
Go top

By using MS SOAP-protocol for any COM component, published on Web server as Web Service, you can create/use its instances without preliminary registration (i.e. without that it's usually made for use COM/DCOM/COM+) on the party of the Internet-client. On the following picture the dialog editing of SOAP-protocol parameters, caused on click Edit SOAP options button from the client-application testclient.exe is shown:

For demonstration of manipulation with VFP-COM+ component on the part of the Internet-client through MS the SOAP-protocol in an example the opportunities MS SOAP Toolkit 3.0 are used. Files, received by the utility WSDL Generator for test VFP-COM the component (vfpcomtest.dll), directly how it was received by me on WinNT 5.0 Server, lay in the catalogue Soap/wsdl/. In dialogue above, the values of parameters are those, as though at generator was specified as the destination catalogue is C:\Inetpub\WebServices\vfpcomtest\ on Web server (with established MS IIS 5 certainly). And subdirectory WebServices should be registered from under Computer Management in Services and Applications/Internet Information Servecies/Default Web Site/ as virtual with checked Run script(such as ASP), if in generator of files you used ASP setting. In the example, at generator of a WSDL-file I used default setting(ISAPI), therefore for the virtual catalogue it is necessary to check Execute(such as ISAPI applications or CGI) also.

If at you all will be made as well as is written by me above, for manipulation through test VFP-client application (testclient.exe), started on yours Web server, the need for editing parameters of the SOAP-protocol will not arise, while for manipulation from the remote Internet-client to yours Web server through the SOAP-protocol, it will be necessary: 

A code of creation the instance of VFP-COM component in client application is the following:

#DEFINE VFPOBJPROGID	"vfpcomtest.longrun"
#DEFINE SOAPCLNTPROGID	"MSSOAP.SoapClient30"
WITH ThisForm
	IF VARTYPE(.oVfpComObj) # 'O'
		IF .chkUseSoap.Value
			*
			*--SOAP
			.oVfpComObj = CreateObject(SOAPCLNTPROGID)
		ELSE	
			*
			*--DCOM
			.oVfpComObj = CreateObject(VFPOBJPROGID)
		ENDIF	
		IF VARTYPE(.oVfpComObj) = 'O'
			IF .chkUseSoap.Value
				*
				*-- SoapInit
				.oVfpComObj.MSSoapInit(.cSoap_wsdlfile ;
					,.cSoap_name, .cSoap_port, .cSoap_wsmlfile)
			ENDIF
		ENDIF
	ENDIF
ENDWITH

Here properties of the form: .cSoap_wsdlfile, .cSoap_name, .cSoap_port, .cSoap_wsmlfile just what are received from dialog of editing parameters of the SOAP-protocol, shown above.

Note about versions. In the example I used just 3.0 versions and if it not so at you (i.e. at you version 2.0 is established and there is no desire to establish just 3.0), you should correct "MSSOAP.SoapClient30" to "MSSOAP.SoapClient" (or "MSSOAP.SoapClient.1") I.e. 

with subsequent recompiling. Unfortunately Microsoft has not become support VersionIndependentProgID.

Subdirectory Soap/wsdl/ besides files received by the utility WSDL Generator, contain the file vfpcomtestSoap2.js, for test call to component from yours Web server, and in subdirectory Soap/JsClient the file vfpcomtestSoap.js to test call from any Internet-client, for which yours Web server is accessible. Edit the code in these files on your names/sites before attempt to apply it, with taking into account the Note about versions MS SOAP Toolkit above.

Attention. In order that it worked, the installation MS SOAP Toolkit 3.0 (or 2.0) both on yours Web server, and on any Internet-clients is necessary (on the clients superfluous it will be possible to remove in a consequence :-). Here the address for free downloading MS SOAP Toolkit 3.0 http://msdn.microsoft.com/downloads/sample.asp?url=/msdn-files/027/001/948/msdncompositedoc.xml&frame=true Also, I has paid attention, that with Web server, on which MS IE 5.? is installed, I did not manage to receive instance of components through the SOAP-protocol. Therefore I suspect, that on Web server the installation just MS IE 6.0 (or above) also is required.

ASP-example
Go top

On next picture the result of the manipulation of the MS IE client to asp-file is shown. At performance asp-page VFP-COM-componet is used on the party Web server.

At click on one of two buttons, submit to server by the method POST is sent. However, to transfer on server the name of method, by which it is required to call, on page the hidden-field is used, in which the name of method as value is saved, and demanded by the client. The source code both parts: client, and server is written on Jave Script, and I have tried to comment it so, as far as it only is possible for me :-)

Use event Init() for getting reference to ContextObject
Go top

If to observe behaviour of the class longrun (see above) it is possible to find out, that the events Init()/Destroy() come on each client-call to the method GetID() of component! I.e. events Init() can be used for get the reference to IContextObject, while in event Destroy() it should be destroyed. In other words, event Init()/Destroy() are mapping of events ObjectControl_Activate() / ObjectControl_Deactivate() as ObjectControl from comsvcs.dll You can check up it on such example of the code:

*/////////////////////////////////////////////////////////////
#DEFINE LOGFILE		"c:\Temp\Vfpcom.log"
#DEFINE METHODLEN	13
#DEFINE MTXAPPSRV	"MTxAS.AppServer.1"

DEFINE CLASS SrvCls AS Session OLEPUBLIC
	PROTECTED oCtx
	PROTECTED cUniqueObjID
	oCtx = NULL
	cUniqueObjID = ""

	FUNCTION Init()
		DECLARE INTEGER GetCurrentThreadId IN win32api
		IF SYS(23390) # '0'
			=SYS(2339, 0)
		ENDIF	
		This.WriteToLog("Init()")
		IF !This.GetObjectContext()
			RETURN .F.
		ENDIF
		This.cUniqueObjID = SYS(2015)
	ENDFUNC
	
	FUNCTION Destroy()
		This.WriteToLog("Destroy()")
		This.oCtx = NULL
	ENDFUNC

	FUNCTION GetID()
		This.WriteToLog("GetID")
		LOCAL lcID
		lcID = This.cUniqueObjID
		This.WriteToLog("GetID:..." + SUBSTR(lcID, 7))
		This.SetComplete(.T.)
		RETURN lcID
	ENDFUNC

	PROTECTED FUNCTION GetThreadID()
		RETURN GetCurrentThreadId()
	ENDFUNC

	PROTECTED FUNCTION GetObjectContext()
		IF VARTYPE(This.oCtx) # 'O'
			LOCAL loMtx
			loMtx = CREATEOBJECT(MTXAPPSRV)
			IF VARTYPE(loMtx) = 'O'
				This.oCtx = loMtx.GetObjectContext()
			ENDIF
		ENDIF
		This.WriteToLog("GetObjectContext()")
		RETURN (VARTYPE(This.oCtx) = 'O')		
	ENDFUNC

	PROTECTED FUNCTION SetComplete(tbResult)
		IF VARTYPE(This.oCtx) = 'O'
			IF tbResult
				This.WriteToLog("SetComplete()")
				This.oCtx.SetComplete()
			ELSE
				This.WriteToLog("SetAbort()")
				This.oCtx.SetAbort()
			ENDIF	
			RETURN .T.
		ELSE
			This.WriteToLog("***UnknownResult***")
			RETURN .F.
		ENDIF	
	ENDFUNC

	PROTECTED FUNCTION WriteToLog(tcMethod)
		IF VARTYPE(tcMethod) # 'C'
			tcMethod = '(Unknown)'
		ENDIF
		tcMethod = PADR(tcMethod, METHODLEN)
		LOCAL lcObjectContextInfo
		IF VARTYPE(This.oCtx) = 'O'
			lcObjectContextInfo = "Yes " ;
				+ TRANSFORM(This.oCtx.ContextInfo.GetContextId())
		ELSE	
			lcObjectContextInfo = "No"
		ENDIF	
		STRTOFILE(TTOC(DATETIME()) ;
			+ " ThreadID: " + TRANSFORM(This.GetThreadID(), '9999') ;
			+ " " + tcMethod + " fired! ObjectContext: " ;
			+ lcObjectContextInfo ;
			+ CHR(13) + CHR(10) , LOGFILE, .T.)
	ENDFUNC

ENDDEFINE
*/////////////////////////////////////////////////////////////

The example of code to call this component next:

*///////////////////////////////////////////////////////
#DEFINE THISPROGID	"smpsrv.SrvCls"
LOCAL loT1, loT2
loT1 = CreateObject(THISPROGID)
IF Vartype(loT1) # 'O'
	RETURN .F.
ENDIF	
loT2 = CreateObject(THISPROGID)
IF Vartype(loT2) # 'O'
	RETURN .F.
ENDIF	
ACTIVATE SCREEN
CLEAR
?loT1.GetID()
?loT1.GetID()
?loT2.GetID()
?loT2.GetID()
RELEASE loT2
RELEASE loT1
*///////////////////////////////////////////////////////

Here file c:\Temp\Vfpcom.log has turned out at me as the result of work:

03/06/06 06:31:44 PM ThreadID: 3788 Init()        fired! ObjectContext: No
03/06/06 06:31:44 PM ThreadID: 3788 GetObjectCont fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 1572 Init()        fired! ObjectContext: No
03/06/06 06:31:44 PM ThreadID: 1572 GetObjectCont fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}
03/06/06 06:31:44 PM ThreadID: 3788 GetID         fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 3788 GetID:...PPUC fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 3788 SetComplete() fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 3788 Destroy()     fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 3788 Init()        fired! ObjectContext: No
03/06/06 06:31:44 PM ThreadID: 3788 GetObjectCont fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 3788 GetID         fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 3788 GetID:...PPV8 fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 3788 SetComplete() fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 3788 Destroy()     fired! ObjectContext: Yes {5EA82F8C-58D3-4244-945E-8CC191F7C6D5}
03/06/06 06:31:44 PM ThreadID: 1572 GetID         fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}
03/06/06 06:31:44 PM ThreadID: 1572 GetID:...PPUS fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}
03/06/06 06:31:44 PM ThreadID: 1572 SetComplete() fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}
03/06/06 06:31:44 PM ThreadID: 1572 Destroy()     fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}
03/06/06 06:31:44 PM ThreadID: 1572 Init()        fired! ObjectContext: No
03/06/06 06:31:44 PM ThreadID: 1572 GetObjectCont fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}
03/06/06 06:31:44 PM ThreadID: 1572 GetID         fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}
03/06/06 06:31:44 PM ThreadID: 1572 GetID:...PPV9 fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}
03/06/06 06:31:44 PM ThreadID: 1572 SetComplete() fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}
03/06/06 06:31:44 PM ThreadID: 1572 Destroy()     fired! ObjectContext: Yes {4AA29188-099A-4F83-8000-F28CC473E0BA}

Here it is possible to see, that:

The possible scenario of experiments
Go top

It is possible to lead VFP-COM-componet above do experiments of two types: multi-threaded COM and single-threaded COM server (dll) (see mode of creation components in dialogue Build... from the VFP-project [VFP 6.0 SP3VS6 and later]). By creating the component one of the specified types and register it on MTS/Component Services. If the machine of the client is not simultaneously as serever, you need make export of the package/application, created by you, for the machine of the client. (For more details of registration/export see subjects: "Installing and Managing COM + Applications: Part 1-2" by Scott Mauvais, "Understanding COM + with VFP, Part 1-3" by Craig Berntson in MSDN, and also reference http://www.craigberntson.com/Articles/kb011.htm (eng) and/or subject "Microsoft Transaction Server for Visual FoxPro Developers" by Randy Brown in MSDN. At registration components on remote server, pay attention, that is required to specify the network password (probably group) user on the tab Identity/Account This user... for a package/application, Other variant: create a new local user with the administrative rights on server and enter him as value of 'This user' (if server is same as client, that happens during creation/debugging a component, last variant most preferable) ... Anyway in opposite variant I did not receive access to a component from the remote client).

Try call server's component, by using client-application mentioned above. Thus create some instances of this application and experiment with commands: Create object, Release object, Get objectID with Start longRun on other instance of the application, and observing (is possible through Terminal Server Client [MSTSC.EXE]) for "activity" server's components. Try also "unexpectedly to stop work" on client machine in an operating time of the several started clients, who are carrying out the command Start longRun on her (for example through the button turn off electro-power :-). What will occur with server's component? Whether will of a component be capable support the clients process at once after similar action? :-)

CODEPAGE VFPxT.DLL problems
Go top

It was defined, that in VFP 6.0/7.0 for multi-theaded dll there is a problem with CODEPAGE, in that sense that:

The code of a simple class below is given to be convinced of it.

Code for check CODEPAGE
Go top

DEFINE CLASS MyClsSes As Session OLEPUBLIC
	FUNCTION GetCpCurrent
		RETURN CPCURRENT()
	ENDFUNC
ENDDEFINE

If by placing this class in the project (for example cpcomtest.pjx), after creation cpcomtest.dll as multi-theaded dll and registration it in a package/application under MS MTS/Component Services, from VFP of a window Command for VFP 6.0 we have:

ob=CreateObject("cpcomtest.MyClsSes")
?ob.GetCpCurrent()
1251
ob1=CreateObject("cpcomtest.MyClsSes")
?ob1.GetCpCurrent()
1252

Pay attention, that:

How correct VFP6T.DLL
Go top

The decision has found Serg Suprun. Here him emotional message from fido7.visual.foxpro.ru news:

Hurrrrrrraaaaaaah!!!

I have won this microsoft, let's Bill hiccup on wake up! :-)))
The default codepage lays in vfp6t.dll to the address
000F2E93.
Simply we change
E4 to E3 on this address also it is received 1251 forever!
It not that decision, which was to be received certainly, but the problem is solved by it.

In SP3's document it is directly written:
Users who need to have servers, each with a unique codepage, can place these
servers, along with a copy of the Vfp6t.dll run-time library in a unique
folder. A server will always default to using a run-time library stored in
its folder.


It seems that after binge it is written :-).

Bye, Serg.

How overcome the problem of VFP7T.DLL/VFP8T.DLL
Go top

Here addresses kindly given by Serg Suprun in fido7.visual.foxpro.ru news and I, as well as promised, I do the duplicate of this information here :-)

File Address

What change

vfp7t.dll(9262) 000D38E8 E4->E3
vfp7t.dll(9282) 000D4DD1 E4->E3
vfp7t.dll(sp1) 000D5B42 E4->E3
vfp8t.dll 00003CD8 E4->E3
vfp8t.dll(sp1) 00003CE3 E4->E3
vfp9t.dll(beta) 00112ED7 E4->E3

For vfp9t.dll version 9.0.0.2412 any problems not found by me... Also it would be desirable to hope, that reduced above list is closed!

What to do if not make of any changes in vfpXt.dll? On Serever it is possible simply using SET NOCPTRANS TO &lcListNamesAllLields, and for temporary cursors use the utility .../Tools/Cpzero/Cpzero.prg for reset code page from code before use.

See also addresses for corrections of a similar problem in vfpoledb.dll on How call Stored Procedure of a VFP-database in VFP 7.0 and later through OLE DB.

The downloading code and etc.
Go top

See also:

 
 
Hosted by uCoz