Академический Документы
Профессиональный Документы
Культура Документы
Craig Berntson
3M Health Information Systems
3757 South 700 West #21
Salt Lake City, UT 84119
Voice: 801-699-8782
www.craigberntson.com
Email: craig@craigberntson.com
Overview
This session gives an overview of COM+ services, beginning with the basics of COM
and how component design changes under COM+. It then moves into creating COM+
components and how to administer COM+ applications and security. It then moves onto
transaction management and transacting VFP tables using Compensating Resource
Managers. Asynchronous events such as Queued Components and Loosely Coupled
Events are covered.
Using Windows Component Services (COM+) with Visual FoxPro
Reviewing COM
For many years, COM has been the method used to integrate and automate
applications under Windows. For example, if you automate Word, Excel, or Outlook
from your Visual FoxPro application, you do it through COM.
COM is an acronym for the Component Object Model. It is a specification for writing
reusable software that runs in component-based systems. It turns out there are two
types of COM components, in-process and out-of-process.
An in-process component is compiled as a DLL and requires an EXE to host it. When
you call the component, it is hosted by your application EXE, thus it runs in your
application s memory space on the same computer. An out-of-process component is
compiled as an EXE and runs in its own memory space. There are pros and cons of
each method. If the in-process DLL should crash, it could corrupt the memory space of
your EXE and cause it to crash too. This isn t the case with a COM EXE. Because it
runs in its own memory space, it is considered safer to run. However, the data transfer
across the process boundaries from your EXE to the COM EXE takes longer than from
your EXE to the COM DLL, making the EXE slower.
The application that uses COM component is called the client and the COM component
is called the server. Do not confuse this with Client/Server databases. Visual FoxPro
provides the capability to create clients and both in-process and out-of-process servers,
with one caveat. VFP in-process components do not support user interfaces, so you
can t get any screen output from them.
Whether you compile an in-process DLL or an out-of-process EXE, what happens under
the hood is pretty much the same. You application never directly talks to the COM
component. Instead, it talks to a proxy. The data is communicated across the process
boundary to a stub, which then passes communication on to the COM component
(Figure 1). You don t have to worry about creating the proxy and stub or a
communication channel for the data. Windows does all that for you. The work that
Windows does for this is frequently called plumbing (after the plumbing in your house).
When you use a COM component, you typically connect to it, do lots of work with it,
then disconnect at the very end. This is a stateful connection, meaning the component
exists (has state) for a lengthy period of time.
WITH This
IF PARAMETERS() = 2
.nNumber1 = tnOne
.nNumber2 = tnTwo
ENDIF
RETURN lnResult
ENDFUNC
WITH This
IF PARAMETERS() = 2
.nNumber1 = tnOne
.nNumber2 = tnTwo
ENDIF
Save and close the program and select Build from the Project Manager. The Build Options dialog is
displayed
Select Single-threaded COM server (DLL) then click OK. Visual FoxPro compiles the program and
creates the DLL.
Notice the OLEPUBLIC keyword. This tells the compiler to create a COM component. If
you design the class visually, check OLE Public on the Class Info dialog (Figure 2).
Figure 2. The Class Info dialog is used to set information for the entire class.
The Build Option dialog gave three choices for building a COM component, as listed in
Table 1.
Table 1. The Build dialog contains three options for creating a COM component
Build Action Description
Win32 Executable / COM server (EXE) Creates an out-of-process EXE COM server
Single-threaded COM server (DLL) Creates a single-threaded in-process DLL COM server
Multi-threaded COM server (DLL) Creates a multi-threaded in-process DLL COM server
A single-threaded COM server can only be instantiated one time. Subsequent calls will
be blocked and will wait until the COM server is available. For example, if you have a
COM server that runs a lengthy process and multiple users attempt to call it at the same
time, the first user will get access and other users will wait their turn. A single-threaded
COM server is useful when the component is installed locally on the workstation.
A multi-threaded COM server gets around this problem. Multiple calls are not blocked.
Use multi-threaded COM server when the component should be installed on a server for
multi-user access. This is the type of COM DLL you will compile when creating COM+
components.
When VFP builds a COM component, it generates several registry entries that point to
the directory and file that was compiled. This way, when the component is used, VFP
can find it when it s run on the development machine. In addition, a type library is
created. This file contains the same root name as the DLL but with a .tlb extension. If
you distribute the COM DLL, you need to include the type library and the proper VFP
runtime libraries.
Now that you ve created the COM component, it s time to use it. The following steps
show you how to do this.
In the Command Window enter MODIFY COMMAND TestLocal
Enter the code in listing 2.
Save and run the program.
CLEAR
ox = CREATEOBJECT( BizRules.Math )
? ox.Multiply(2, 3)
ox.nNumber1 = 5
ox.nNumber2 = 6
? ox.Add()
When you run the program, first the screen is cleared, the COM component is
instantiated. VFP looks up BizRules.Math in the registry to get the location of the COM
file. Then, the Multiply method is called and the result of 6 is displayed. The two
numbers are then set to new values, the Add method is called, and the result of 11 is
displayed. When the program ends, the component is released.
What is DCOM?
Under the Distributed Component Object Model (DCOM), the component is installed on
a remote computer, typically a server. The component runs in memory on the server,
rather than the work station. An in-process DLL still needs to be hosted by an EXE. In
the case of DCOM, this is typically DLLHost.exe.
The plumbing for a DCOM application is very similar to that of a COM application,
except that the communication is across a computer boundary (Figure 3). Windows still
provides all the plumbing for this to happen. However, you have to do some
configuration so the workstation knows on which server the component is installed. You
have probably already guessed that DCOM is slower than COM because the
communication from the application to the component needs to go across the network.
One advantage of DCOM over COM is that the component is centrally located on the
server. You don t need to distribute the component to each user when it is updated. For
example, you can create a component that contains business rules for generating
invoices. If those rules change, you need only to install a new DLL on the server without
touching the client workstations. DCOM applications are also typically stateful.
Administration
Security
Transaction management
Just-in-time Activation
Object Request Broker (ORB)
Connection Pooling
Loosely Coupled Events
Queued Components
Dynamic Load Balancing
One other difference between DCOM and COM+ is that you should only use in-process
components in COM+. These DLLs are hosted by the COM+ runtime. Before learning
how to create a COM+ component, you need to understand a couple of other concepts,
context and threading.
The COM+ runtime is capable of hosting multiple instances of the same or different
COM DLLs. Each DLL is isolated of each other by running each one in its own context.
The DLL has access to the context to learn about the environment where it is running.
Single threading means that an application or component can only do one thing at a
time. Visual FoxPro is mostly single threaded. It uses multiple threads internally for
some things, but it does not give you the capability to create more than one thread.
When you build a Win32 Executable / COM Server (exe) or a Single-threaded COM
server (dll), you get single threading.
loMtx = CREATEOBJECT("MTXAS.APPSERVER.1")
loContext = loMtx.GetObjectContext()
WITH This
IF PARAMETERS() = 2
.nNumber1 = tnOne
.nNumber2 = tnTwo
ENDIF
loContext.SetComplete()
loContext = NULL
loMtx = NULL
RETURN lnResult
ENDFUNC
*****************
loMtx = CREATEOBJECT("MTXAS.APPSERVER.1")
loContext = loMtx.GetObjectContext()
WITH This
IF PARAMETERS() = 2
.nNumber1 = tnOne
.nNumber2 = tnTwo
ENDIF
loContext.SetComplete()
loContext = NULL
loMtx = NULL
RETURN lnResult
ENDFUNC
ENDDEFINE
Save and close the program and select Build from the Project Manager. The Build Options dialog is
displayed.
Select Multi-threaded COM server (DLL) then click OK. Visual FoxPro compiles the program and creates
the DLL.
Before you can use the new component, you need to install it in the COM+ runtime. You
will want to install it locally for testing. For deployment, you will need to place the DLL,
the type library, and the VFP runtime files on the server.
Expand the Component Services Node to Computers -> My Computer -> COM+ Applications.
Right-click on COM+ Applications and select New -> Application from the context menu. The COM+
Application Install Wizard is displayed.
Click Next to move past the welcome page.
On the Install or Create page, you want to create a new empty application (Figure 5).
On the Create Empty Application page (Figure 6), enter the name for your application. For this example,
use My COM+ App. Make sure Server Application is selected, then click Enter.
Figure 6. When you create an empty application, you need to name the application.
The next page, Set Application Identity (Figure 7), allows you to set the Windows user identity that this
component runs under. The default is Interactive User. It is not a good idea to set the component to run
under this identity as someone needs to be logged on something not common for a server. It is
recommended that you setup a Windows user specifically for your component and assign the specific
rights to that user that will be needed. For the purpose of this example, select Interactive User, then click
Next. The Thank You page is displayed.
Click Finish to create the COM+ Application. Now you need to add the FoxCOMPlus.dll to the new COM+
application.
Expand the My COM+ App node in the Component Services Manager.
Right click on Components and select New -> Component from the context menu. The Welcome page of
the COM+ Component Install Wizard is displayed. Click Next.
On the Import or install a component page (Figure 8), click Install new component(s). A file picker dialog
is displayed.
Figure 7. COM+ applications can be set to run under a specific user account.
Figure 8. After creating a new COM+ application you need to add the components.
Navigate to the proper folder and select both FoxCOMPlus.dll and FoxCOMPlus.tlb. The Install new
components page is displayed (Figure 9). Click Next. The Thank You page is displayed.
Figure 9. Use the Install new components page to specify the DLLs to add to the application.
To test the component, run the same test program that you ran earlier (Listing 3). To
verify that you actually instantiate the COM+ component, select the My COM+ App ->
Components node in the Component Services Manager (Figure 10). When the ball in
the right-hand panel is spinning, the component is currently instantiated.
Figure 10. After adding a component you can manage it using the Component Services Manager.
Figure 11. The application timeout is set on the application's property sheet Pooling and Recycling page.
PROCEDURE Init
STRTOFILE("Init: " + This.cProp + CRLF, This.cFile, 1)
This.oCom = CREATEOBJECT("MTXAS.APPSERVER.1")
ENDPROC
PROCEDURE Destroy
STRTOFILE("Destroy" + CRLF, This.cFile, 1)
ENDPROC
PROCEDURE SetProp(tcNewValue)
loContext = This.oCom.GetObjectContext()
STRTOFILE("SetProp (before): " + This.cProp + CRLF, This.cFile, 1)
This.cProp = tcNewValue
STRTOFILE("SetProp (after): " + This.cProp + CRLF, This.cFile, 1)
loContext.SetComplete()
ENDPROC
PROCEDURE GetProp()
loContext = This.oCom.GetObjectContext()
STRTOFILE("GetProp: " + This.cProp + CRLF, This.cFile, 1)
loContext.SetComplete()
ENDPROC
ENDDEFINE
Save the new program as TestJITA.prg, then compile and run it.
As you can see from the example, the value of cProp gets reset to its original value
because the Init method is run again when the component is called the second time.
This example also shows how COM+ is stateless. The value, or state, of cProp was not
maintained between calls. Stateless execution is how COM+ components are able to
scale to thousands of users. However, this comes at a price. If you have a lot of code in
the Init or Destroy methods, it will have to run on each call to the component,
decreasing performance.
Note You may not be able to rebuild the COM DLL. This is because the COM+ runtime still has the
component in memory, even though you destroyed your instance of the component. The component will
stay in memory for the length of the timeout, specified in the Component Services Manager. If you find
you cannot rebuild the component, right-click on the application in the Component Services Manager and
select Shut down from the context menu, then try again to rebuild the component.
Client Installation
You ve seen how to install the component on the server. However, in a production
environment, the client at this point knows nothing about where this component is
located. You need to install the proxy information on the client. The Component
Services Manager provides a wizard for you to create the client install package. The
following steps walk you through this.
1. In the Component Services Manager, right click on My COM+ App and select Export. The COM+
Application Export Wizard is launched. Click Next. The Application Export Information page is
displayed (Figure 12).
Figure 12. You can export a COM+ application to install it on another server.
Enter the path and file name (C:\COMPlus\ClientInstall) for the install file to be created.
Select Application proxy Install on other machines to enable access to this machine.
Click Next then Finish.
Two files are created, ClientInstall.msi and ClientInstall.msi.cab. Install one of these files
(you don t need both) on each client computer or include them in your setup program.
The file VFPCOMPlus.dll is not included in this install. The client doesn t need it, as the
component is referenced from the client and run on the server. What is created and
installed on the client is the proxy information the client needs to find and instantiate the
component.
Security
Security is an important part of any application. Setting up security under COM+ is easy
because the COM+ runtime handles security for you. You configure security in the
Component Services Manager.
COM+ security is role-based. You assign users to a specific role, then assign that role
to the particular COM+ application, component, or even a specific method. This type of
security is often called declarative. However, rather than individually assign users
directly to a role, it is considered best practice to add the users to a Windows security
group, then add the group to the COM+ security role.
In addition, COM+ allows you to use query the context of the component to determine to
which role the user belongs. For example, you may have a component that both a
manager or clerk can run, but base the processing on which role (manager or clerk) is
currently accessing the component. For information on programmatic security, consult
the COM+ documentation on the MSDN website.
Transactions
Imagine you are saving invoice data. Typically this requires updating multiple tables,
invoice header, invoice detail, customer information, inventory, shipping, and possibly
other tables. What would happen if only one of some of those tables get updated? Well,
the current data would be invalid. That s where transactions come in. Transactions
ensure that the data is saved properly.
You are probably used to using VFP transactions with BEGIN/END TRANSACTION and
committing or rolling back with COMMIT/ROLLBACK. However, VFP transactions only
work for VFP data and are not managed by COM+. In addition, VFP transactions are
not ACIDic because they are not durable.
For transactions to participate under COM+, the database needs to have a Resource
Manager. VFP does not have a Resource Manager, but VFP data can participate in
COM+ transactions by using a Compensating Resource Manager, discussed later in
this document.
COM+ transactions have several advantages over regular VFP transactions, or even
standard transactions in SQL Server, Oracle, and other databases. These advantages
include:
The COM+ runtime uses the Microsoft Distributed Transaction Coordinator (MSDTC) to
handle transactions. This means that the transaction is handled outside the database.
This may sound risky, but because the transaction is done in a two-phase commit, it
works well.
In phase one, the MSDTC queries each database, asking it if it can save the data. Once
each database has replied, phase two starts. If each database has replied that it can do
the transaction, then the MSDTC tells each database to commit the data. If any of the
databases replies that it can not do the transaction, the MSDTC issues a rollback to
each database.
Earlier examples used SetComplete and SetAbort. I explained that these methods told
COM+ that you are done with the component. They also tell the DTC to commit or abort
the transaction. However, sometimes you want to commit or abort without releasing the
component. This is accomplished with Transaction Voting.
Under Transaction Voting, there are two bits that you use to handle the transaction and
the release of the component. The first, the Consistency bit is set when you want to
commit the transaction. The second, the Done bit, is set when you are ready to release
the component. The Context object provides four interface methods to help manage
these bits.
In addiction to voting on the transaction, you can set the transactional support for the
component using the Component Services Manager. You will see exactly how to set
this in the upcoming example. The four settings are:
There is one more item to cover before getting to the example. COM+ applications
typically run business and data components. Business components are setup as
requires a transaction, requires a new transaction, or does not support transactions.
Data components are setup as requires a transaction or does not support transactions.
Now, on to the example. Here you will see how to add transactional code to the
component and set the automatic transaction support provided by COM+. The example
is shown in three parts, the user interface, the business component, and the data
components. In order to show that the transaction works across different databases, the
Pubs and Northwind sample databases from SQL Server are used.
The user interface is shown in Figure 13. An example form for testing COM+
transactions..
To simplify the example, a cursor to hold the data is created in Load method (Listing 6)
of the form.
Listing 6. The Init() method code from the Transactions form.
The Save button calls the SaveData method (Listing 7) of the form.
Listing 7. The SaveData() method code from the Transaction form.
IF llRetVal
MESSAGEBOX("Congratulations! Insert succeeded!", 64, "COM+ Save Results")
ELSE
MESSAGEBOX("Bad news. Insert failed", 48, "COM+ Save Results")
ENDIF
This code is quite easy to follow. First, the cursor is converted to XML to be passed to
the business component. Next, the business component, which will be installed in a new
COM+ application, is created, then the InsertData method is called. Finally, a message
is displayed to indicate if the data was successfully inserted or if it failed.
1. Now it s time to create the business component. Create a new project called xActBiz and add a
new program file called xActBiz.prg. Listing 8 shows the code.
Listing 8. This code is used to create the Transaction example business object.
PROCEDURE Init
* Create a reference to the MTS object
This.oMtx = CREATEOBJECT("MTXAS.APPSERVER.1")
***********************
llRetVal = .F.
llPubsRetVal = .F.
llNorthwindRetVal = .F.
loPubsData = This.oContext.CreateInstance("xActData.PubsData")
loNWData = This.oContext.CreateInstance("xActData.NWData")
lnPubsRetVal = loPubsData.InsertAuthor(tcData)
lnNWRetVal = loNWData.InsertEmployees(tcData)
RETURN llRetVal
ENDPROC
********************
There are a few things to note about InsertAuthor method. Instead of instantiating the
data components with a standard VFP CreateObject(), it uses CreateInstance() method
of the Context object. This creates the data objects in the same context as the business
component. This is required for the transaction to work correctly. After calling the data
components, the status of inserts is checked and the entire transaction is committed or
aborted.
It is also important to note the Error method. Should an error occur, the transaction is
aborted and the error returned to the client.
The business object should be compiled as a Multithreaded-COM DLL. You ll see how
to setup the COM+ application after looking at the data components.
Again, to simplify the example, both the data objects will be created in the same project.
In a production environment, each database would be serviced by separate
components and each table would have its own class. To start the data component,
create a new project called xActData. The code for PubsData.prg is shown in Listing 9.
Listing 9. Pubs database transaction example data object code.
PROCEDURE Init
* Create a reference to the MTS object
This.oMtx = CREATEOBJECT("MTXAS.APPSERVER.1")
***********************
XMLTOCURSOR(tcXML, "cAuthors")
lcSQL = "INSERT INTO dbo.authors (au_id, au_lname, au_fname, ;
phone, address, city, state, zip, contract) ;
VALUES ('999-99-9999', '" ;
+ cAuthors.LName + "', '" + cAuthors.FName + "', '" ;
IF lnRetVal > 0
* Handle Transaction Setting (Consistency)
* 0 = commit, 1 = abort
This.oContextState.SetMyTransactionVote(0)
ELSE
This.oContextState.SetMyTransactionVote(1)
ENDIF
USE IN cAuthors
RETURN lnRetVal
ENDPROC
*********************
The first thing you ll see in this code is that the COM+ Services and Context objects are
instantiated, just as they were in the business component. However, because the data
component was instantiated by COM+, this component will run in the same context.
A SQL INSERT statement is then created and sent to the database via SQL Pass
Through. Note that the Au_Id field value, which is the Pimary Key, is hard coded. The
transaction voting is then made, based on the success or failure of the INSERT.
The code for the Northwind database is nearly identical. Create a new program called
xActNorthwind and enter the code in Listing 10.
Listing 10. Northwind database data object code.
PROCEDURE Init
* Create a reference to the MTS object
This.oMtx = CREATEOBJECT("MTXAS.APPSERVER.1")
***********************
XMLTOCURSOR(tcXML, "cEmployees")
lcSQL = "INSERT INTO dbo.employees (LastName, FirstName, ;
+ HomePhone, Address, City, Region, PostalCode) ;
+ VALUES ('cEmployees.LName + "', '" + cEmployees.FName + "', '" ;
+ cEmployees.Phone + "', '" + cEmployees.Address + "', '" ;
+ cEmployees.City + "', '" + cEmployees.State + "', '" ;
+ cEmployees.Zip + "')"
IF lnRetVal > 0
* Handle Transaction Setting (Consistency)
* 0 = commit, 1 = abort
This.oContextState.SetMyTransactionVote(0)
ELSE
This.oContextState.SetMyTransactionVote(1)
ENDIF
USE IN cEmployees
RETURN lnRetVal
ENDPROC
*********************
The Primary Key for the Employees table is generated by SQL Server.
Now that you have the code entered, compile the xActData project as a Multi-Threaded
COM DLL. Now you need to create a new COM+ application in the Component
Services Manager. Name this application xAction and add the xActBiz and xActData
components to the new application. Notice that there was no code to begin, commit, or
rollback the transaction, only code voting on the outcome. The transaction management
will be setup in the Component Services Manager and handled by COM+. The following
steps show you how to do this.
1. Right-click on the xActBiz component in the Component Services Manager and select Properties
from context menu. The Properties dialog is displayed.
Select the Transactions tab in the Properties dialog (Figure 14).
Figure 14. Set transaction management on the Transaction page of the Properties dialog.
Run the xAct form, enter some data, and click Save. The data is added to the
databases. Now, run the form a second time. Because a record with a duplicate au_id is
attempted to be added to the Authors table, the entire transaction is rolled back. You
can verify this by running a query on both the Authors and Employees tables.
The solution to this is a Compensating Resource Manager (CRM). The CRM provides a
quick and easy way to integrate application resources into the DTC. However, a CRM
does not provide the isolation capabilities of a RM.
A CRM consists of a pair of COM servers. The first, the CRM Worker, writes the durable
log of action to be taken in case of recovery and performs the main action, like inserting
a record. The second, the CRM Compensator, is instantiated by the CRM system at the
completion of the transaction to handle the commit or abort. The entire process of using
a CRM is shown in Figure 15.
Figure 15. This figure shows the flow of information in a Compensating Resource Manager.
The following example code demonstrates a CRM. The example consists of four parts,
the UI, a COM+ business objects that calls the CRM worker, the CRM Worker itself, and
the CRM Compensator. The example uses the Northwind Customers table that ships
with VFP.
You ll notice the Commit and Abort buttons. A real world application would not have
these, but they facilitate the example by making it easier to demonstrate those features.
Now you need to update the code for the form. The Load() method code (see Listing
11), should remain unchanged.
Listing 11. The Load event method of the CRM Example form.
The Save button simply calls the custom SaveData() method of the form. This code
changes a bit from previous code, but it is still simple (see Listing 12).
Listing 12. The SaveData method from the CRM Example form.
llRetVal = ThisForm.oBiz.InsertData(lcXML)
This code creates a reference to the CRMBiz object, then calls the InsertData method
and passes the XML converted data.
Once you have created and saved form, you can create the CRM business object.
oMTX = NULL
oContext = NULL
*********************
*********************
PROCEDURE CommitData()
IF VARTYPE(This.oContext) = "O"
This.oContext.SetComplete()
ENDIF
This.CleanUp()
ENDPROC
*********************
PROCEDURE AbortData()
IF VARTYPE(This.oContext) = "O"
This.oContext.SetAbort()
ENDIF
This.CleanUp()
ENDPROC
*********************
PROCEDURE Destroy
This.Cleanup()
ENDPROC
*********************
PROCEDURE InsertData(tcXML)
LOCAL loWorker, llRetVal
llRetVal = .F.
This.oMTX = CREATEOBJECT("MTXAS.APPSERVER.1")
This.oContext = This.oMTX.GetObjectContext()
loWorker = CREATEOBJECT("CRMData.sesWorker")
llRetVal = loWorker.InsertData(tcXML)
IF !llRetVal
THIS.AbortData()
ENDIF
RETURN llRetVal
ENDPROC
ENDDEFINE
The business object code is similar to what you have seen before. The CRM Worker
object is created and the data passed to the InsertData() method.
Compile this code into a Multi-threaded COM DLL. You will create the COM+
application for after creating the CRM Worker and Compensator.
PROTECTED oCrmLogControl
oCrmLogControl = ""
*********************
llRetVal = .T.
lcProgIdCompensator = "CRMData.sesCompensator"
lcDescription = "Sample VFP CRM"
IF VARTYPE(This.oCrmLogControl) != "O"
This.oCrmLogControl = CREATEOBJECTEX("CrmClerk.CrmClerk.1", "", "")
ENDIF
TRY
FOR lnI = 1 TO 10
lnErr = This.oCrmLogControl.RegisterCompensator( ;
lcProgIdCompensator, lcDescription, 7)
ENDFOR
CATCH TO loErr
llRetVal = .F.
FINALLY
ENDTRY
RETURN llRetVal
ENDPROC
****************************
llRetVal = .T.
laLogRecord[1] = tcXML
TRY
* Create and write durable log of action to take (create file)
* Must call - zero based array
COMARRAY(This.oCrmLogControl, 0)
CATCH
This.oCrmLogControl.ForceTransactionToAbort()
llRetVal = .F.
FINALLY
ENDTRY
RETURN llRetVal
ENDPROC
****************************
PROCEDURE InsertData(tcXML)
LOCAL llRetVal
llRetVal = .T.
TRY
* Register the CRM Compensator
This.RegisterCRMCompensator()
This.RegisterCRMLog(tcXML)
CATCH TO loErr
This.oCrmLogControl.ForceTransactionToAbort()
llRetVal = .F.
FINALLY
ENDTRY
RETURN llRetVal
ENDPROC
ENDDEFINE
This code requires some explanation. The InsertData() method first calls the
RegisterCompensator() method. The CRM Clerk is a COM+ object that handles the
interfacing between the CRM Worker and the CRM Compensator and the durable log. It
must be created with CREATEOBJECTEX() because it does not support an IDispatch
interface. After the clerk is instantiated, the RegisterCompensator method is called so
that it knows what component to use as the CRM Compensator. Three parameters are
passed. First, is the ProgID of the CRM Compensator that you will create in the next
section. The second parameter is a string that contains the name of what to call the log.
This string is used by the monitoring interfaces. The third parameter is the number 7.
This tells the clerk that the referenced compensator will be used in all three phases of
the CRM transaction. The three phases are prepare, commit, and abort.
Next, the InsertData() method calls the RegisterCRMLog() method, passing the XML
data as a parameter. Think of this step similar to the SQL Server transaction log as it
writes out a durable log. The CRM interfaces support passing an array to hold any data
you need to get from the Worker to the Compensator. In the example, the array will
carry the data. However, it must be passed as a zero-based array, hence the call to
COMARRAY(). The array is passed to the log, then the log is written to disk.
Do not compile the CRMData project at this time. You will do that after adding the
compensator code.
oCrmLogControl = NULL
PROCEDURE ICrmCompensatorVariants_SetLogControlVariants(pLogControl ;
AS VARIANT) AS VOID ;
HELPSTRING "method SetLogControlVariants"
LOCAL lcTransactionUOW
This.oCrmLogControl = pLogControl
ENDPROC
***************************
PROCEDURE ICrmCompensatorVariants_PrepareRecordVariants(pLogRecord ;
AS VARIANT) AS LOGICAL ;
HELPSTRING "method PrepareRecordVariants"
***************************
***************************
***************************
PROCEDURE ICrmCompensatorVariants_AbortRecordVariants(pLogRecord ;
AS VARIANT) AS LOGICAL ;
HELPSTRING "method AbortRecordVariants"
***************************
PROCEDURE ICrmCompensatorVariants_CommitRecordVariants(pLogRecord ;
AS VARIANT) AS LOGICAL;
HELPSTRING "method CommitRecordVariants"
LOCAL llRetVal
llRetVal = .T.
TRY
XMLTOCURSOR(pLogRecord[1], "cCustomer")
SET EXCLUSIVE OFF
USE \complus\Data\Customers IN 0
INSERT INTO Customers (CustomerID, CompanyName, ContactName, ;
Address, City, Region, PostalCode, Country, Phone) ;
VALUES (cCustomer.CustomerID, cCustomer.CompName,
cCustomer.Contact, cCustomer.Address, cCustomer.City, ;
cCustomer.State, cCustomer.Zip, ;
cCustomer.Country, cCustomer.Phone)
CATCH TO loErr
llRetVal = .F.
FINALLY
CLOSE DATABASES ALL
ENDTRY
RETURN llRetVal
ENDPROC
***************************
PROCEDURE ICrmCompensatorVariants_BeginCommitVariants(bRecovery ;
AS LOGICAL) AS VOID ;
HELPSTRING "method BeginCommitVariants"
ENDPROC
***************************
***************************
PROCEDURE ICrmCompensatorVariants_BeginAbortVariants(bRecovery ;
AS LOGICAL) AS VOID;
HELPSTRING "method BeginAbortVariants"
ENDPROC
***************************
Save the code and compile the CRMData project as a Multi-threaded COM DLL.
Figure 17. Turn on CRMs on the Advanced tab of the application's properties dialog.
On the Transactions tab of the properties dialog for both the business component and the CRM Worker
component, select Required.
Configure the Compensator component. On the Transaction page of the Properties dialog, make sure Not
supported is selected.
Select the Activation page and uncheck Enable Just In Time Activation.
Select the Concurrency page and select Not supported.
Run the CRM form, enter some data, then click Save and Commit. The data is saved to the Customers
table. Try it again, but this time click Save and Abort. No data is written to the table.
This example only used one table, but it should be pretty simple to add additional tables
and even UPDATE and DELETE commands.
Queued Components
In the examples I have shown you thus far, you have seen how to get synchronous
results back from a COM+ component. However, there are times when you may need to
use asynchronous components. COM+ provides two methods for this. The first, Queued
Components is presented in this section. I present the second, Loosely Coupled Events,
in the next section.
Queued Components are often called Messaging Components because messages are
sent to and read from a queue.
To better understand this, look at Figure 18, which shows a typical synchronous
application. In the diagram, the workstation calls the Orders component, which writes
the Orders database and then in turn calls the Shipping component, that writes to the
Shipping database. The entire process is rolled up into a single transaction. But, what
happens if the Order component or database can t be accessed? In this case, the order
can t be taken because the transaction will fail.
Figure 18. This figure shows how a typical application wraps all updates in a single transaction.
Compare this to Figure 19, where Queued Components are used. You will notice that
several transactions are involved. What happens is the workstation writes messages
intended for the Orders component into a queue. When the Orders component
becomes available, it picks up the message, begins a transaction, and writes the data to
the Orders database. It also writes to another queue that is read by the Shipping
component to update the Shipping database. Each queue is also transactional, assuring
delivery of the messages, even when a component is not available.
Figure 19. Queued Components use multiple transactions to ensure data is delivered.
As another example, image that the company needs to ensure enough computing
power to handle peak order times from 9:00 AM to Noon. By using Queued
Components, the Shipping processes will not run during those times, but will be started
when the peak order hours are over.
You may be wondering what the yellow bars are in Figure 19. A closer look at what is
happening is needed. Instead of talking directly to the component, the application talks
to a recorder (Figure 20). When the Orders component activates, a listener hears that a
message has been recorded and passes it on to a player. The player plays the
message to the component. The application thinks it has been talking directly to the
component and the component assumes it has been talking directly with the application.
The COM+ infrastructure supplies the recorder, listener, and player. The communication
from the Order component to the Shipping component happens exactly the same way.
Because Queued Components use Microsoft Message Queue (MSMQ) under the hood,
you need to make sure you have MSMQ running. The following steps show how to do
this.
1. Right click on My Computer on the Start menu and select Manage. The Computer Management
snap-in for the Microsoft Management Console (MMC) is displayed (Figure 21).
Expand Services and Applications node.
2. Verify that Message Queuing is listed.
If Message Queuing is not listed, you ll need to add it from Add/Remove Programs in Windows
Control Panel.
This example will use the form from the earlier discussion on Compensating Resource
Managers, so it will look like Figure XX. The difference will be the code in the SaveData
method of the form. Use the following steps to make changes to the form.
1. In the command window, type MODIFY FORM CRM. The Form Designer is opened.
Select File > Save As from the menu.
In the Save As dialog, enter QC.FRM and click Save
Delete all the code from the SaveData method of the QC form and replace it with the code in Listing 16.
loCatalog = CREATEOBJECT("COMAdmin.COMAdminCatalog")
loCatalog.Connect("")
loCatalog.StartApplication("QC1")
loBiz = GetObject("queue:/new:FoxQC.sesQC")
loBiz.InsertData(lcXML)
loCatalog.ShutdownApplication("QC1")
All that is left now is to configure COM+ and run the application. As with previous
examples, COM+ security will be turned off to simplify the example.
1. Create a new COM+ application called QC. Note this is the same name used the application in
the SaveData method of the QC form.
Add the FoxQC.dll component to the application.
Right-click on the QC application and select Properties. The Properties dialog is displayed.
Select the Security tab.
Make sure Enforce access checks for this application is not checked.
Change the Authentication level for Calls drop down is set to None.
On the Queuing tab, make sure the checkboxes Queued This application can be reached by MSMQ
queues and Listen This application when activated will process messages that arrive on its MSMQ
queue are both checked.
As you can imagine, there are drawbacks to Queued Components. For example, all
parameters to the component s methods are input only. You can t expect a return value
because you don t know when the component will actually run or the requestor could
terminate before the server processing completes. Not only do you need to handle this
situation, but you need to be able to handle errors that can occur. There are three ways
you can do this:
Optimistic You trust the infrastructure and assume everything works. I don t
recommend this method.
Less Paranoid Using this method, you generate messages selectively using
exception protocols. In other words, send the exceptions to a log for someone to
handle.
Now it s time too look at the other way to get asynchronous communication in COM+,
Loosely Coupled Events.
With transient subscriptions, setup of the subscriber must be done through the COM+
administration objects. The subscriber is activated and in memory, waiting for the event.
Transient subscriptions have better performance than persistent subscriptions, but will
not survive system shutdown. Additionally, you can have only one subscriber per event.
I think that transient subscriptions are not as useful because they aren t persistent.
Because of this, I do not address transient subscriptions in this article.
Persistent Subscriptions
The following example shows how to use persistent subscriptions. It consists of the
same form that s been used before and two MTDLLs, a publisher and a subscriber. The
Load() event of the form is the same that you ve seen in the previous examples. Listing
18 shows the SaveData() method.
Listing 18.
The publisher code is simple. Because it s an interface into the subscriber, it contains
only stubs for the same methods in the subscriber. Create a new project, Persistpub.pjx,
and a new program, Persistpub.prg that contains the code in Listing 19, then compile it
as a MTDLL.
Listing 19.
All the functionality is in the subscriber code. Create a new project named
Persistsub.pjx and a new program named Persistsub.prg. Add the code in Listing 20,
then compile the project as a MTDLL.
Listing 20.
This code implements the interface from the publisher. You could add additional
subscribers to update other databases or systems. Just create another project and
program, that also implements the subscriber interface. If you need additional methods,
don t forget to add them to both the publisher and subscriber. All that s left now is to
setup the events and test the application. The following steps show you how to setup
the COM+ event system.
1. Create a new COM+ application named PersistPub.
Add a new component to the application, but do not select Install new component(s) on the first screen of
the wizard (Figure 22). Instead, select Install new event class(es). A file picker is displayed.
Figure 22. When installing an event class, use Install new event on the Component Install Wizard.
Select Persistpub.dll and click Open. The Install new components page is displayed. Click Next, then
Finish.
Create a new COM+ application named PersistSub.
Add Persistsub.dll as a new component to the application.
Drill down to the Persistsub.sesSubscriber component in the PersistPub application to reveal the
Subscriptions node.
Right-click on Subscriptions and select New Subscription from the context menu.
Click Next on the welcome screen of the Wizard. The Select Subscription Method(s) page (Figure 23) is
displayed.
Figure 23. This page is used to select the proper interfaces of an event class.
Check Use all interfaces for this component (Figure 23). Click Next. The Select Event Class page (Figure
24) is displayed.
Select the persistpub.sesPublisher interface ID in the list box and click Next. The Subscription Options
page (Figure 25) is displayed.
Figure 24. Options such as name and when to enable the subscription are entered in the New Subscription Wizard.
Enter the name for the subscription and check Enable this subscription immediately then click Next and
Finish.
Run the Persist form and test the components.
There are some interesting options available for persistent events. Figure 25 shows the
Options page of the Properties dialog for the Subscription VFP Persistent Events. The
most interesting one is the Filter criteria class. You can use the filter to have multiple
subscribers, each handling specific needs.
Figure 25. Optional subscription options are entered on the Options page of the Properties dialog.
Summary
Windows supplies many services that make your applications more robust and easier to
design, develop, manage, and configure. Features such as security, transactions,
queued components, and loosely coupled events can be used to ensure that only the
proper users enter data that gets delivered to the database. While the future is .Net-
based solutions using Indigo, it is merely a wrapper around COM+ and other Windows
services. That means that COM+ will be here for many years to come.
Craig Berntson is a Microsoft Most Valuable Professional (MVP) for Visual FoxPro, a Microsoft Certified
Solution Developer, and President of the Salt Lake City Fox User Group. He is the author of CrysDev: A
Developer s Guide to Integrating Crystal Reports , available from Hentzenwerke Publishing. He has also
written for FoxTalk and the Visual FoxPro User Group (VFUG) newsletter. He has spoken at Advisor
DevCon, Essential Fox, DevEssentials, the Great Lakes Great Database Workshop, Southwest Fox,
DevTeach, FoxCon, Microsoft DevDays and user groups around the country. Currently, Craig is a Senior
Software Engineer at 3M Health Information Systems in Salt Lake City. You can reach him at
craig@craigberntson.com,, www.craigberntson.com, or his blog at www.craigberntson.com/blog.