使用 CreateActCtx Win32 创建激活上下文 API

Creating an Activation Context with CreateActCtx Win32 API

我正在尝试使用 CreateActCtx Win32 API 创建激活上下文。互联网上并没有很多使用此功能的代码,但经过大量谷歌搜索后,我找到了两个讨论此功能的博客,并走到了这一步。但是,我在 Google 或 SO 上的其他任何地方都没有找到关于此 API 及其调用形式 .Net 的更多信息,这很奇怪。我认为这很奇怪的原因是因为我正在尝试做一些虽然很少见但在我看来是合理的事情。我正在尝试使用免注册 COM 互操作,其中 COM Dll 驻留在将在运行时决定的文件夹中。当 Dll 位于同一文件夹中时,这可以通过清单文件来完成。但是,当 COM Dll 位于执行程序集的工作目录之外的另一个文件夹中时,必须明确向 OS 提供清单文件。为什么必须在运行时决定文件夹是此时无法更改的业务需求。我们需要尽快解决这个问题。

我不是 Pinvoke 方面的专家,我真的不知道应该如何调试您从 API 收到的错误消息。在此特定实例中,我收到错误 87,如 here 所述 "Invalid Parameter"。我尝试阅读更多关于 Pinvoke 的内容,并确保我使用正确的托管类型进行编组,并按照 MSDN 文档中的描述正确使用参数。在这一点上,我真的不知道如何进一步调试它!我完全不知道为什么此方法返回错误消息。这是我的代码(为简洁起见,我删除了激活、释放和其他相关方法):

// 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;
}

我可以使用两种模式 API。一种是使用嵌入在 EXE 中的清单或使用已经单独存在的清单文件。目前这两种方法都返回错误消息。我已经测试了清单文件的格式,当我不通过 API 时它可以工作(因此清单文件是正确的)。

任何关于如何进一步调试的帮助将不胜感激。

我认为你在结构翻译方面做得有点不错。我会这样:

[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;
}

我认为不再支持 ANSI 真的没有多大意义。这意味着函数应该是:

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

现在,当您尝试将 lpResourceName 设置为资源索引时,这会困扰您。所以你实际上会使用它作为你的结构声明:

[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;
}

并将lpResourceName设置为(IntPtr)2

你有 lpResourceName 作为 UInt16,这就是问题所在。它是 32 位或 64 位,具体取决于进程架构,但绝不是 16 位。