Вы находитесь на странице: 1из 50

Using Windows Component

Services (COM+) with


Visual FoxPro

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).

Craig Berntson, 2005


Page 3 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 1. COM components talk to each other across a process boundary.

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.

Creating a COM component


At this point, you may be wondering how to create a COM component. The following
steps walk you through creating a COM component.
Create a new project called BizRules
Add a new program file named MathClass.prg. Enter the code in Listing 1 into the editor.

Listing 1. This code is used to create a COM component

DEFINE CLASS Math AS SESSION OLEPUBLIC


nNumber1 = 0
nNumber2 = 0

FUNCTION Add(tnOne AS Integer, tnTwo AS Integer) AS Integer


LOCAL lnResult

WITH This
IF PARAMETERS() = 2
.nNumber1 = tnOne
.nNumber2 = tnTwo
ENDIF

lnResult = .nNumber1 + .nNumber2


ENDWITH

Craig Berntson, 2005


Page 4 of 49
Using Windows Component Services (COM+) with Visual FoxPro

RETURN lnResult
ENDFUNC

FUNCTION Multiply(tnOne AS Integer, tnTwo AS Integer) AS Integer


LOCAL lnResult

WITH This
IF PARAMETERS() = 2
.nNumber1 = tnOne
.nNumber2 = tnTwo
ENDIF

lnResult = .nNumber1 + .nNumber2


ENDWITH
RETURN lnResult
ENDFUNC
ENDDEFINE

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).

Craig Berntson, 2005


Page 5 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Listing 2. Code to instantiate and use a COM component.

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

Craig Berntson, 2005


Page 6 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Figure 3. DCOM components communicate across computer boundaries.

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.

Craig Berntson, 2005


Page 7 of 49
Using Windows Component Services (COM+) with Visual FoxPro

So, what are Component Services?


Windows Component Services, also called COM+, is a set of services that host COM
components for DCOM-type communication. At a basic level, COM+ works the same
way as DCOM (See Figure 3). However, unlike DCOM, COM+ applications get much of
their scalability and extensibility from stateless connections. When using stateless
connections, you should connect to the component, run the method, then disconnect.

In addition to increased scalability, there are a number of additional services provided


by COM+. These services include:

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.

Threading refers to how many operations at a time the application or component is


capable of running. There are many types of threading, but I will only discuss three
here, single, multi, and apartment-model.

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.

When an application is multi-threaded, it is capable of doing several things at the same


time. A VFP COM DLL that is compiled using the Multi-threaded COM server (dll) uses
a special type of multi-threading called apartment-model threading. Under apartment
model threading, each thread lives in its own apartment and is unaware of other
apartments. Conflicts with global data are avoided by giving each apartment its own
copy of the global data. You don t have to worry about apartment model threading as it
happens automatically with VFP COM DLLs.

Craig Berntson, 2005


Page 8 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Creating a COM+ Component


Now it s time to look at how to create a COM+ component. Technically, you could use
the DLL created earlier, but it will have no connection to its context. The code in Listing
3 builds on the DLL by adding some code to interact with its context.
1. Create a new project called FoxCOMPlus
Add a new program file named MathClass.prg. Enter the code in Listing 3 into the editor.

Listing 3. A simple COM+ component

DEFINE CLASS Math AS SESSION OLEPUBLIC


nNumber1 = 0
nNumber2 = 0

FUNCTION Add(tnOne AS Integer, tnTwo AS Integer) AS Integer


LOCAL loMtx, loContext, lnResult

loMtx = CREATEOBJECT("MTXAS.APPSERVER.1")
loContext = loMtx.GetObjectContext()

WITH This
IF PARAMETERS() = 2
.nNumber1 = tnOne
.nNumber2 = tnTwo
ENDIF

lnResult = .nNumber1 + .nNumber2


ENDWITH

loContext.SetComplete()
loContext = NULL
loMtx = NULL

RETURN lnResult
ENDFUNC

