2

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.

Farhad Alizadeh Noori
  • 2,276
  • 17
  • 22
  • Pack = 4 looks dubious. Have you check that the structure size is correct? Facing this problem, always write unmanaged code first so that you can tell whether it's an interop error, or a semantic error in the data you pass. – David Heffernan Aug 10 '15 at 17:02
  • You must not ever take [pinvoke.net](http://www.pinvoke.net) seriously. I have yet to find a declaration, that isn't wrong one way or another. The [ACTCTX](http://pinvoke.net/default.aspx/Structures/ACTCTX.html) structure is no exception. If you want to learn, read [Calling Native Functions from Managed Code](https://msdn.microsoft.com/en-us/library/ms235282.aspx) instead. – IInspectable Aug 10 '15 at 17:20
  • @DavidHeffernan: I just implemented this in visual c++ and I am getting a handle back with no lasterror set. The size of the struct in c++ and .Net is 32. – Farhad Alizadeh Noori Aug 10 '15 at 19:27
  • @IInspectable Thanks. Yeah I actually landed on that website during my research and my instincts told me not to trust it as it didn't seem like it had the exposure to many experts as websites like SO have. – Farhad Alizadeh Noori Aug 10 '15 at 19:28
  • http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/UnsafeNativeMethods.cs,61d5039c95da3286 – Hans Passant Aug 10 '15 at 21:01
  • @HansPassant: Do you happen to know, where the `hModule` member of the [ACTCTX](https://msdn.microsoft.com/en-us/library/aa374149.aspx) structure went? Even if it's not strictly required, it'll change the structure's size, passed as the `cbSize` member. – IInspectable Aug 11 '15 at 12:22
  • Hmm, that's indeed curious. Hard to guess, .NET goes back pretty far. I thought it was an XP feature, maybe WindowsME ate it. – Hans Passant Aug 11 '15 at 12:33

1 Answers1

4

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490