Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    17
  • comments
    4
  • views
    32481

Manifest Embedding and Activation

Sign in to follow this  
Mathucub

3114 views

First, you cannot understand manifests without understanding the Activation
Context (ActCtx) API. I'll go into more details later, but the ActCtx API
subverts ::LoadLibrary, ::CoCreateInstance and a couple other windows functions
that deal with loading libraries and resources.

This allows a module to use resources that are in \Windows\WinSxS or in the
execution directory and not have them be registered in the system registry. It
allows for any number of versions of a library to be present on a particular
system. (Eg, you can have msvcrt.dll for versions 6, 2005, 2008, 2010 and have
the same filename for each, just a different location in WinSxS. No more DLL
Hell!) For Reg Free COM, the manifest will have all the same information as
what would normally be in the registry. They are very similar to "plists" on OS
X.


Manifests can either be stored on the file system beside their module as
myprogram.exe.manifest or mylibrary.dll.manifest or embedded as a RT_MANIFEST
resource. Be very careful here: on Windows XP, if a program had an RT_MANIFEST,
and someone placed a myprogram.exe.manifest on the file system--the file would
take priority and override the resource based one. On Vista/7 they changed this
behavior--the resource will always win.


There are 3 values that the RT_MANIFEST resource can be embedded as. Here are
their general uses:


Resource ID 1 (CREATEPROCESS_MANIFEST_RESOURCE_ID) is activated implicitly
when the .EXE is executed. The activation context blankets all DLLs implictly
or explicitly loaded by the .EXE. (Unless they self manage, loaded DLLs inherit
this context.)


Resource ID 2 (ISOLATIONAWARE_MANIFEST_RESOURCE_ID) intercepts static imports
of the DLLs listed in the manifest for this module only. Also, if the module is
built as a USRDLL with MFC version 7 or later it activates the context for
dynamic use when execution control reaches any entry point in the DLL, via
AFX_MANAGE_STATE(AfxGetStaticModuleState()).


Resource ID 3 (ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID) is never
used by the Windows loader. It is used only in explicit user code that wants to
manually load and establish an activation context. In particular it is used by
the #define intercepts declared by setting ISOLATION_AWARE_ENABLED=1 before
including windows.h. For the macro implementation of ISOLATION_AWARE_ENABLED
see WinBase.inl, WinUser.inl, Commctrl.inl, Comdlg.inl, and Prsht.inl. The
intercept macros wrap the XP-theme-aware APIs such as CreateWindowEx() and
LoadLibrary() in order to activate the V6 context, which is loaded from resource
ID 3 - WinBase.inl WinbaseIsolationAwarePrivatetRgzlnPgpg actCtx.lpResourceName
= (LPCWSTR)(ULONG_PTR)3. The context is wrapped around each API call such that
the context is activated upon each API call and deactivated upon each API
return.


Now that we know where these values can be embedded--when are they used?
Well, there are two major ways. The windows executable loader will find these
and make an ActCtx when a module is loaded. This is where it gets interesting
though: they also need to be manually managed at runtime. At runtime, ActCtx's
have to be pushed and popped when crossing module boundaries that target
different library versions.


Say my library is loaded and I have a manifest on
ISOLATIONAWARE_MANIFEST_RESOURCE_ID (2); after my module is loaded the calling
application has ever right (and should) restore his own ActCtx, otherwise I've
polluted all his calls.


MFC can take care of this automatically. Lets look at his code to understand
what he is doing. Later on, we will have to take the same code and convert it
to C# so that AxHost InterOp Dlls can gain MFC's capabilities such that we can
use InterOp Dlls that reference unregistered DLLs in visual studio's design
window.


In afxstate.cpp:

void AFX_MODULE_STATE::CreateActivationContext()
{
_AfxInitContextAPI();
HMODULE hModule = m_hCurrentInstanceHandle;

WCHAR rgchFullModulePath[MAX_PATH + 2];
rgchFullModulePath[_countof(rgchFullModulePath) - 1] = 0;
rgchFullModulePath[_countof(rgchFullModulePath) - 2] = 0;
DWORD dw = GetModuleFileNameW(hModule, rgchFullModulePath, _countof(rgchFullModulePath)-1);
if (dw == 0)
{
return;
}
if (rgchFullModulePath[_countof(rgchFullModulePath) - 2] != 0)
{
SetLastError(ERROR_BUFFER_OVERFLOW);
return;
}
//First try ID 2 and then ID 1 - this is to consider also a.dll.manifest file
//for dlls, which ID 2 ignores.
ACTCTXW actCtx;
actCtx.cbSize = sizeof(actCtx);
actCtx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID;
actCtx.lpSource = rgchFullModulePath;
actCtx.lpResourceName = MAKEINTRESOURCEW(ISOLATIONAWARE_MANIFEST_RESOURCE_ID);
actCtx.hModule = hModule;
m_hActCtx = AfxCreateActCtxW(&actCtx);
if (m_hActCtx == INVALID_HANDLE_VALUE)
{
actCtx.lpResourceName = MAKEINTRESOURCEW(ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID);
m_hActCtx = AfxCreateActCtxW(&actCtx);
}
if (m_hActCtx == INVALID_HANDLE_VALUE)
{
actCtx.lpResourceName = MAKEINTRESOURCEW(CREATEPROCESS_MANIFEST_RESOURCE_ID);
m_hActCtx = AfxCreateActCtxW(&actCtx);
}
if (m_hActCtx == INVALID_HANDLE_VALUE)
{
m_hActCtx = NULL;
}
}