*****************

FUNCTION Multiply(tnOne AS Integer, tnTwo AS Integer) AS Integer


LOCAL loMtx, loContext, lnResult

loMtx = CREATEOBJECT("MTXAS.APPSERVER.1")
loContext = loMtx.GetObjectContext()

WITH This
IF PARAMETERS() = 2
.nNumber1 = tnOne
.nNumber2 = tnTwo
ENDIF

lnResult = .nNumber1 * .nNumber2


ENDWITH

loContext.SetComplete()
loContext = NULL
loMtx = NULL

Craig Berntson, 2005


Page 9 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Installing the component in the COM+ runtime


COM+ organizes components into applications. The following steps walk you through
creating the COM+ and application installing the component into the new application.
1. From Windows Administrative Tools, select Component Services. The Component Services
Manger is displayed (Figure 4).

Craig Berntson, 2005


Page 10 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 4. The Component Services Manager is used to administer COM+ applications.

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).

Craig Berntson, 2005


Page 11 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 5. Add a new COM+ application with the Install Wizard

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.

Craig Berntson, 2005


Page 12 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Craig Berntson, 2005


Page 13 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 7. COM+ applications can be set to run under a specific user account.

Craig Berntson, 2005


Page 14 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Craig Berntson, 2005


Page 15 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 9. Use the Install new components page to specify the DLLs to add to the application.

Click Finish to complete the installation.

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.

Craig Berntson, 2005


Page 16 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 10. After adding a component you can manage it using the Component Services Manager.

Just In Time Activation and Statelessness


When you instantiate the component, COM+ uses Just In Time Activation (JITA) to load
and create the component. The CREATEOBJECT() function never really instantiates
the component. Instead, it connects to the COM+ runtime, which in turn, instantiates the
component, through the use of a proxy. When SetComplete() or SetAbort() is called,
COM+ releases the component, but the proxy remains. Your application does not know
that the component no longer exists. The COM+ runtime activates the component on
the next call. In fact, the Init method of the component will be called again because the
component has been instantiated again. You may notice that the second call to the
component takes less time. That is because JITA keeps a copy of the component in
memory for a period of time after you re done using it. The default time is 15 minutes,
but can be changed on the Pooling & Recycling page (Figure 11) of the application s
Property dialog in the Component Services Manager.

Craig Berntson, 2005


Page 17 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 11. The application timeout is set on the application's property sheet Pooling and Recycling page.

The following steps walk show how JITA works.


1. Create a new project named JITA.pjx.
Add a new program using the code in Listing 4.

Listing 4. Code showing Just in Time Activation

#DEFINE CRLF CHR(13) + CHR(10)

DEFINE CLASS JITA AS Session OLEPUBLIC


oCom = NULL
cFile = "c:\complus\log.txt"
cProp = "Original Val"

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

Craig Berntson, 2005


Page 18 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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 JITA.prg.


Compile the project as a Multi-threaded COM DLL.
Add the new DLL to the My COM+ App application in the Component Services Manager.
Create a new program. Use the code in Listing 5. Do not include this program in the project.

Listing 5. Activating and using the Just in Time Activation component.

SET SAFETY off


ERASE Log.txt
CLEAR
ox = CREATEOBJECT("jita.jita")
ox.SetProp("New Value")
ox.GetProp()
ox = NULL
MODIFY FILE Log.txt

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.

To better understand this JITA and statelessness, modify JITAExample.prg and


comment out the SetComplete() line in the SetProp() method. Compile the DLL, then
rerun TestJITA. You will see that the value of cProp has its new value and that the Init
and Destroy methods were not called until GetProp() was called. At this point you may
be thinking that it may be better to not call SetComplete() until the end of your
application. I advise you not to do this as it hurts scalability, one of the biggest features
of COM+.

Craig Berntson, 2005


Page 19 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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

Craig Berntson, 2005


Page 20 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

The following steps show how to setup declarative security.


1. Create a new Windows Security group named COM+ Test Group.
Add your Windows login to this group.
In the Component Services Manager, expand the node under My COM+ App.
Right click on Roles and select New Role from the context menu. Enter Test Role for the role name, then
click OK to save the role.
Expand the Test Role node, right click on Users and select New User.
right click on the My COM+ App application and select Properties from the context menu.
Select the Security page on the properties dialog.

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.

A complete transaction must follow four rules, called ACIDity:

Atomicity: This means the entire transaction commits or nothing commits.


Consistency: The transaction is a correct transformation of the system state

Craig Berntson, 2005


Page 21 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Isolation: Transactions are protected by seeing each other s partial or


uncommitted results
Durability: Committed updates to managed resources can survive failures. For
example, if the server should lose power before all the tables have been updated,
the partial updates are automatically rolled back.

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:

Updates to multiple databases. For example, a single transaction can span


multiple databases, even from multiple vendors.
Support for major databases such as SQL Server and Oracle.
Internet capable through TIP (Transaction Internet Protocol), a protocol
developed by Microsoft, Compaq, Hitachi, and other companies.
Bring Your Own Transaction (BYOT). This is supported via Compensating
Resource Managers and TIP.

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.

Craig Berntson, 2005


Page 22 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

SetDeactivateOnReturn Sets the Done bit


GetDeactivateOnReturn Gets the value of the Done bit
SetMyTransactionVote Sets the Consistency bit
GetMyTransactionVote Gets the value of the Consistency bit

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:

Requires a transaction The component must run under a transaction. If there is


not currently a transaction, a new one is started.
Requires a new transaction The component must run under a transaction. A
new one is always started.
Supports transactions The component will use a transaction if one is in
progress, but it not, a new one is not started.
Does not support transactions The component doesn t care if a transaction is
running or not.

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..

Craig Berntson, 2005


Page 23 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

CREATE CURSOR cXact (Fname C(10), LName C(20), Address C(40), ;


City C(15), State C(2), Zip C(5), Country C(15), Phone C(12))
SELECT cXact
APPEND BLANK

The Save button calls the SaveData method (Listing 7) of the form.
Listing 7. The SaveData() method code from the Transaction form.

LOCAL lcXML, loBiz, llRetVal, lcMessage

CURSORTOXML("cxAct", "lcXML", 1, 0, 0, "1")


loBiz = CREATEOBJECT("xActBiz.xActBiz")
llRetVal = loBiz.InsertData(lcXML)

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.

Craig Berntson, 2005


Page 24 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

DEFINE CLASS xActBiz AS Session OLEPUBLIC


oMtx = NULL
oContext = NULL
oContextState = NULL

PROCEDURE Init
* Create a reference to the MTS object
This.oMtx = CREATEOBJECT("MTXAS.APPSERVER.1")

* Create a reference to the Context object


This.oContext = This.oMtx.GetObjectContext()

* Get an interface to the Context state


This.oContextState = GETINTERFACE(This.oContext, "iContextState")
ENDPROC

***********************

PROCEDURE InsertData(tcData AS String) AS String


LOCAL loPubsData, loNWData, lnPubsRetVal, ;
lnNWRetVal, llRetVal

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)

IF lnPubsRetVal > 0 AND lnNWRetVal > 0


This.oContextState.SetMyTransactionVote(0)
llRetVal = .T.
ELSE
This.oContextState.SetMyTransactionVote(1)
llRetVal = .F.
ENDIF

* Handle activation setting (Done)


* .T. = Deactivate, .F. = Leave activated
This.oContextState.SetDeactivateOnReturn(.T.)

RETURN llRetVal
ENDPROC

********************

PROCEDURE Error(tnError, tcMethod, tnLine)


This.oContextState.SetMyTransactionVote(1)
This.oContextState.SetDeactivateOnReturn(.T.)

Craig Berntson, 2005


Page 25 of 49
Using Windows Component Services (COM+) with Visual FoxPro

COMRETURNERROR(tcMethod, "Error: " + ALLTRIM(STR(tnError)) ;


+ ", Line: " + ALLTRIM(STR(tnLine)))
ENDPROC
ENDDEFINE

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.

DEFINE CLASS PubsData AS Session OLEPUBLIC


oMtx = NULL
oContext = NULL
oContextState = NULL

PROCEDURE Init
* Create a reference to the MTS object
This.oMtx = CREATEOBJECT("MTXAS.APPSERVER.1")

* Create a reference to the Context object


This.oContext = This.oMtx.GetObjectContext()

* Get an interface to the Context state


This.oContextState = GETINTERFACE(This.oContext, "iContextState")
ENDPROC

***********************

PROCEDURE InsertAuthor(tcXML AS String, toContextState) AS Boolean


LOCAL lnHandle, lcSQL, lnRetVal

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 + "', '" ;

Craig Berntson, 2005


Page 26 of 49
Using Windows Component Services (COM+) with Visual FoxPro

+ cAuthors.Phone + "', '" + cAuthors.Address + "', '" ;


+ cAuthors.City + "', '" + cAuthors.State + "', '" ;
+ cAuthors.Zip + "', 1)"

lnHandle = SQLSTRINGCONNECT("driver=SQL Server; ;


+ server=(local);database=pubs")
lnRetVal = SQLEXEC(lnHandle, lcSQL)
SQLDISCONNECT(lnHandle)

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

*********************

PROCEDURE Error(tnError, tcMethod, tnLine)


IF !ISNULL(This.oContextState)
This.oContextState.SetMyTransactionVote(1)
ENDIF

COMRETURNERROR(tcMethod, "Error: " + ALLTRIM(STR(tnError)) ;


+ ", Line: " + ALLTRIM(STR(tnLine)))
ENDPROC
ENDDEFINE

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.

DEFINE CLASS NWData AS Session OLEPUBLIC


oMtx = NULL
oContext = NULL
oContextState = NULL

PROCEDURE Init
* Create a reference to the MTS object
This.oMtx = CREATEOBJECT("MTXAS.APPSERVER.1")

Craig Berntson, 2005


Page 27 of 49
Using Windows Component Services (COM+) with Visual FoxPro

* Create a reference to the Context object


This.oContext = This.oMtx.GetObjectContext()

* Get an interface to the Context state


This.oContextState = GETINTERFACE(This.oContext, "iContextState")
ENDPROC

***********************

PROCEDURE InsertEmployees(tcXML AS String) AS Boolean


LOCAL lnHandle, lcSQL, lnRetVal

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 + "')"

lnHandle = SQLSTRINGCONNECT("driver=SQL Server; ;


+ server=(local);database=northwind")
lnRetVal = SQLEXEC(lnHandle, lcSQL)
SQLDISCONNECT(lnHandle)

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

*********************

PROCEDURE Error(tnError, tcMethod, tnLine)


IF !ISNULL(This.oContextState)
This.oContextState.SetMyTransactionVote(1)
ENDIF

COMRETURNERROR(tcMethod, "Error: " + ALLTRIM(STR(tnError)) ;


+ ", Line: " + ALLTRIM(STR(tnLine)))
ENDPROC
ENDDEFINE

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

Craig Berntson, 2005


Page 28 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Select Required setting for Transaction support.


Click OK to save the changes and close the Properties dialog
Repeat this step for the other two components.

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.

Craig Berntson, 2005


Page 29 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Compensating Resource Managers


The COM+ transactions that you have seen up until now require a Resource Manager
(RM) to handle the transaction. This RM is tightly integrated into the Distributed
Transaction Coordinator. It turns out that RMs are difficult to write and not all data
stores are supported. For example, what if you want to transactionally handle the
deleting of files? There is no RM to support this. Like wise, there is no RM to support
Visual FoxPro data.

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.

Craig Berntson, 2005


Page 30 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 15. This figure shows the flow of information in a Compensating Resource Manager.

The following steps explain what happens when using a CRM.


1. The application instantiates the business object. This business object is running inside COM+.
2. The business object instantiates the CRM Worker.
3. The CRM Worker instantiates the Log Control. The Log Control is part of the COM+ runtime.
The name of the COM server for the Compensator is passed as a parameter to the Log
Controller.
The business object calls methods of the COM+ Context object to either commit or abort the transaction.
The COM+ runtime calls the Log Controller to commit or abort.
Because the CRM Compensator is hooked into the Log Controller events, it is automatically called and
handles the actual commit or abort.

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.

Creating the CRM User Interface


Modify the form that you have been using in previous examples and save it as CRM.frx.
Figure 16 shows the modified form.

Craig Berntson, 2005


Page 31 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 16. The Compensating Resource Manager form.

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.

CREATE CURSOR cXact (Fname C(10), LName C(20), Address C(40), ;


City C(15), State C(2), Zip C(5), Country C(15), Phone C(12))
SELECT cXact
APPEND BLANK

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.

LOCAL lcXML, llRetVal

CURSORTOXML("cxAct", "lcXML", 1, 0, 0, "1")


IF TYPE("ThisForm.oBiz") != "O" OR ISNULL(ThisForm.oBiz)
ThisForm.oBiz = CREATEOBJECT("CRMBiz.sesBiz")
ENDIF

Craig Berntson, 2005


Page 32 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

The CRM Business Object


The CRM business object is similar to previous business objects I have used in this
document. It passes the data onto the data object, which in this case is the CRM
Worker. Create a new project called CRMBiz and a new program file named
CRMBiz.prg. The code in Listing 13 should be added to the new program file.
Listing 13. The CRM Business object.

DEFINE CLASS sesBiz AS session OLEPUBLIC

PROTECTED oMTX, oContext

oMTX = NULL
oContext = NULL

*********************

PROTECTED PROCEDURE Cleanup


This.oContext = null
This.oMTX = null
ENDPROC

*********************

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

Craig Berntson, 2005


Page 33 of 49
Using Windows Component Services (COM+) with Visual FoxPro

*********************

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.

The CRM Worker


Here s where things start to get complicated, but follow along and you should be fine.
Create a new project called CRMData and add the code in Listing 14 as a new program
file, CRMWorker.prg.
Listing 14. The CRM Worker.

DEFINE CLASS sesWorker AS session OLEPUBLIC

PROTECTED oCrmLogControl

oCrmLogControl = ""

*********************

PROTECTED PROCEDURE RegisterCRMCompensator


LOCAL lcProgIdCompensator, lcDescription, lnErr, llRetVal

llRetVal = .T.
lcProgIdCompensator = "CRMData.sesCompensator"
lcDescription = "Sample VFP CRM"

IF VARTYPE(This.oCrmLogControl) != "O"
This.oCrmLogControl = CREATEOBJECTEX("CrmClerk.CrmClerk.1", "", "")
ENDIF

TRY

Craig Berntson, 2005


Page 34 of 49
Using Windows Component Services (COM+) with Visual FoxPro

FOR lnI = 1 TO 10
lnErr = This.oCrmLogControl.RegisterCompensator( ;
lcProgIdCompensator, lcDescription, 7)
ENDFOR

CATCH TO loErr
llRetVal = .F.
FINALLY
ENDTRY
RETURN llRetVal
ENDPROC

****************************

PROTECTED PROCEDURE RegisterCRMLog(tcXML)


LOCAL laLogRecord[1], llRetVal

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)

* Write out the log record -- array of variants


This.oCrmLogControl.WriteLogRecordVariants(@laLogRecord)

* Write out log


This.oCrmLogControl.ForceLog()

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

Craig Berntson, 2005


Page 35 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

The CRM Compensator


The last piece of code is the CRM compensator. Add a new program file,
CRMComp.prg (see) to the CRMData project.
Listing 15. The CRM Compensator

EXTERNAL ARRAY pLogRecord


DEFINE CLASS sesCompensator AS session OLEPUBLIC
PROTECTED oCrmLogControl

oCrmLogControl = NULL

IMPLEMENTS ICrmCompensatorVariants IN "comsvcs.dll"

PROCEDURE ICrmCompensatorVariants_SetLogControlVariants(pLogControl ;
AS VARIANT) AS VOID ;
HELPSTRING "method SetLogControlVariants"

LOCAL lcTransactionUOW
This.oCrmLogControl = pLogControl
ENDPROC

Craig Berntson, 2005


Page 36 of 49
Using Windows Component Services (COM+) with Visual FoxPro

***************************

PROCEDURE ICrmCompensatorVariants_PrepareRecordVariants(pLogRecord ;
AS VARIANT) AS LOGICAL ;
HELPSTRING "method PrepareRecordVariants"

* Setting This to .T. causes CommitRecordVariants not to get called


RETURN .F.
ENDPROC

***************************

PROCEDURE ICrmCompensatorVariants_BeginPrepareVariants() AS VOID;


HELPSTRING "method BeginPrepareVariants"
ENDPROC

***************************

PROCEDURE ICrmCompensatorVariants_EndPrepareVariants() AS LOGICAL;


HELPSTRING "method EndPrepareVariants"

* Vote on the outcome of the transaction. Return .F. to rollback


RETURN .T.
ENDPROC

***************************

PROCEDURE ICrmCompensatorVariants_AbortRecordVariants(pLogRecord ;
AS VARIANT) AS LOGICAL ;
HELPSTRING "method AbortRecordVariants"

* Code for aborting the transaction goes here


RETURN .T.
ENDPROC

***************************

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.

Craig Berntson, 2005


Page 37 of 49
Using Windows Component Services (COM+) with Visual FoxPro

FINALLY
CLOSE DATABASES ALL
ENDTRY
RETURN llRetVal
ENDPROC

***************************

PROCEDURE ICrmCompensatorVariants_BeginCommitVariants(bRecovery ;
AS LOGICAL) AS VOID ;
HELPSTRING "method BeginCommitVariants"
ENDPROC

***************************

PROCEDURE ICrmCompensatorVariants_EndCommitVariants() AS VOID;


HELPSTRING "method EndCommitVariants"
ENDPROC

***************************

PROCEDURE ICrmCompensatorVariants_BeginAbortVariants(bRecovery ;
AS LOGICAL) AS VOID;
HELPSTRING "method BeginAbortVariants"
ENDPROC

***************************

PROCEDURE ICrmCompensatorVariants_EndAbortVariants() AS VOID;


HELPSTRING "method EndAbortVariants"
ENDPROC
ENDDEFINE

As you see, this code implements the ICompensatorVariants interface in COMSvcs.dll.


There are three methods of interest here.

The first is ICrmCompensatorVariants_AbortRecordVariants(). The return value from


this method determines whether other databases in the transaction abort or commit.

The second, ICrmCompensatorVariants_AbortRecordVariants() is where you place


code to run when an abort occurs. In my example, there is nothing to do, so no code is
present.

Finally, ICrmCompensatorVariants_CommitRecordVariants() is where the action is. The


code gets the XML data from the array that was passed in from the CRM Worker. The
XML is converted to a cursor, then used to update the Customers table.

Save the code and compile the CRMData project as a Multi-threaded COM DLL.

Craig Berntson, 2005


Page 38 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Setting up the CRM in COM+


Now that you have the components, you still need to set them up in COM+ and test
them. The following steps walk you through doing this.
1. Create a new COM+ application called VFPCRM.
2. Add both CRMBiz.dll and CRMData.dll as new components in the application.
3. On the Advanced tab (see Figure 17) of the properties dialog for VFPCRM application, check
Enable compensating resource managers.

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.

Craig Berntson, 2005


Page 39 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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 provide several advantages to application design. For example,


by using Queued Components, you can minimize the effects of a workstation or server
being disconnected from the network or you can use them to balance the processing
needs across the day.

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.

Craig Berntson, 2005


Page 40 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Craig Berntson, 2005


Page 41 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 20. A close-up view of how Queued Components work.

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.

Craig Berntson, 2005


Page 42 of 49
Using Windows Component Services (COM+) with Visual FoxPro

Figure 21. Message Queuing is managed in the Computer Management snap-in.

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.

Listing 16. The SaveData() method of the Queued Component form.

LOCAL lcXML, loCatalog, loBiz

CURSORTOXML("cXact", "lcXML", 1, 0, 0, "1")

loCatalog = CREATEOBJECT("COMAdmin.COMAdminCatalog")
loCatalog.Connect("")
loCatalog.StartApplication("QC1")
loBiz = GetObject("queue:/new:FoxQC.sesQC")

loBiz.InsertData(lcXML)

loCatalog.ShutdownApplication("QC1")

Close and save changes to the form.

Craig Berntson, 2005


Page 43 of 49
Using Windows Component Services (COM+) with Visual FoxPro

The following steps walk you through creating the component.


1. Create a new project called FoxQC.
Add a new program to the project. Use the code in Listing 17.

Listing 17. This code is used to create a Queued Component.

DEFINE CLASS sesQC AS Session OLEPUBLIC


PROCEDURE InsertData(tcXML)
XMLTOCURSOR(tcXML, "cCustomer")

SET EXCLUSIVE OFF


USE Data\Customers IN 0 SHARED
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)
CLOSE DATABASES ALL
ENDPROC
ENDDEFINE

