为“任何CPU”构建p4api.net.dll [英] Building p4api.net.dll for 'Any CPU'

查看:172
本文介绍了为“任何CPU”构建p4api.net.dll的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

警告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? li>
  • 如果没有这样做,我该怎么做才能检查什么(如果有的话)平台依赖关系,这会阻止我为任何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项目上设置了一个后期制作步骤,并在其中执行了任何其他代码之前,插入了指令来调用我写的静态提取器/加载器函数。



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



当我回到家时,我会看到添加源代码,显示我如何写提取和加载功能,以及其他任何可能的需要更加清晰。对不起,这个答案在你原来问了一年之后,但是我也需要在几天之前弄清楚这个问题的解决方案,而且自从我设法做到这一点以后,我认为这可能会让任何可能遇到这个问题的人分享未来非常复杂的问题!



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

 内部静态类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
{
//使用(System.IO.Stream outFile = System.IO.File.Create(outPath))将程序集复制到临时文件

{
dllStream.CopyTo(outFile);
}
}
catch
{
}
}
}

///< summary>
///根据当前架构的位,加载p4bridge.dll的正确版本。
///请注意,这是由模块初始化程序调用的,该模块初始化程序在该模块
///被加载之后才被调用,但在其中执行任何其他代码之前。
///< / summary>
内部静态void LoadP4BridgeDLL()
{
//找出我们将p4bridge.dll解压后的位置
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);

//从我们的程序集资源中提取正确的架构版本的p4bridge.dll
string resourceName = Environment.Is64BitProcess? Perforce.P4.p4bridge64.dll:Perforce.P4.p4bridge86.dll;

//如果dll已经存在,那么我们不必再尝试提取它,除非它无法加载
if(System.IO.File.Exists(dllPath))
{
//尝试加载DLL
if(LoadLibrary(dllPath)!= IntPtr.Zero)
return;
}

// DLL尚未提取,或加载失败。再试一次!
ExtractResource(resourceName,dllPath);

//尝试加载DLL
IntPtr h = LoadLibrary(dllPath);
System.Diagnostics.Debug.Assert(h!= IntPtr.Zero,无法加载库+ dllPath);
}
}

这里是应该用来挂钩的命令行进入.net模块初始化程序。请注意, /k:MyKey.snk 参数允许程序集在修改后被强制签名。

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


I got hit earlier this week with my first BadImageFormatException when trying to use the p4api.net.dll. Turns out my assumption that I could just use the 64-bit version of it and p4bridge.dll was incorrect!

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.

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. 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.

I downloaded and compiled the p4api.net source and tried specifying any CPU, but it didn't work. Now it says that the architecture is set for x86 and I still get a similar MSB3270 error - now for 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. But if I use CorFlags.exe on p4api.net.dll it is definitely giving PE/32BIT flags that are platform specific.

So to my questions:

  • Does anyone know offhand if it's even possible to build p4api.net for Any CPU?
  • 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?

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. 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. 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.

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. 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.

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.

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. 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). Also, p4bridge.dll is several megabytes in size and there's not much point in performing unnecessary IO every time p4api.net is loaded!

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. Note that the /k:MyKey.snk argument allows the assembly to be strong signed after it's been modified.

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

这篇关于为“任何CPU”构建p4api.net.dll的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