Looks pretty straight forward. Find the filename of the current module. Next,
in order of ISOLATIONAWARE_MANIFEST_RESOURCE_ID,
ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID, then
CREATEPROCESS_MANIFEST_RESOURCE_ID, try to find an embedded manifest and create
a manifest that get gets stored in the AfxState of this module.


Now that we have an ActCtx, when does it get activated?
Well, let look at an MFC USERDLL call:

STDMETHODIMP CVxMesh3D::LoadMesh(BSTR filename)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())

HRESULT hr=S_OK;

hr=GetMesh3DObject()->LoadMesh(filename);

return hr;
}


Looks like a standard COM wrapper call. There is an internal Cpp object accessed
by GetMesh3DObject(). An InterOp or a COM client calls in with a BSTR of the
mesh we want to load in the scenegraph. All the magic happens in the
AFX_MANAGE_STATE(AfxGetStaticModuleState()) call.


After a bit of digging, you'll find that that macro actually makes an instance
of the class "AFX_MAINTAIN_STATE2." During that object's construction the
following call is made:


AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pNewState) throw()
{
#ifdef _AFXDLL
m_pThreadState = _afxThreadState.GetData();
ASSERT(m_pThreadState);
if(m_pThreadState)
{
m_pPrevModuleState = m_pThreadState->m_pModuleState;
m_pThreadState->m_pModuleState = pNewState;
}
else
{
// This is a very bad state; we have no good way to report the error at this moment
// since exceptions from here are not expected
m_pPrevModuleState=NULL;
m_pThreadState=NULL;
}
#endif

if (AfxGetAmbientActCtx() &&
pNewState->m_hActCtx != INVALID_HANDLE_VALUE)
{
m_bValidActCtxCookie = AfxActivateActCtx(pNewState->m_hActCtx, &m_ulActCtxCookie);
}
else
{
m_bValidActCtxCookie = FALSE;
}
}


Pretty straight forward. If there is a stored ActCtx, then it is activated.
ActCtx's must always be pushed and popped if the module is not using the
caller's ActCtx, so where does he get deactivated?


If we look in that class's destructor:

// AFX_MAINTAIN_STATE2 functions
_AFXWIN_INLINE AFX_MAINTAIN_STATE2::~AFX_MAINTAIN_STATE2()
{
#ifdef _AFXDLL
// Not a good place to report errors here, so just be safe
if(m_pThreadState)
{
m_pThreadState->m_pModuleState = m_pPrevModuleState;
}
#endif

if (m_bValidActCtxCookie)
{
BOOL bRet;
bRet = AfxDeactivateActCtx(0, m_ulActCtxCookie);
ASSERT(bRet == TRUE);
}
}


Again straight forward. If there is an activated ActCtx, turn if off (popping it
off the stack). There is a really elegant use of the language here. Placing the
activation and deactivation in the constructor and destructor makes it such
that we never have to worry about manually making the push and pop calls. Even
if there is an exception!

Say GetMesh3DObject() was NULL in the above sample. That would cause a throw.
Say my calling application is and old VC6 client. My module was compiled with
the VS2005 C runtime. If the ActCtx is not deactivated, when the exception is
caught by the caller, any runtime calls he makes will be routed to the 2005
runtime, instead of the msvcrt.dll version 6. That is an obvious recipe for a
crash. Since the deactivation code is in the destructor, it is guaranteed to be
called!

In my last post I mentioned not trusting the ide's use of mt.exe to embed your
module's manifest. The reason is that you really want to decide which of the
three enumeration values you want to place it at. Also, again, if you let it do
the embed it will not leave a finalized copy of the manifest on the file system.
We need that file for other steps of this the Reg Free process.


In my solution I turned off the Embed, and use this call in the post build
step: mt /nologo -manifest "$(TargetPath).manifest" -outputresource:"$(TargetPath)";2

This uses the command line manifest tool and embeds my manifest as
ISOLATIONAWARE_MANIFEST_RESOURCE_ID.

At this point, you really haven't made any changes that move us toward Reg Free
COM. We have the standard manifest that the linker produced for us, but all it
should contain is the references to the c runtime. We are still going to have
to make our other manifests. I'll go into these steps in my next entry.
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!