Mega Code Archive

 
Categories / Delphi / COM
 

Active Server Pages and COM Apartments

Title: Active Server Pages and COM Apartments Question: Facts and Guidelines for ASP/COM programming from Don Box. Look at the great resources from Don at http://www.develop.com/dbox/default.asp Answer: Active Server Pages and COM Apartments Don Box, DevelopMentor This document is my attempt to write down in one place what one can and cannot expect from ASP and COM apartments under IIS 4. Feel free to link to it (I don't plan on moving it), however, the copyright belongs to me, largely because a full-blown explanation will appear in an upcoming (probably July) MSJ ActiveX/COM Q&A If you find errors or missing aspects, please send me mail at dbox@develop.com. Put ASPAPT in the subject line to bring it to my attention. General COM Facts (skip this if you really understand what an apartment is) Processes are composed of one or more apartments. There are two types of apartments. Single-threaded apartments (STA) and multithreaded apartments (MTA). There is at most one MTA per process. There can be many STAs per process. Threads enter apartments by calling CoInitializeEx or one of the wrappers (CoInitialize/OleInitialize). Threads that call CoInitializeEx(0, COINIT_MULTITHREADED) enter the MTA of the process. Threads that call CoInitializeEx(0, COINIT_APARTMENTTHREADED) enter a new STA that no other threads can enter. Inprocess Classes indicate which type of apartment they are compatible with using the ThreadingModel registry entry. If the thread that calls CoCreateInstance(INPROC) is of a compatible apartment type, the object is created using the client's thread. If the thread that calls CoCreateInstance(INPROC) is of an incompatible apartment type, the object is created on a COM-created thread and the client receives a proxy. This means that all method invocations require a thread switch. This makes method invocation much slower (by a factor of over 1000). Classes marked as ThreadingModel=Single (or with no annotation) can only be created directly by the first STA in the process. Secondary STAs and MTA-based clients get proxies. Classes marked as ThreadingModel=Apartment can be created directly by any STA in the process. MTA-based clients get proxies. Classes marked as ThreadingModel=Free can be created directly by the MTA only. STA-based clients get proxies. Classes marked as ThreadingModel=Both can be created directly from anywhere. No one ever gets proxies. Interface pointers returned by COM API functions and methods are apartment-relative. They can only be accessed by threads in the same apartment that initialized them. To pass an interface pointer across apartments, CoMarshalInterface (or some relative like the GlobalInterfaceTable) must be used to produce an apartment-neutral representation. Objects that do not care about apartments aggregate the free-threaded marshaler using the CoCreateFreeThreadedMarshaler API function. This basically informs the system that your object is not only thread-safe, but is apartment-neutral and that pointers to your object are freely accessible from any apartment in the process. Objects that aggregate the free-threaded marshaler must ensure that they do not themselves hold apartment-relative resources (such as interface pointers). Instead, FTM-based objects typically hold apartment-neutral GIT cookies and build apartment-relative references in each method. General ASP Facts ASP is just another COM client. It does not play "threading tricks" on your object, but rather, simply plays by the rules of COM. ASP pages are processed by a pool of ASP-managed STA threads. If session state is disabled, IIS makes no attempt to serialize request from a single web client. Each request is simply enqueued on a random thread in the pool. If session state is enabled, IIS serializes concurrent requests within a single ASP session. This is why there are no session.Lock and session.Unlock methods (a la the Application object). Multiple sessions within an ASP application can execute concurrently. By default, ASP sessions do NOT have thread/apartment affinity. This affords the ASP plumbing flexibility in terms of dispatching requests to available/idle threads. If another page in the client's session is being processed by one of ASP's pooled threads, the dispatcher simply enqueues the second page request on that thread. This keeps the session from stealing more than one thread at a time. IIS does not normally try to keep a session on a particular thread/STA across session idle times (that is, if the client is constantly submitting requests in parallel from a particular session, they will stick to the same thread, but if the client stops to read a page, the next request may be processed by a different thread). The presence of session-scoped objects can affect this, however (see next section). The Application_OnStart/Session_OnStart event handlers run on the same thread that processes the first page of the session/application. ASP scripts in inprocess ASP applications run as IUSR_MACHINENAME if authentication is not enabled. ASP scripts in out-of-process ASP applications run as IUSR_MACHINENAME if authentication is not enabled. ASP scripts always run with the credentials presented by the web client if authentication is enabled. Page and Session Scope Components Because ASP scripts run on STA threads, if your inprocess component is marked ThreadingModel=Both or ThreadingModel=Apartment, you will be created directly on the thread that processes the ASP script. This is just how COM works. Because ASP scripts run on STA threads, if your inprocess component is marked ThreadingModel=Free or ThreadingModel=Single (or absent), you will be created on a system-managed thread and the ASP script will get a proxy. This is just how COM works. Also: Your methods will execute using the credentials of the Local System account if you are an inprocess ASP application. Your methods will execute using the credentials of the IWAM_MACHINENAME account if you are an out-of-process ASP application. Method invocation will be considerably slower, as a thread switch is needed to get from the proxy to the object. By default, ASP prohibits out-of-process activation via Server.CreateObject or tags. You can change this by munging a property in the metabase. If you store an interface pointer to a free-threaded-marshaler-based inprocess object as a session property, your session still has no apartment/thread affinity. This applies to both the tag in a global.asa file as well as explicitly setting a property on the session object from within a script or ASP component. If you store an interface pointer to a proxy or a non-FTM-based inprocess object as a session property, your session must always run on the same thread. This applies to both the tag in a global.asa file as well as explicitly setting a property on the session object from within a script or ASP component. Forcing ASP to always run your session on a particular thread results in a sub-optimal thread allocation scheme and is not ideal. Application Scope Components ASP uses a dedicated STA thread to process Application-scope object tags in a global.asa file. This thread is never used to run ASP pages. The reason for this is to ensure that any STA-based objects don't get pinned to a thread that is needed to process ASP pages. On my installation, this thread runs with the same credentials as the first ASP request, however, I have not had time to apply the post-SP3 hotfix which addresses some of the security "issues" in IIS4. If an application-scope in-process object created with the object tag in a global.asa file uses the Free-threaded Marshaler, ASP scripts will have direct access to the object. If an application-scope in-process object created with the object tag in a global.asa file doesn't use the Free-threaded marshaler, ASP scripts will gain access to it via a proxy. Method invocation will be considerably slower, as a thread switch is needed to get from the proxy to the object. You cannot store references to non-FTM-based inprocess objects as application properties (using Set application("key") = ). You can store references to FTM-based inprocess objects and proxies as application properties (using Set application("key") = ). Guidelines If you are writing inprocess components that only have page scope, ThreadingModel=Apartment is all that is needed to ensure you run on the thread of the ASP script. If you are writing inprocess components that will have session scope, consider using the free-threaded marshaler to avoid pinning the session to a particular thread. If you are writing inprocess components that will have application scope, consider using the free-threaded marshaler to avoid forcing a thread switch at each method invocation. If you are writing components that have application or session scope, reconsider. Designs based on singletons are bad news in both MTS and IIS and should be rearchitected to use some other technique to achieve shared state (e.g., transient per-page COM objects that store their shared state as "flat" properties in either app/session objects or somewhere more exotic like SQL Server). Out-of-process ASP applications are easier to debug and have the additional benefit of fault-isolation between ASP apps and inetinfo. Inprocess ASP applications dispatch faster, but are more difficult to debug and offer no fault-isolation. Acknowledgments George Reilly, David Wihl and Tim Ewald have all been very supportive of my quest for ASP nirvana. That being said, any errors on this page are my own.