Visual Studio 2010 DTE:如何使添加的DLL引用绝对而不是复制

8

概述:

我们需要使用DTE复制添加引用对话框的行为,当您添加特定的DLL时(它会在CSProj文件中的引用中添加提示路径条目)。

**注意:这里还有一个相关但不是重复的帖子,链接在这里:https://stackoverflow.com/questions/6690655/visual-studio-2010-add-in-how-to-get-a-references-hint-path-property 请阅读该帖子以获取更多关于此问题的信息。我现在已经添加了一个相当高的赏金来获得答案,并将乐意为任何合理的答案提供点赞 :)*

到目前为止:

我正在使用DTE将项目引用转换为直接的DLL引用。

假设我有一个简单的解决方案,其中包含一个Project2(父项目)引用了一个Project1(子项目),我可以像这样进行更改:

project1Reference = FindProjectReference(project2.References, project1);
project1Reference.Remove();
Reference dllReference = project2.References.Add(project1DllPath);

其中project1DllPath指的是"c:\somewhere\Project1\Bin\Debug\Project1.dll"文件。

我尚未解决的问题是,新的引用不是指向“c:\somewhere\Project1\Bin\Debug\Project1.dll”,而是指向“c:\somewhere\Project2\Bin\Debug\Project1.dll”(并且该文件被复制到那里)。

如果我使用“添加引用”菜单直接/手动添加DLL,则不会进行此复制。

如何在不复制文件并引用其副本的情况下添加对现有项目的DLL的引用?

我尝试在添加后添加了dllReference.CopyLocal = false;,但除了设置标志外,它没有任何区别。创建后似乎没有修改路径的选项。

更新:我还尝试了通过编程方式从Project2中删除对Project1的任何构建依赖关系,但这没有效果。

以下是csproj文件之间的差异:

作为项目:

  <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj">
      <Project>{86B3E118-2CD1-49E7-A180-C1346EC223B9}</Project>
      <Name>ClassLibrary1</Name>
    </ProjectReference>
  </ItemGroup>

作为 DLL 引用(路径完全丢失):
 <ItemGroup>
    <Reference Include="ClassLibrary1, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <Private>False</Private>
    </Reference>
    ...
  </ItemGroup>

作为手动引用的 DLL:
  <ItemGroup>
    <Reference Include="ClassLibrary1, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\ClassLibrary1\bin\Debug\ClassLibrary1.dll</HintPath>
    </Reference>
    ...
  </ItemGroup>

看起来能够指定DLL引用的提示路径是关键。如何在DLL引用上设置提示路径(假设您只有对Reference属性的句柄)?
更多信息(2011年7月20日):
下面Muse VSExtensions的建议不会影响到所涉及的DLL,因为从DLL的项目BIN复制已经完成,到父项目的BIN文件夹。父项目不需要使用引用路径,因为它已经在其输出文件夹中拥有子DLL。
此外,项目的“引用路径”保存在project.csproj.user文件中,而不是project.csproj文件中。
5个回答

11

我相信这是VS 2010的一个新错误/功能,因为我有一个插件,几天前从VS 2008迁移后开始显示类似的行为......基本上,如果您向VS的程序集搜索路径中添加任何内容的引用,则会在没有路径提示的情况下添加。

我找到了其他解决此问题的VS插件(包括Power Tools、NuGet等),它们似乎都使用了MsBuild。我不知道MsBuild是否会增加资源使用率——我自己没有看到太大的减速,可能是因为References.Add()本身就很慢。但请注意,要获取MsBuild项目的实例,需要使用名为"GetLoadedProjects"的方法,这可能意味着它在已经存在于内存中的数据上工作。

下面是我用来修复插件的代码,它是我在网络上找到的简化版本... 基本上,思路是像往常一样添加引用,然后使用MsBuild设置路径提示。设置提示是一项微不足道的操作,但是找到要添加提示的MsBuild项目项的实例却非常复杂。我尝试使用仅MsBuild的替代方案,但遇到了其他问题... 这个似乎可以工作。