Save the code as QC.prg.


Compile the project as a Multi-threaded COM DLL.

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:

Pessimistic This is the same methods you ve seen previously, using


synchronous components.

Craig Berntson, 2005


Page 44 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Loosely Coupled Events


Like Queued Components, Loosely Coupled Events allow asynchronous calls. The
same issues of getting a message back exist. The big difference is that Loosely
Coupled Events use a Publish and Subscribe method where one component publishes
events and one or more components subscribe to those events. The COM+
infrastructure handles the publish and subscribe processes and delivers the event
messages from the publisher to the subscriber. There are two types of subscriptions,
persistent and transient.

Under persistent subscriptions, administration is handled through the Component


Services Manager. Subscribers are not created until the event is published and a single
published event can cause multiple subscribers to respond. You can also have filtering
conditions so that a subscriber is only sent events that meet specific criteria.
Additionally, persistent subscriptions will survive system shutdown.

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.

LOCAL lcXML, loPublisher

CURSORTOXML("cxAct", "lcXML", 1, 0, 0, "1")


loPublisher = CREATEOBJECT("PersistPub.sesPublisher")
loPublisher.InsertData(lcXML)
loPublisher = NULL

Craig Berntson, 2005


Page 45 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

DEFINE CLASS sesPublisher AS Session OLEPUBLIC


PROCEDURE InsertData(tcData AS String)
ENDPROC
ENDDEFINE

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.

DEFINE CLASS sesSubscriber AS Session OLEPUBLIC


IMPLEMENTS IsesPublisher IN PersistPub.sesPublisher

PROCEDURE IsesPublisher_InsertData(tcData AS String)


XMLTOCURSOR(tcData, "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)
CLOSE DATABASES ALL
ENDPROC
ENDDEFINE

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.

Craig Berntson, 2005


Page 46 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Craig Berntson, 2005


Page 47 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Craig Berntson, 2005


Page 48 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Craig Berntson, 2005


Page 49 of 49
Using Windows Component Services (COM+) with Visual FoxPro

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.

Craig Berntson, 2005


Page 50 of 49
This document was created with Win2PDF available at http://www.daneprairie.com.
The unregistered version of Win2PDF is for evaluation or non-commercial use only.

Вам также может понравиться