【问题标题】:Building p4api.net.dll for 'Any CPU'为“任何 CPU”构建 p4api.net.dll
【发布时间】:2016-12-20 14:50:53
【问题描述】:

本周早些时候,我在尝试使用 p4api.net.dll 时遇到了第一个 BadImageFormatException。原来我的假设是我只能使用它的 64 位版本,而 p4bridge.dll 是不正确的!

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

warning MSB3270: There was a mismatch between the processor architecture of the project being built "MSIL" and the processor architecture of the reference "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”,警告就会消失,我只需要做一些魔术来确保根据我在项目中定义的 CurrentPlatform 使用正确的 p4bridge.dll。

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

所以我的问题:

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

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

提前感谢您的任何帮助/想法。

【问题讨论】:

    标签: c# .net dll p4api.net


    【解决方案1】:

    我目前没有代码,但我可以向您描述我为解决此问题所做的工作。问题是,虽然 p4api.net 库在设置为针对 Any CPU 时编译得很好,但底层的本机 C++ 库 (p4bridge.dll) 是针对 x86 或 x64 的,并且无法同时为这两种架构编译它动态链接库。因此,我必须变得聪明!

    为了完成这项工作,我将 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 项目上设置了一个后期构建步骤,该项目运行该工具并让它插入指令来调用静态我在执行模块中的任何其他代码之前编写的提取器/加载器函数。

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

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

    编辑: 这是提取和加载正确 p4bridge.dll 的类的实现。它仅在未提取 DLL 或找到的 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-01
      相关资源
      最近更新 更多