c# – 为’Any CPU’构建p4api.net.dll

本周早些时候,当我尝试使用p4api.net.dll时,我遇到了第一个Bad
ImageFormatException.原来我的假设是我可以使用它的64位版本而且p4bridge.dll不正确!

在我的调查中,我注意到我有一个警告:

警告MSB3270:正在构建的项目“MSIL”的处理器体系结构与参考“p4api.net,Version = 2014.3.100.9144,Culture = neutral,PublicKeyToken = f6b9b9d036c873e1,processorArchitecture = AMD64”的处理器体系结构不匹配, “AMD64”

我了解到这意味着我使用的.dll指定使用64位,而不是使用Any-CPU构建.

虽然我们可以将项目更改为特定的x64,但我被要求调查是否仍然可以将其作为平台无关的可能性.我仍然是p4api.net的新手并且不得不处理这种依赖,但我的理解是,如果我可以将p4api.net.dll构建为“任何CPU”,警告就会消失,我只需要做一些魔术,以确保使用正确的p4bridge.dll取决于我在项目中定义的CurrentPlatform.

我下载并编译了p4api.net源并尝试指定任何CPU,但它不起作用.现在它说该架构是为x86设置的,我仍然得到类似的MSB3270错误 – 现在是x86.但是,我没有用p4api.net解决方案得到这样的警告,因此它似乎没有我所知道的任何平台依赖性.但是,如果我在p4api.net.dll上使用CorFlags.exe,它肯定会提供特定于平台的PE / 32BIT标志.

所以我的问题:

>有没有人知道是否有可能为任何CPU构建p4api.net?
>如果做不到这一点,我需要做些什么来检查哪些(如果有的话)平台依赖存在会阻止我为任何CPU构建p4api.net.dll?

如果上面的答案都没有,我可能会有新的问题,但是当我到达那里时会越过那座桥! 🙂

提前感谢任何帮助/想法.

最佳答案 我目前没有代码,但我可以向你描述我为解决这个问题所做的工作.问题是虽然p4api.net库在设置为目标任何CPU时都可以正常编译,但底层本机C库(p4bridge.dll)的目标是x86或x64,并且没有办法在一个架构中编译它DLL.因此,我必须聪明!

为了使这项工作,我将p4bridge.dll的两个版本添加到p4api.net项目,将它们重命名为p4bridge86.dll和p4bridge64.dll,并将它们标记为包含在程序集资源中.接下来,我在p4api.net库中编写了一个静态函数,它找出了机器运行的架构,获取了正确的p4bridge.dll资源,将其保存到当前正在执行的p4api.net.dll旁边的磁盘上,最后P /在解压缩的p4bridge.dll上调用Windows LoadLibrary函数.

这个难题的最后一部分是确保你编写的这个函数在p4api.net中的任何类型被实例化之前运行,因为在那时加载器将看到引用p4bridge.dll的类型并尝试从磁盘加载它,如果你从来没有运行提取功能,它不会在那里,你会抛出异常.为了解决这个问题,我不得不对.NET有点不满:我下载了Einar Egilsson的奇妙小工具InjectModuleInitializer,并在运行该工具的p4api.net项目上设置了一个构建后步骤,并插入了调用静态提取器的指令/我在执行模块中的任何其他代码之前编写的loader函数.

通过这个设置,我有一个为任何CPU编译的p4api.net程序集,但可以自动处理所需的本机p4bridge.dll必须单独存在于x86和x64体系结构的事实.

当我稍后回到家时,我会看到添加源代码,显示我是如何编写提取和加载函数的,以及其他可能需要更清晰的内容.对不起,这个答案是在您最初询问后的一年内提出的,但我也需要在几天前找出解决此问题的方法,因为我设法做到这一点,我认为值得分享给任何可能遇到此问题的人将来非常复杂的问题!

编辑:
这是提取和加载正确的p4bridge.dll的类的实现.它只提取DLL,如果它没有被提取,或者它找到的那个无法加载(因为它可能是错误的架构,由于某种原因).另外,p4bridge.dll的大小是几兆字节,每次加载p4api.net时执行不必要的IO都没有多大意义!

internal static class P4BridgeLoader
{
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string dllToLoad);

    private static void ExtractResource(string resourceName, string outPath)
    {
        using (System.IO.Stream dllStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
        {
            try
            {
                // Copy the assembly to the temporary file
                using (System.IO.Stream outFile = System.IO.File.Create(outPath))
                {
                    dllStream.CopyTo(outFile);
                }
            }
            catch
            {
            }
        }
    }

    /// <summary>
    /// Loads the correct version of p4bridge.dll, based on the bit with of the current architecture.
    /// Note that this is called by the module initializer, which gets called just after this module
    /// is loaded but before any other code inside it is executed.
    /// </summary>
    internal static void LoadP4BridgeDLL()
    {
        // Figure out where we are going to put the p4bridge.dll once we've extracted it
        string codeBase = Assembly.GetExecutingAssembly().CodeBase;
        UriBuilder uri = new UriBuilder(codeBase);
        string assemblyPath = Uri.UnescapeDataString(uri.Path);
        string assemblyDir = Path.GetDirectoryName(assemblyPath);
        string dllPath = Path.Combine(assemblyDir, "p4bridge.dll");

        // Extract the correct architecture version of p4bridge.dll from our assembly's resources
        string resourceName = Environment.Is64BitProcess ? "Perforce.P4.p4bridge64.dll" : "Perforce.P4.p4bridge86.dll";

        // If the dll already exists, then we shouldn't have to try extracting it again unless it fails to load
        if (System.IO.File.Exists(dllPath))
        {
            // Attempt to load the DLL
            if (LoadLibrary(dllPath) != IntPtr.Zero)
                return;
        }

        // DLL either wasn't already extracted, or failed to load. Try again!
        ExtractResource(resourceName, dllPath);

        // Attempt to load the DLL
        IntPtr h = LoadLibrary(dllPath);
        System.Diagnostics.Debug.Assert(h != IntPtr.Zero, "Unable to load library " + dllPath);
    }
}

这是用于挂接到.net模块初始化程序的命令行.请注意,/ k:MyKey.snk参数允许程序集在修改后进行强签名.

InjectModuleInitializer.exe /k:MyKey.snk /m:Perforce.P4.P4BridgeLoader::LoadP4BridgeDLL p4api.net.dll
点赞