Creating an Activation Context with CreateActCtx Win32 API

2.9k views Asked by At

I am trying to create an activation context using the CreateActCtx Win32 API. There is not a lot of code out there on the internet where this function is used, but I was able to find two blogs that talk about this after a lot of googling and got this far. However, I haven't found much more info on this API and its call form .Net anywhere else on Google or SO which is strange. The reason I think it is strange is because I'm trying to do something that although rare, is rather justified in my opinion. I'm trying to use registration-free COM interop where the COM Dlls reside in a folder that is to be decided in runtime. When the Dlls are in the same folder this can be done with manifest files. However, when the COM Dll is in another folder than the executing assembly's working directory, one has to explicitly provide the manifest file to the OS. Why the folder has to be decided during runtime is a business requirement that cannot be changed at this point. We need to get this out fast.

I am not an expert in Pinvoke and I don't really know how one is supposed to debug an error message you receive from the API. In this specific instance I receive an error 87 which is "Invalid Parameter" as described here. I have tried to read more on Pinvoke and make sure that I'm using the right managed types for marshalling purposes and am using the parameters properly as described in the MSDN doc. At this point, I don't really know how to debug this further ! I'm completely lost as to why this method is returning with an error message. Here is my code(I've removed the activate, release and other pertinent methods for brevity):

// Activation context structure
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
internal struct ACTCTX
{
    public Int32 cbSize;
    public UInt32 dwFlags;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string lpSource;
    public UInt16 wProcessorArchitecture;
    public UInt16 wLangId;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string lpAssemblyDirectory;
    public UInt16 lpResourceName;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string lpApplicationName;
    public IntPtr hModule;
}

// Activation Context API Functions
[DllImport("Kernel32.dll", SetLastError = true, EntryPoint = "CreateActCtx")]
internal extern static IntPtr CreateActCtx(ref ACTCTX actctx);

private IntPtr m_hActCtx = (IntPtr)0;

public ActivationContextWin32API()
{
    m_hActCtx = (IntPtr)0;
}

//private const int ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID = 0x004;
private const int ACTCTX_FLAG_RESOURCE_NAME_VALID = 0x008;
public bool CreateContext(string manifestPath, string rootFolder, out UInt32 dwError)
{
    dwError = 0;
    ACTCTX info = new ACTCTX();
    info.cbSize = Marshal.SizeOf(typeof(ACTCTX));
    info.lpSource = manifestPath;
    info.hModule = IntPtr.Zero;
    info.lpAssemblyDirectory = rootFolder;
    info.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID;
    info.lpResourceName = 2;

    lock (this)
    {
        m_hActCtx = CreateActCtx(ref info);
        if (m_hActCtx == (IntPtr)(-1))
        {
            dwError = (uint)Marshal.GetLastWin32Error();
            return false;
        }
    }

    return true;
}

There are two modes that I can use this API. One is to use a manifest that is embedded in the EXE or to use a manifest file that already exists separately. Currently both methods are returning with an error message. I have already tested the manifest file's format and it works when I don't go through the API(so the manifest file is sound).

Any help on how to debug this further would be appreciated.

1

There are 1 answers

0
David Heffernan On BEST ANSWER

I think you've made a bit of a meal of the structure translation. I'd have it like so:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct ACTCTX
{
    public int cbSize;
    public uint dwFlags;
    public string lpSource;
    public UInt16 wProcessorArchitecture;
    public UInt16 wLangId;
    public string lpAssemblyDirectory;
    public string lpResourceName;
    public string lpApplicationName;
    public IntPtr hModule;
}

I don't think there's really much point in supporting ANSI any more. That means that the function should be:

[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
extern static IntPtr CreateActCtx(ref ACTCTX actctx);

Now, that's going to bite you when you try to set lpResourceName to be a resource index. So you would actually use this as your struct declaration:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct ACTCTX
{
    public int cbSize;
    public uint dwFlags;
    public string lpSource;
    public UInt16 wProcessorArchitecture;
    public UInt16 wLangId;
    public string lpAssemblyDirectory;
    public IntPtr lpResourceName;
    public string lpApplicationName;
    public IntPtr hModule;
}

And set lpResourceName to be (IntPtr)2.

You had lpResourceName as UInt16 and that was the problem. It's either 32 or 64 bits depending on the process architecture, but never 16.