Using the Application Scope to improve COM performance



The Java call to create a COM object instance can take substantial time. As a result, creating COM objects in ColdFusion can be substantially slower than in ColdFusion 5. For example, on some systems, creating a Microsoft Word application object could take over one second using ColdFusion, while on the same system, the overhead of creating the Word object could be about 200 milliseconds.

Therefore, in ColdFusion, you can improve COM performance substantially if you can share a single COM object in the Application scope among all pages.

Use this technique only if the following are true:

  • The COM object need not be created for every request or session. (For session-specific objects, consider using the technique described here with the Session scope in place of the Application scope.)

  • The COM object is designed for sharing.

Because the object can be accessed from multiple pages and sessions simultaneously, also consider the following threading and locking issues:

  • For best performance, make the object multi-threaded. Otherwise, only one request can access the object at a time.

  • Lock the code that accesses and modifies common data. In general, you do not have to lock code that modifies a shared object’s data, including writable properties or file contents, if multiple requests do not share the data (as opposed to the object) . However, specific locking needs depend on the COM object’s semantics, interface, and implementation.

  • All cflock tags in the application that use an Application scope lock share one lock. Therefore, code that accesses a frequently used COM object inside an Application scope lock can become a bottleneck and reduce throughput if many users request pages that use the object. In some cases, you can avoid some contention by placing code that uses the COM object in named locks. Place the code that creates the object in an Application scope lock.

Note: You can also improve the performance of some COM objects by creating Java stubs, as described in Accessing Complex COM Objects using Java proxies. Using a Java stub does not improve performance as much as sharing the COM object, but the technique works with all COM objects. Also, generate Java stubs to correctly access complex COM objects that do not properly make all their features available through the COM IDispatcher interface. Therefore, to get the greatest performance increase and prevent possible problems, use both techniques.

Example 1: Using the FileSystem object

The following example uses the Microsoft FileSystem Scripting object in the Application scope. This code creates a user-defined function that returns a structure that consists of the drive letters and free disk space for all hard drives on the system.

<cfapplication name="comtest" clientmanagement="No" Sessionmanagement="yes"> 
 
<!--- Uncomment the following line if you must delete the object from the 
Application scope during debugging. Then restore the comments.  
This technique is faster than stopping and starting the ColdFusion server. ---> 
    <!--- <cfset structdelete(Application, "fso")> ---> 
 
<!--- The getFixedDriveSpace user-defined function returns a structure with 
the drive letters as keys and the drive's free space as data for all fixed  
drives on a system. The function does not take any arguments ---> 
 
<cffunction name="getFixedDriveSpace" returnType="struct" output=True> 
    <!--- If the FileSystemObject does not exist in the Application scope, 
    create it. ---> 
    <!--- For information on the use of initialization variables and locking in 
        this code, see "Locking application variables efficiently" in Chapter 15, 
        "Using Persistent Data and Locking" ---> 
    <cfset fso_is_initialized = False> 
    <cflock scope="application" type="readonly" timeout="120"> 
        <cfset fso_is_initialized = StructKeyExists(Application, "fso")> 
    </cflock> 
    <cfif not fso_is_initialized > 
        <cflock scope="Application" type="EXCLUSIVE" timeout="120"> 
            <cfif NOT StructKeyExists(Application, "fso")> 
                <cfobject type="COM" action="create" class="Scripting.FileSystemObject" 
                    name="Application.fso" server="\\localhost"> 
            </cfif> 
        </cflock> 
    </cfif> 
 
    <!--- Get the drives collection and loop through it to populate the 
        structure. ---> 
    <cfset drives=Application.fso.drives()> 
    <cfset driveSpace=StructNew()> 
    <cfloop collection="#drives#" item="curDrive"> 
        <!--- A DriveType of 2 indicates a fixed disk ---> 
        <cfif curDrive.DriveType IS 2> 
        <!--- Use dynamic array notation with the drive letter for the struct key 
        ---> 
            <cfset driveSpace["#curDrive.DriveLetter#"]=curDrive.availablespace> 
        </cfif> 
    </cfloop> 
    <cfreturn driveSpace> 
</cffunction> 
 
<!--- Test the function. Get the execution time for running the function ---> 
<cfset start = getTickCount()> 
<cfset DriveInfo=getFixedDriveSpace()> 
<h3>Getting fixed drive available space</h3> 
<cfoutput>Execution Time: #int(getTickCount()-start)# milliseconds</cfoutput><br><br> 
<cfdump label="Drive Free Space" var="#driveInfo#">

Example 2: Using the Microsoft Word application object

The following example uses the Microsoft Word application COM object in the Application scope to convert a Word document to HTML. This example works with Word 2000 as written. To work with Word 97, change “Val(8)” to “Val(10)”.

This example uses an Application scope lock to ensure that no other page interrupts creating the object. Once the Word object exists, the example uses a named lock to prevent simultaneous access to the file that is being converted.

<cfapplication name="comtest" clientmanagement="No" Sessionmanagement="yes"> 
<!--- Uncomment the following line if you must delete the object from the 
Application scope ---> 
<!--- <cfset structdelete(Application, "MyWordObj")> ---> 
 
<!--- use the GetTickCount function to get a current time indicator, used for 
        displaying the total processing time. ---> 
<cfset start = GetTickCount()> 
<!--- If necessary, create the Word.application object and place it in the 
        Application scope ---> 
<cfset WordObj_is_initialized = False> 
<cflock scope="application" type="readonly" timeout=120> 
    <cfset WordObj_is_initialized = StructKeyExists(application, "MyWordObj")> 
</cflock> 
<cfif not WordObj_is_initialized > 
    <cflock scope="Application" type="exclusive" timeout="120"> 
        <cfif not StructKeyExists(application, "MyWordObj")> 
 
<!--- First try to connect to an existing Word object ---> 
            <cftry> 
                <cfobject type="com" 
                    action="connect" 
                    class="Word.application" 
                    name="Application.MyWordobj" 
                    context="local">  
                <cfcatch> 
<!--- No object exists, create one ---> 
                    <cfobject type="com" 
                        action="Create" 
                        class="Word.application" 
                        name="Application.MyWordobj" 
                        context="local">  
                </cfcatch> 
            </cftry> 
             
            <cfset Application.mywordobj.visible = False> 
        </cfif> 
    </cflock> 
</cfif> 
 
<!--- Convert a Word document in temp.doc to an HTML file in temp.htm. ---> 
<!--- Because this example uses a fixed filename, multiple pages could try 
    to use the file simultaneously. The lock ensures that all actions from 
    reading the input file through closing the output file are a single "atomic" 
    operation, and the next page cannot access the file until the current page 
    completes all processing.  
    Use a named lock instead of the Application scope lock to reduce lock contention. ---> 
<cflock name="WordObjLock" type="exclusive" timeout="120"> 
    <cfset docs = application.mywordobj.documents()> 
    <cfset docs.open("c:\CFusion\wwwroot\temp.doc")> 
    <cfset converteddoc = application.mywordobj.activedocument> 
    <!--- Val(8) works with Word 2000. Use Val(10) for Word 97 ---> 
    <cfset converteddoc.saveas("c:\CFusion\wwwroot\temp.htm",val(8))> 
    <cfset converteddoc.close()> 
</cflock> 
 
<cfoutput> 
    Conversion of temp.htm Complete<br> 
    Execution Time: #int(getTickCount()-start)# milliseconds<br> 
</cfoutput>