How multi-threaded COM component under MS MTS/Component Services "works"? |
> 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.
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).
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.
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. :-)
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.
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().
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. |
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.
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 :-)
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:
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? :-)
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.
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:
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.
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.
See also: