Activation Context API
If you have not read When to Use the Activation Context API or How to Use the Activation Context API you may wish to read them first.
When using the activation context API directly, it is critically important to release the activation context as soon as possible and to make really sure that any calls made outside of the current module (program, DLL, CLR assembly or class) are made with the same activation context stack that was active when we received control. Failing to ensure this, we are inviting context pollution and a whole new class of hard to diagnose program failures.
In C++ it is possible to write code in a very clean manner by following a simple rule: activate the activation context only for the LoadLibrary or CoCreateInstance calls that needs to create the COM object or load the DLL then immediately deactivate the context. In CLR-based languages (C#, VB.Net) and, even, in VB6 it is not possible to deactivate the context immediately because the runtime performs on-the-fly type conversions and it needs access to the type library. Just deactivate the context as soon as you can.
Additional code improvement may be added by the use of C++ or CLR (C#,VB.Net,...) classes. Again, in C++ objects are destroyed upon the exit from the block so you can rely on any cleanup operations programmed into the destructor to be executed synchronously. In CLR the object is not actually destroyed until the garbage collector gets to it so you cannot rely on automatic cleanup, you need to clean up explicitly.
Example C++ classes
// ---------------------------------------------------------------------------- // CActCtxHandle // ---------------------------------------------------------------------------- class CActCtxHandle { friend class CActCtxCookie; protected: HANDLE m_hContext; public: // Set the context handle to NULL CActCtxHandle(void) : m_hContext(NULL) { } // Release the activation context but keep the last error value ~CActCtxHandle(void) { if (!m_hContext) return; DWORD dwError = GetLastError(); ReleaseActCtx(m_hContext); SetLastError(dwError); } // Free the activation context handle, always safe to call void Release(void) { if (NULL!=m_hContext) ReleaseActCtx(m_hContext); m_hContext = 0; } // Create a new activation context, only works if no handle bool Create(LPCTSTR psManifest, LPCTSTR psRootFolder) { // We already have an activation context handle if (NULL!=m_hContext) { SetLastError(ERROR_ALREADY_INITIALIZED); return false; } // Set up the ACTCTX ACTCTX actctx = {sizeof(actctx)}; actctx.lpSource = psManifest; actctx.lpAssemblyDirectory = psRootFolder; actctx.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID; // Create the context, keep the handle if successfull m_hContext = CreateActCtx(&actctx); if (INVALID_HANDLE_VALUE==m_hContext) m_hContext = NULL; return NULL!=m_hContext; } }; // ---------------------------------------------------------------------------- // CActCtxCookie // ---------------------------------------------------------------------------- class CActCtxCookie { protected: ULONG_PTR m_cookie; public: CActCtxCookie(void) : m_cookie(0) { } // Deactivate the context but keep the last error ~CActCtxCookie(void) { if (!m_cookie) return; DWORD dwError = GetLastError(); DeactivateActCtx(0, m_cookie); SetLastError(dwError); } // Activate an existing context bool Activate(const CActCtxHandle &ach) { // The context handle must exist if (!ach.m_hContext) { SetLastError(ERROR_INVALID_HANDLE); return false; } // We must not have a context active if (0!=m_cookie) { SetLastError(ERROR_ALREADY_INITIALIZED); return false; } // Activate the context and keep the cookie BOOL fOK = ActivateActCtx(ach.m_hContext, &m_cookie); if (!fOK) m_cookie = 0; return 0!=fOK; } // Deactivate the existing context, may generate an exception if this // context is not at the top of the active avtivation context stack // for the current thread. void Deactivate(void) { if (0!=m_cookie) DeactivateActCtx(0, m_cookie); m_cookie = 0; } };
This code is copied from the example project "Examples\ActCtx\ActCtx_cpp".
Normally when a program creates an activation context, it needs to keep the activation context handle for a relatively long time but it needs to activate and deactivate the context quite frequently. Additionally it is important to deactivate the context as soon as it is not required to avoid activation context pollution. Most commonly this means that an activation context should be deactivated no later the on exit from the current block.
The above two classes serve these two purposes. CActCtxHandle is a container for the activation context handle and will close the handle when object is destroyed. CActCtxCookie is a container for the active activation context (and the corresponding cookie). This class deactivates the activation context when the object is destroyed.
This is an example so these classes are as simple as possible while providing useful functionality. Example usage (note that error checking has been removed, refer to the actual example to see the complete code):
bool Test_2(LPCTSTR psManifestPath, LPCTSTR psRootFolder) { CActCtxHandle ach; if (!ach.Create(psManifestPath, psRootFolder)) { return false; } CActCtxCookie acc; if (!acc.Activate(ach)) { return false; } ... create and use the COM object here return true; }
As you can see the task of deactivating the activation context and, in this case, releasing the context is performed automatically on exit from this function by the two destructors. Additionally it would be a good idea to use a try-finally construct to make sure that the destructors are executed in case of an exception.
Example C# class
public class ActivationContextHelper : IDisposable { ... Activation Context API Functions and structure are declared here private IntPtr m_hActCtx = (IntPtr)0; public ActivationContextHelper() { m_hActCtx = (IntPtr)0; } void IDisposable.Dispose() { Clear(); } public void Clear() { lock (this) { if (0 != m_hActCtx.ToInt32()) ReleaseActCtx(m_hActCtx); m_hActCtx = (IntPtr)0; } } public bool CreateContext(string manifestPath, string rootFolder, out UInt32 dwError) { ACTCTX info = new ACTCTX(); info.cbSize = Marshal.SizeOf(typeof(ACTCTX)); info.dwFlags = 0; info.lpSource = manifestPath; info.lpAssemblyDirectory = rootFolder; info.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID; lock (this) { m_hActCtx = CreateActCtx(ref info); } return true; } public IntPtr ActivateContext(out UInt32 dwError) { IntPtr cookie = IntPtr.Zero; lock (this) { ActivateActCtx(m_hActCtx, out cookie)) } return cookie; } public bool DeactivateContext(IntPtr cookie, out UInt32 dwError) { lock (this) { DeactivateActCtx(0, cookie)) } return true; } public bool ReleaseContext(out UInt32 dwError) { lock (this) { ReleaseActCtx(m_hActCtx); m_hActCtx = (IntPtr)0; } return true; } }
This code is copied from the example project "Examples\Asp.Net.20\AspManifestHelpers". Note that error checking has been removed to simplify the example. Also all the necessary declarations of the Windows APIs are not included in the above code but are a part of the example included with Manifest Maker.
This class does not provide automatic cleanup like the C++ class does, but it does make activation context operations easier then they would be without it. The main benefit is that the class encloses all the necessary definitions which otherwise would need to be included every time the SxS API is used. Example usage:
ActivationContextHelper ach = new ActivationContextHelper(); string path = AppDomain.CurrentDomain.BaseDirectory; string file = path + "webapp.manifest"; UInt32 dwError = 0; ach.CreateContext(file, path, out dwError); IntPtr cookie = ach.ActivateContext(out dwError); ... create and use the COM object here ach.DeactivateContext(cookie, out dwError); ach.ReleaseContext(out dwError);
This example was copied from "Examples\Asp.Net.20\ProgramManifest". Again, error checking is removed.
Although this example creates and activates the activation context at the same time then deactivates and deletes at the same time, it does not need to be done in this manner. If your application needs an activation context many times during processing you would create it once, keep it around and activate / deactivate as necessary then, at the end of processing release the context handle.
Read more...
- When to use the activation context API
- How to use the activation context API
- Isolating Components
Includes an example using a CActivationContext C++ class. - C++ object for activating and Deactivating Contexts
Includes a 'CActCtxActivator' example C++ class and some very important notes to programmers.