还有一件可能感兴趣的事情:该代码包含一种优化方式——如果引用的路径与我们要添加的路径相等,则不会将提示添加到新引用中。这对于特定情况已足够,并且在VS决定使用输出文件夹中的dll而不是我们告诉它的dll时,可以正确检测到。但是,当我尝试向已经在输出文件夹中的dll添加引用(我为许多相关项目使用单个输出文件夹)时,插件没有设置提示路径,项目似乎切换到使用路径中的其他dll(在我的情况下是来自其PublicAssemblies文件夹的dll)......因此,完全可以删除那个“if (!newRef.Path.Equals(...”行并始终添加提示。我仍在调查此情况,所以欢迎任何额外的——提示或代码改进。

string newFileName = "the path to your.dll";
VSLangProj.VSProject containingProject = yourProject;

VSLangProj.Reference newRef;

newRef = containingProject.References.Add(newFileName);
if (!newRef.Path.Equals(newFileName, StringComparison.OrdinalIgnoreCase))
{
    Microsoft.Build.Evaluation.Project msBuildProj = Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.GetLoadedProjects(containingProject.Project.FullName).First();
    Microsoft.Build.Evaluation.ProjectItem msBuildRef = null;

    AssemblyName newFileAssemblyName = AssemblyName.GetAssemblyName(newFileName);
    foreach(var item in msBuildProj.GetItems("Reference"))
    {
        AssemblyName refAssemblyName = null;
        try 
        {
            refAssemblyName = new AssemblyName(item.EvaluatedInclude);
        }
        catch {}

        if (refAssemblyName != null)
        {
            var refToken = refAssemblyName.GetPublicKeyToken();
            var newToken = newFileAssemblyName.GetPublicKeyToken();

            if
            (
                refAssemblyName.Name.Equals(newFileAssemblyName.Name, StringComparison.OrdinalIgnoreCase)
                && ((refAssemblyName.Version != null && refAssemblyName.Version.Equals(newFileAssemblyName.Version))
                    || (refAssemblyName.Version == null && newFileAssemblyName.Version == null))
                && (refAssemblyName.CultureInfo != null && (refAssemblyName.CultureInfo.Equals(newFileAssemblyName.CultureInfo))
                    || (refAssemblyName.CultureInfo == null && newFileAssemblyName.CultureInfo == null))
                && ((refToken != null && newToken != null && Enumerable.SequenceEqual(refToken, newToken))
                    || (refToken == null && newToken == null))
            )
            {
                msBuildRef = item;
                break;
            }
        }
    }

    if (msBuildRef != null)
    {
        Uri newFileUri = new Uri(newFileName);
        Uri projectUri = new Uri(Path.GetDirectoryName(containingProject.Project.FullName).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar);

        Uri relativeUri = projectUri.MakeRelativeUri(newFileUri);
        msBuildRef.SetMetadataValue("HintPath", relativeUri.ToString());
    }
}

如果您愿意,可以为我的匹配问题提供答案(或简化版):http://stackoverflow.com/questions/6690655/visual-studio-2010-add-in-how-to-get-a-references-hint-path-property - iCollect.it Ltd

2
这个问题一定要使用DTE解决吗?你可以使用MSBuild自动化来完成这个任务... 它有一些类可以解析csproj文件的内容。
请看:Microsoft.Build.Evaluation Namespace,其中有一些关于如何加载csproj文件以及如何更改它的有用信息。还可以参考Project Class

这是我们构建的 Visual Studio 插件,旨在使本地开发非常大的解决方案变得超级快速。它已经使用 DTE 来执行大多数功能,因此将 MSBuild 添加到其中并不是一个好选择。无论如何,感谢您的建议。 - iCollect.it Ltd

0

面对一个好的挑战很难拒绝... 虽然我不在那里,但我认为我有一些不错的提示来推动这个问题的解决。
首先,我创建了一个微小的测试插件来复现你的问题,但是...我失败了!

    foreach (Project project in (object[])_applicationObject.ActiveSolutionProjects)
    {
        ((VSProject)project.Object).References.Add(@"c:\temp\test\FromFolder\bin\debug\KmlLib.dll");
    }

这是我硬盘上某处的随机 DLL。该 DLL 未签名,并以如下方式出现在我的 csproj 中(正是你想要实现的内容):

   <Reference Include="KmlLib">
      <HintPath>..\..\FromFolder\bin\debug\KmlLib.dll</HintPath>
    </Reference>

然后我注意到你的dll是有签名的。这也没有任何区别。接下来我做了一个测试,复制了一些标准的MS dll。我从\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies\中选择了VsWebSite.Interop.dll,并将其复制到我的FromFolder\bin\debug\目录下。添加该引用后突然重现了你的情况>我得到了include但没有hintpath。
然后进行最后的测试:我将VsWebSite.Interop.dll重命名为xxVsWebSite.Interop.dll,并包含了该dll。突然间,hintpath又被添加了!

综合所有这些和你的描述,我的猜测是,在添加引用时,VS首先查看所引用的dll是否可以在当前的搜索位置(GAC、项目文件夹、路径(?), ..)中找到。如果可以找到,则不会添加hintpath。如果找不到,则需要添加hintpath。

为了验证这个理论,你可以进行两个测试:

  • 将引用的dll复制到一个完全不同的文件夹中,然后从那里引用 --> 仍然不会包含hintpath,因为具有相同签名的dll仍然在“路径”中
  • 将引用的dll复制并重命名为一个完全不同的文件夹,并引用新名称 --> 应该添加hintpath

对你的结果很好奇 :)


有关dll搜索顺序(一般情况下)的信息可以在http://msdn.microsoft.com/en-us/library/ms682586(VS.85).aspx找到。 - Eddy
如果您直接使用“添加引用对话框”添加一个DLL,该DLL是解决方案中另一个项目的输出,则会添加我们所需的提示路径(因此这是可能的)。我们无法移动或复制文件,因为我们工具的整个目的就是加快构建时间,同时保留对这些库的完全调试。感谢您的努力 :) - iCollect.it Ltd

