简体   繁体   English

为'Any CPU'构建p4api.net.dll

[英]Building p4api.net.dll for 'Any CPU'

I got hit earlier this week with my first BadImageFormatException when trying to use the p4api.net.dll. 本周早些时候,当我尝试使用p4api.net.dll时,我遇到了第一个BadImageFormatException。 Turns out my assumption that I could just use the 64-bit version of it and p4bridge.dll was incorrect! 原来我的假设是我可以使用它的64位版本而且p4bridge.dll不正确!

In my investigation I noticed I had a warning: 在我的调查中,我注意到我有一个警告:

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"

I have learned that this means that the .dll I used is specified to use 64-bit, rather than being built with Any-CPU. 我了解到这意味着我使用的.dll指定使用64位,而不是使用Any-CPU构建。

While we could change the project to be specifically x64, I have been asked to investigate if it's still possible to keep it as platform agnostic as possible. 虽然我们可以将项目更改为特定的x64,但我被要求调查是否仍然可以将其作为平台无关的可能性。 I am still new to both p4api.net and having to deal with this sort of dependency, but my understanding is that if I can build p4api.net.dll as 'Any CPU' the warning would go away, and I would just need to do some magic to make sure the correct p4bridge.dll was used depending on the CurrentPlatform I defined in the project. 我仍然是p4api.net的新手并且不得不处理这种依赖,但我的理解是,如果我可以将p4api.net.dll构建为“任何CPU”,警告就会消失,我只需要做一些魔术,以确保使用正确的p4bridge.dll取决于我在项目中定义的CurrentPlatform。

I downloaded and compiled the p4api.net source and tried specifying any CPU, but it didn't work. 我下载并编译了p4api.net源并尝试指定任何CPU,但它不起作用。 Now it says that the architecture is set for x86 and I still get a similar MSB3270 error - now for x86. 现在它说该架构是为x86设置的,我仍然得到类似的MSB3270错误 - 现在是x86。 I don't get a warning like this with the p4api.net solution, however, so it doesn't appear to have any platform dependencies that I am aware of. 我没有得到这样的警告与p4api.net解决方案,但是,所以它不会出现有任何的平台依赖性,我知道的。 But if I use CorFlags.exe on p4api.net.dll it is definitely giving PE/32BIT flags that are platform specific. 但是,如果我在CorFlags.exe上使用CorFlags.exe ,它肯定会提供特定于平台的PE / 32BIT标志。

So to my questions: 所以我的问题:

  • Does anyone know offhand if it's even possible to build p4api.net for Any CPU? 有没有人知道是否有可能为任何CPU构建p4api.net?
  • Failing that, what would I have to do to examine what (if any) platform dependencies exist that would prevent me from building p4api.net.dll for Any CPU? 如果做不到这一点,我需要做些什么来检查哪些(如果有的话)平台依赖存在会阻止我为任何CPU构建p4api.net.dll?

If the answers to the above are no I'll probably have new questions, but will cross that bridge when I get there! 如果上面的答案都没有,我可能会有新的问题,但是当我到达那里时会越过那座桥! :) :)

Thanks in advance for any help/thoughts. 提前感谢任何帮助/想法。

I don't have the code with me at the moment, but I can describe to you what I did to solve this problem. 我目前没有代码,但我可以向你描述我为解决这个问题所做的工作。 The issue is that while the p4api.net library will compile just fine when set to target Any CPU, the underlying native C++ library (p4bridge.dll) is targeting either x86 or x64, and there's no way to compile it for both architectures in one DLL. 问题是虽然p4api.net库在设置为目标任何CPU时会编译得很好,但底层本机C ++库(p4bridge.dll)的目标是x86或x64,并且没有办法在一个架构中编译它DLL。 Thus, I had to get clever! 因此,我必须聪明!

To make this work, I added both versions of p4bridge.dll to the p4api.net project, renaming them p4bridge86.dll and p4bridge64.dll, and marked them to be included as assembly resources. 为了使这项工作,我将p4bridge.dll的两个版本添加到p4api.net项目,将它们重命名为p4bridge86.dll和p4bridge64.dll,并将它们标记为包含在程序集资源中。 Next, I wrote a static function in the p4api.net library that figured out which architecture the machine is running on, gets the correct p4bridge.dll resource, saves it to disk next to the p4api.net.dll that's currently executing, and finally P/Invokes the Windows LoadLibrary function on the extracted p4bridge.dll. 接下来,我在p4api.net库中编写了一个静态函数,它找出了机器运行的架构,获取了正确的p4bridge.dll资源,将其保存到当前正在执行的p4api.net.dll旁边的磁盘上,最后P /在解压缩的p4bridge.dll上调用Windows LoadLibrary函数。

The final piece of the puzzle is making sure this function you've written runs before any types inside p4api.net are instantiated, as at that point the loader will see types referencing p4bridge.dll and attempt to load it from disk, and if you've never run the extraction function, it won't be there and you'll have an exception thrown. 这个难题的最后一部分是确保你编写的这个函数在p4api.net中的任何类型被实例化之前运行,因为在那时加载器将看到引用p4bridge.dll的类型并尝试从磁盘加载它,如果你从来没有运行提取功能,它不会在那里,你会抛出异常。 To fix this, I had to get kinda hacky with .NET: I downloaded Einar Egilsson's fantastic little tool InjectModuleInitializer and set up a post build step on the p4api.net project which ran the tool and had it insert instructions to call the static extractor/loader function I wrote before any other code in the module was executed. 为了解决这个问题,我不得不使用.NET:我下载了Einar Egilsson的奇妙小工具InjectModuleInitializer,并在运行该工具的p4api.net项目上设置了一个构建后步骤,并插入了调用静态提取器的指令/我在执行模块中的任何其他代码之前编写的loader函数。

With this setup, I had a single p4api.net assembly which was compiled for Any CPU, but could automatically deal with the fact that the required native p4bridge.dll must exist separately for the x86 and x64 architectures. 通过这个设置,我有一个为任何CPU编译的p4api.net程序集,但可以自动处理所需的本机p4bridge.dll必须单独存在于x86和x64体系结构的事实。

When I get home later, I'll see about adding source code showing exactly how I wrote the extract and load function, and anything else that might need more clarity. 当我稍后回到家时,我会看到添加源代码,显示我是如何编写提取和加载函数的,以及其他可能需要更清晰的内容。 Sorry this answer came over a year after you asked originally, but I too needed to figure out a solution to this problem a couple days ago and since I managed to do it, I thought it would be worth sharing for anyone who might run into this very convoluted problem in the future! 对不起,这个答案是在您最初询问后的一年内提出的,但我也需要在几天前找出解决此问题的方法,因为我设法做到这一点,我认为值得分享给任何可能遇到此问题的人将来非常复杂的问题!

Edit: Here's the implementation of the class that extracts and loads the correct p4bridge.dll. 编辑:这是提取和加载正确的p4bridge.dll的类的实现。 It only extracts the DLL if it either isn't extracted, or the one it does find fails to load (because perhaps it's the wrong architecture, for some reason). 它只提取DLL,如果它没有被提取,或者它找到的那个无法加载(因为它可能是错误的架构,由于某种原因)。 Also, p4bridge.dll is several megabytes in size and there's not much point in performing unnecessary IO every time p4api.net is loaded! 另外,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);
    }
}

And here's the command line that should be used to hook in to the .net Module Initializer. 这是用于挂接到.net模块初始化程序的命令行。 Note that the /k:MyKey.snk argument allows the assembly to be strong signed after it's been modified. 请注意,/ /k:MyKey.snk参数允许程序集在修改后进行强签名。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM