VFP COM+ Services Samples

 

Introduction

COM+ Transactions

COM+ Events:

·         Persistent Subscriptions

·         Transient Subscriptions

Queued Components

Compensating Resource Managers

 

Introduction

Windows 2000 introduces a new set of component services (COM+ Services) that extend the ability to create rich distributed transactional solutions for your Windows or web applications. Visual FoxPro 7.0 includes new features to improve its COM Server story to better support many of these great services. These features include the ability to implement an interface as well as reference objects that don’t necessarily have an IDispatch interface.

 

Some of the COM+ Services such as Transactions (formerly known as Microsoft Transaction Server) existed prior to Windows 2000 and were supported by Visual FoxPro. New services such as COM+ Events and Queued Components offer new options for your applications that weren’t previously available in Visual FoxPro. For more information on COM+ Services, see the COM+ (Components Services) section of the Platform SDK http://msdn.microsoft.com/developer/sdk/platform.asp.

 

The samples below are very simple and merely meant to show basic concepts of using a specific service. Use of these services in real world applications will typically require better data validation and error handling. You should be familiar with the basics of using COM+ Applications such as creating a new application (formerly known as MTS package), adding new components, and setting transaction attributes. Instructional details on using COM+ Services are available in the Platform SDK.

COM+ Transactions

Microsoft Transaction Server introduced a great story for building distributed transactional applications. With VFP6 SP3, you could create highly scalable applications using VFP MTDLL servers. This example shows transactions using a VFP database containing remote views to SQL Server. Because SQL Server data supports OLE Transactions, its actions are processed by the Microsoft Distributed Transaction Coordinator (MS DTC). So, all data inserts, updates and deletes will automatically be rolled back if the transaction is aborted. Since VFP data does not support OLE Transactions, its data is not automatically rolled back with an aborted transaction. However, you can use Compensating Resource Managers (see below) to achieve the same outcome. This example also includes a component that sends a message to an MSMQ queue. Since MSMQ queues can be transactional via the MS DTC, messages sent to a queue will be removed if the transaction is aborted.

Running the sample:

  1. You will first need to upsize your Testdata.DBC database in \Samples\Data to your SQL Server Pubs database. You can use the Upsizing Wizard to do this. Once done, open up the Testdata.DBC in the \Samples\COM+\Transactions folder and change the MTXPubs connection so that it points to your Pubs database. The remote views in this DBC should then open correctly.
  2. Change #DEFINES in FOXTXN.H which have paths in them.
  3. Build the DLL as a multi-threaded DLL.
  4. Create a COM+ Application (any name is fine) and add all 4 components. Register all but ORDER to Supports Transactions. The ORDER component should be marked as Requires Transaction. Note: if you build your DLL with Windows 95/98, make sure to select both DLL and TLB files when adding VFP components to the COM+ application.
  5. If you have MSMQ running, you can optionally create a private queue to include as another part of the distributed application (see MSMQ_QUEUE in FOXTXN.H for name to use). The sample does not require MSMQ in order to run. You can also comment out lines in ORDER.PRG.
  6. Run the CustOrder form (it is the base client for this application). Choose a customer from the dropdown at top. You can then pick items to add to the order from the products dropdown below. The Add button adds new items. When you are ready to process the entire order, which could contain multiple products, click on the Process button. A message box will appear when the entire order is completed (remember, there are 4 VFP COM Components to process things).
    1. You can test a bad customer by selecting a customer with a Max Order Amount less than $5000. If this is the case, the transaction will abort (including all actions). You can view MSMQ queue to see if message arrived or didn’t (due to abort). Also, the Products form shows current product amounts in database.
    2. You can test bad product (another abort scenario) by ordering a quantity greater than what is available (see Products form for quantity in stock).

Behind the scenes:

The example is fairly complex in that it uses 4 VFP COM Components (OLEPUBLICs). The ORDER component is actually the first one called and it initiates the transaction. The first component it calls is the MSMQ one (if setup). A message is sent to an MSMQ Queue containing the details of the order. When the MSMQ component is finished, it calls SetComplete() to commit its part of the transaction and deactivate itself. The ORDER component next calls the CUSTOMER component. If the customer is valid (maxordamt > $5000) then the order amount is debited from account and SetComplete() is called. If invalid customer, then SetAbort() is called and entire transaction including MSMQ message is rolled back. Finally, if the order is still intact, the ORDER component calls the PRODUCT component to debit quantity amounts. Again, if not enough quantity of an item is in stock, the entire transaction is rolled back including the MSMQ message and any PRODUCT data edits.

 

One of the enhancements added to transaction support in the new COM+ Services is the ability to separate the transaction and activation functionality provided by SetComplete()/SetAbort(). With MTS, calling SetComplete() not only committed your transaction, it also deactivated the component. There are many scenarios, however, where you would want to set a particular transaction state in a method calls of an object and possibly change this in a subsequent method call. With MTS, you needed to provide fancy error trapping sorts of routines to accomplish such behavior. COM+ now provides a new interface called IContextState which allows for more granular control of Consistency and Doneness. The following code snippet can be used in your COM+ Applications to obtain reference to the ContextState object and make these settings. Notice the use of new GetInterface() function.

 

      LOCAL oMTX, oContext, oContextState

      LOCAL lTxnState, lGetTxnState, lDone, lGetDone

      lGetDone = .F.     && initialize setting

      lGetTxnState = 0  && initialize setting

 

      oMTX = CREATEOBJECT("MTXAS.APPSERVER.1")

      oContext = oMTX.GetObjectContext()

      oContextState = GetInterface(oContext,"IContextState")

 

      * Handle activation setting (Doneness)

      * Values: .T. - Deactivate, .F. - Leave activated

      lDone = .T.

      oContextState.SetDeactivateOnReturn(lDone)

      oContextState.GetDeactivateOnReturn(@lGetDone)

     

      * Handle transaction setting (Consistency)

      * Values: 0 - commit, 1 - abort

      lTxnState = 1

      oContextState.SetMyTransactionVote(lTxnState)

      oContextState.GetMyTransactionVote(@lGetTxnState)

 

COM+ Events

These samples show how to use the new COM+ Events (also known as Loosely Coupled Events or LCE) model with VFP 7.0 servers. With COM+ Events, you have two options for creating events.

 

Persistent Subscriptions. With Persistent Subscriptions, you have a publish/subscribe relationship where multiple clients can subscribe to a single published Event. The key word here with Persistent Subscriptions is "class". Objects do not need to exist for events to occur since they are handled by the COM+ Event System managed by the operating system. Subscriber objects are created (and subsequently destroyed) only when an event is triggered. A single event can trigger multiple Subscriptions. Additionally, you can set up filtering conditions so that a Subscriber event is only triggered when a particular condition is met. Persistent Subscriptions are stored in the COM+ catalog and survive system shutdowns.

 

Transient Susbscriptions. Rather than being tied to classes, Transient Subscriptions work with existing objects. They are also stored in the COM+ catalog, but they do not survive a system shutdown. In addition, you cannot have multiple Subscriber objects receiving the event. Transient Subscriptions offer the advantage of having objects always being live, so performance is maximized.

 

The COM+ Component Services MMC snap-in allows you to manage Persistent Subscriptions through a nice user-interface. With Transient Subscriptions, you need to set up events using administrative API routines. The Transient Subscriptions example below shows how to do this.

 

The samples below show a simple Book Company that needs to perform two actions:

 

 

Both of these actions are considered events in the COM+ Events system. An Event is set up in the system merely to establish the specific methods (and parameters) for Subscribers to use. The Event is simply a VFP COM Server with a class definition containing no code (i.e., Interface). Subscribers need to be able to IMPLEMENT that interface so that specific code can be written to handle events. With VFP 7.0, your COM Servers can IMPLEMENT the Event interface.

Persistent Subscription Example

The Persistent Subscription sample shows setup and creation of a common COM+ Event using VFP7 Servers. Much of the work involves using the Component Services MMC (Microsoft Management Console) similarly to how you may have worked with Microsoft Transaction Server packages.

 

  1. Create and Set Up Event Class:

 

    1. The FOXBOOK_PUB.PJX project in the \Samples\COM+\Events folder can be used to build the VFP MTDLL COM Server used for the event class. If you look at the BOOK_PUB.PRG file, you will see a simple OLEPUBLIC class definition with two methods (NewBook and PriceChange). These methods correspond to the two actions performed by Book Company (see above). Build a multi-threaded DLL from this project (FOXBOOK_PUB.DLL). Important: make sure to update constants in the EVENTS.H file as needed.
    2. Create a new COM+ Application named FoxBookPub.
    3. Now, expand the new FoxBookPub node, select the Components node, right-click on New->Component menu item to bring up Component Wizard:

·         Click on Install new event class(es) button,

·         Select the FOXBOOK_PUB.DLL file,

·         Click Next buttons until done

    1. The event class work is now complete, so you can close the FOXBOOK_PUB project.

 

  1. Create and Set Up a Subscriber:

 

    1. The FOXBOOK_SUB.PJX project can be used to build the VFP MTDLL COM Server used for the Subscriber. If you look at the BOOK_SUB1.PRG file, you will see the same class definition as that of the Event class. However, it uses the IMPLEMENTS keyword to implement the interface of the Event class. Here is where you put your code to handle events triggered by COM+ Events System. Build a multi-threaded DLL from this project (FOXBOOK_SUB.DLL).
    2. Create a new COM+ Application and call it FoxBookSub.
    3. Create a new Component, however, instead of selecting the Install new event class button, select Install new component (and pick the FOXBOOK_SUB.DLL file).
    4. Now comes the important part: creating the Subscription. Expand the Components node to expose the new VFP component. Expand that node and right-click on the Subscriptions node to select New->Subscriptions menu item. In the dialog:

·         On the second page, select the check box at bottom labeled ‘Use all interfaces for this component’ and click the Next button,

·         Click on the FOXBOOK_PUB.BOOKPUB ProgID that appears in the list box,

·         Click Next and enter a name for subscription (e.g., Fox Subscription 1),

·         Click on Enable this subscription immediately checkbox and Next/Finish buttons until done

    1. The first Subscription is now set up. You can set up multiple Subscriptions if you like (one of the nice advantages of Persistent Subscriptions).

         

  1. Running the Application

 

You are now ready to try out your Persistent Subscription. Run the BOOKS.SCX form (in FOXBOOK_CLIENT project) which uses a sample BOOKS.DBF table. You can click on the New Book button to bring up a dialog for adding a new book. When you do this and click OK, a record is inserted into the BOOKS table and the NewPrice method on the Event is called. This causes an event to be triggered for all Subscribers. In our example, the NewBook event code in the Subscriber writes out an entry to the audit log. You can click on the Audit Log button to see this. In addition, you can see the active objects in the Component Services MMC window. The PriceChange event works the same way. Take a look at the code in these forms to see how event gets triggered.

 

COM+ Events lets you control a number of factors related to events that may be of interest in your application. At the Publisher end, you can open up the Properties dialog in Component Services window. The Advanced tab has several options under the LCE frame including ability to allow in-process Subscribers. Perhaps most interesting is ability to add Parameter Filters for Subscribers. If you open a Subscriber's properties dialog, the Options tab has a text box called Filter Criteria. You can enter a valid condition string here to control whether an event fires for a Subscriber. For example, you may have a Filter set for a specific book (e.g., cBookName="Biking Across the Road"). A Subscriber object is only created if criteria for the filter are met.

Transient Subscription Example

As mentioned earlier, Transient Subscriptions cannot be created using the COM+ Component Services Explorer. Setup is handled via the COM+ admin objects. The Publisher and Subscriber are created only when needed. Although Transient Subscriptions are stored in the COM+ catalog, they do not survive a system shutdown. In fact, since this information is not persisted, your Subscribers do not need to be VFP COM servers.

 

Before you begin, make sure you update any necessary constants in EVENTS.H. Also, the FOXBOOKS_PUB.DLL file needs to be created (this was done automatically with Persistent Subscriptions sample above). Because there is no setup required for Transient Subscriptions, you can run the sample now. Launch the TCE_EVENTS.SCX form (in FOXBOOK_CLIENT project). There are several sections on this form. Click the Create button to create a COM+ Event . At this point, we have created a new PROGID which you can test in the Command Window (this is one stored in EVENTS.H file -- "Books.Publisher"). Type oPub = CreateObject("Books.Publisher") and then try using Intellisense in the Command Window typing oPub. . At this point, we have only created an Event, so no action occurs if you enter something like: oPub.PriceChange("Foo",100).

 

Next click on the Subscribe button. We now have a Subscriber set up to handle the Event. This Subscriber object is an instance of the class definition in the BOOK_TCE.PRG file. Notice that this class IMPLEMENTS the same bookpub interface used in the Persistent Subscription example. And the action of the user events is to write out an entry to the audit log file.

 

We can now test our Transient Subscription sample by running the BOOKS client form again and clicking on the Change Price... button. Make sure to select the TCE checkbox. If you view the audit file, you will see a new TCE entry.

 

Queued Components

Queued Components (QC), a key feature of COM+ and based on Message Queuing Services (MSMQ), provides an easy way to invoke and execute components asynchronously. Processing can occur without regard to the availability or accessibility of either the sender or receiver. A shop-at-home television network is an example of asynchronous processing. Viewers call in their purchases and the information is taken by order processors — who may or may not be connected to the server. In an asynchronous, disconnected scenario, the orders are taken as quickly as they can be phoned in and are queued for later retrieval and processing by the server.

 

The sample below is a simple Pizza Order system in which customer name and pizza order is made to a VFP COM server running as a Queued Component. The calls are made and the order is sent to a simple Order text file. Because they are Queued, the orders are made asynchronously, so many can be taken at once. Depending on how the QC application is setup, you can have the orders simply recorded (where no Order text file is created) and played back later.

Running the sample:

  1. Before you begin, ensure that you have MSMQ running on your machine otherwise you will be unable to create the necessary MSMQ queues needed for Queued Components. You can check this by opening the Computer Management administrative tool. If enabled, you will see a Message Queuing item under the Services and Applications group.
  2. In the \Samples\COM+\QC folder, build a MTDLL COM server from the FoxQC project. 
  3. Create a new COM+ Application. You should name it QC1 (otherwise you need to change the #DEFINE in FOXQC.H and rebuild DLL).
  4. Open the Properties dialog for the QC1 application and make the following changes:
    1. In the Securities tab, uncheck the "Enforce access checks…" checkbox.
    2. In the Securities tab, change the "Authentication level…" dropdown to None.
    3. In the Queuing tab, check the two checkboxes.

          Note: for purposes of the sample only, we are not enforcing any security settings. Production applications should always enforce strong security.

  1. Add the FoxQC DLL to this application and make sure the "Enforce component level access checks" checkbox in the Security tab of its Properties dialog is not checked.
  2. Open up the Interfaces node under the FoxQC.Pizza component and select the Ipizza interface. Open up its Properties dialog, select the Queuing tab and check the Queued checkbox.
  3. You are now ready to try the example. Run the Pizza form in the project.
    1. You should first run a simple test to ensure your component is working outside of the Queued Components environment by checking the Test… checkbox at the bottom of the form. If all functioned as expected, you should see your order in a text file.
    2. Uncheck the checkbox to test under Queued Components. Again, if the call was successful, you should see text file with the order. Additionally, you can open up the Component Services explorer and view the QC1 application still running.

Note: if you are not running from local machine, set the QC_MACHINE #DEFINE of the FOXQC.H file to your specific machine name where COM+ Application is located and recompile the Pizza form.

    1. Let's now simulate running Queued Components with Listener turned off so that calls are recorded and played back later.

·         Quit your running instance of VFP

·         In Component Services explorer, Shutdown the QC1 application if it is running.

·         In Properties dialog of QC1 application, uncheck the 2nd Listen checkbox in the Queuing tab.

·         Restart VFP and run the Pizza form. Click on the Cleanup button to delete any existing order text file.

·         Select ingredients you want for a new order and click on the Place Order button. You will not see an Order file open. You can click Refresh all you want, but it isn't there since the Queued Components listener is turned off.

·         Quit VFP again

·         In Component Services explorer, Shutdown the QC1 application if it is running.

·         In Properties dialog of QC1 application, check the 2nd Listen checkbox in the Queuing tab.

·         Restart VFP and run the Pizza form. Notice the View button is still disabled since Order text file still doesn't exist.

·         Back in the Component Services explorer, Start the QC1 application.

·         In VFP, click on the Refresh button of the Pizza form. You should now see View button enabled. Click on it to see your earlier recorded order now appear.

Behind the scenes:

This example is quite simple. Take a look at the Pizza class in FOXQC.PRG. Classes registered for Queued Components have specific requirements. These include write-only properties (see the _COMATTRIB settings), no return values for methods (see AS VOID settings) and no in/out parameters (by reference). You should also avoid passing objects as parameters or setting properties to object references. Because the server object is not available when the client makes the call, server results can be returned by sending a message that creates another object. In this way, two-way communication occurs not in every case but only when it is required, by a series of one-way messages between objects. Since actual actions are recorded in a queue, you should take precautions to ensure your code does not rely on either a specific machine (since it could be run back on a different one). Also, the code may be played back hours or even days after the original recording was made.

 

The MSMQ Workgroup configuration does not permit Queued Components to support application security. If you've installed MSMQ with the "Workgroup" configuration, you must also disable security on each Queued Application accessed in this configuration. In the Properties dialog of the COM+ Application with your Queued Component, select the Security tab and change Authentication Level for Calls to None. In this sample, we are not using transactions. However, because MSMQ supports transactions, its probably a good idea to setup your QC application using transactions (see the Transaction sample for getting an ObjectContext reference and calling SetAbort/SetComplete).

 

Open up the TESTQC.PRG file to see how clients need to make calls to a QC component. There are two important steps. First, you need to make sure the COM+ application containing the Queued Component is running. You can automate starting this using the COM Catalog Administration API routines such as in the example. The second step is to create an instance of the component using the special Queue moniker and GetObject(). Instead of CreateObject() or NewObject(), you need to go through the Queued moniker so that COM calls are recorded instead of being made immediately.

 

Compensating Resource Managers

One of the exciting new COM+ Services for VFP developers is that of Compensating Resource Managers (CRMs). A CRM provide a quick and easy way to integrate application resources with Microsoft Distributed Transaction Coordinator (MS DTC) transactions. It is an alternative to developing a full Microsoft Transaction Services resource manager. CRMs allow you to have user code participate in transactions. You can now write VFP COM servers that get triggered when a transaction is committed or aborted.

 

The sample below shows CRMs in transactions that create or delete files. One of the components of your distributed application might want to create a new file. With a CRM, you can have that file removed if the transaction is aborted. The good news for VFP developers is that with CRMs you can now have VFP data participate in transactions. For example, you can use a CRM to rollback records added to a VFP table if an abort occurs.

Running the sample:

  1. In the \Samples\COM+\crm folder, build MTDLL COM servers from both projects (crmfilesvfp and crmfilesclient).
  2. Create a new COM+ Application (e.g., CRMFILESVFP) and in the Advanced tab of its Property dialog, check the option to Enable Compensating Resource Managers.
  3. Add both DLLs to this application and set their Transaction attributes as follows:

·         CrmFilesClient.CrmFilesVFP - Required

·         CrmFilesVFP.CrmFilesCompensatorVFP – Not Supported

·         CrmFilesVFP.CrmFilesWorkerVFP – Required

  1. Important: in the Properties dialog of the Compensator class, select the Activation tab and uncheck Enable Just In Time Activation. Now select the Concurrency tab and click on the Not Supported radio button for Synchronization support.
  2. Run the CRMFILES form.
    1. If you choose to Create a file, the abort will remove it.
    2. If you choose to Delete a file, a temp backup copy is made and then the original is deleted. An abort will restore original by copying temp backup to it.
    3. The Log button shows what is happening behind the scenes in terms of which methods are being called on the CRMs.
  1. View source code.

Behind the scenes:

A CRM is provided as a pair of COM components – a CRM Worker and a CRM Compensator. The CRM Worker is responsible for doing the main work of the specific CRM. The CRM infrastructure provides an interface to the CRM Worker through which the CRM Worker can write records to a durable log file on disk. The CRM Worker must write records to the log and make them durable before it performs its work so that, if a crash occurs, recovery can occur correctly.

 

The CRM Compensator is the component that is created by the CRM infrastructure at the completion of the transaction. It implements a defined interface by which the CRM infrastructure can pass on notifications of transaction completion and the log records that were previously written by the CRM Worker. Because VFP COM servers have a single-threaded apartment model, it is important that you set the  synchronization property to “not supported” in order to avoid a deadlock in the prepare phase.

 

CRMs are intended for advanced developers since you will need to handle a variety of possible scenarios that can occur with your application including that of a crash which could occur between the time the log is written out and the actual action is taken.

 

The CRMWORKER.PRG file contains the OLEPUBLIC class used by the CRM Worker to handle writing out to log and actual action (create or delete file). Notice the CREATEOBJECTEX() call. This is used to create an instance of “CrmClerk.CrmClerk.1” which does not support an IDispatch interface. We can then write to the durable log with this object. The WriteLogRecordVariants() function, which writes out the durable log, must be passed an array which is zero-based (hence use of COMARRAY function). The CRMCOMP.PRG file contains the CRM Compensator class. Notice that it has an IMPLEMENTs clause so that it can implement the interface for the compensator events. These events allow you to handle commit or abort actions against the transaction. The pLogRecord variant parameter returned by several events contains the array written out to the log by the CRM worker. The CRM flags and sequence number are appended as the last two elements in the array (see the ICrmCompensator::PrepareRecord topic in MSDN for more details).