0
为了解决这个问题,您需要从DTE向项目属性中添加引用路径
在Visual Studio中设置引用路径的步骤如下:
  1. 在“解决方案资源管理器”中选择项目。
  2. 在“项目”菜单上,单击“属性”。
  3. 单击“引用路径”。
  4. 在“文件夹”文本框中,指定包含程序集的文件夹路径。要浏览到该文件夹,请单击省略号(…)。
  5. 单击“添加文件夹”。
您还需要从自动化对象中执行相同的操作。
如果有帮助,请告诉我。

不幸的是,这会做一些不同的事情(向父项目添加搜索缺失DLL的位置)。它不会停止我们添加的现有引用指向本地复制的DLL(而不是原始DLL)。我们需要在每个单独的DLL子引用“添加时”特别添加提示路径。如果您从“添加引用”对话框中添加,则会出现此行为,但我们找不到如何在DTE中复制它的方法。您提到的引用路径不会传播到csproj文件中的各个引用,因为它们已经被本地复制到该项目的bin目录中。 - iCollect.it Ltd
我们注意到,对项目的“引用路径”所做的任何更改实际上都会保存在project.csproj.user文件中,而不是project.csproj本身,因此对于这个问题没有用处。我已根据您的评论更新了问题。无论如何,谢谢。 - iCollect.it Ltd

0
最近我在Visual Studio 2010中添加dll引用时遇到了问题,我无法使其添加HintPath,这导致TFS构建出现问题。我注意到我几周前安装的Productivity Power Tools插件已更改了添加引用对话框。我从工具菜单中关闭了插件的“可搜索添加引用对话框”选项,并在重新启动Visual Studio 2010后再次尝试添加引用,这次HintPath也出现了,我很高兴。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接