如何在被C#项目调用的非托管C++项目中添加引用?

45

一个方案(the.sln)

一个C++项目(在2010年是mycppproject.vcxproj,在2008年是mycppproject.vcproj),它编译一个导出一些函数的本地DLL。在调试中,它会构建c:\output\Debug\ mycppproject_d.dll,在发布时,它会构建c:\ output \ Release \ mycppproject.dll。

一个包含对DLL的PInvoke调用的C#控制台应用程序(mycsharpconsole.csproj)。

所有编译都很好。

当我构建时,我想能够向c#项目添加对cpp DLL项目的引用,以便它可以将适当的文件从适当的目录复制到c#项目构建的\ bin \ Debug目录中。

这应该是可能的,因为IDE知道关于DLL构建的所有信息,以及C#应用程序的构建位置。

在Visual Studio 2010中:

我尝试了在c#项目上“依赖项……”并添加对mycppproject的依赖项,但没有效果。

我尝试了在c#项目上“添加引用……”并添加对cpp项目的引用,但我收到一个警告消息:“项目“mycppproject”的目标框架版本高于当前项目的目标框架版本。你还想将此引用添加到你的项目吗?”(是/否/取消)。

点击“是”会产生错误消息“无法添加对mycppproject的引用”。


抱歉,这确实是那个问题的重复。我在初次搜索中没有找到它。 - WaffleSouffle
4个回答

39

您无法添加对非托管 DLL 的引用。

相反,您应该创建一个后期生成任务来复制文件。

或者,您可以将非托管 DLL 作为文件链接添加到 C# 项目中,并将 Build Action 设置为None ,将 Copy to Output Directory 设置为 Copy If Newer


3
如果我使用你的第二个建议,那么如果C++ dll被重新构建,它会获取新文件吗?它似乎在将其作为项目链接添加时就复制了该文件。 - Matt Warren
10
从打开的对话框中的下拉菜单中选择“添加为链接”。 - SLaks
1
这对于不同的构建配置(如Release和Debug)没有帮助。 - WaffleSouffle
1
@WaffleSouffle:使用文本编辑器打开你的C#项目的*.csproj文件,搜索所有"YourNativeCppProject.dll"出现的地方(如果你添加了*.pdb文件作为链接,你会发现不止一个出现),并使用宏更改包含路径,例如: Include="$(SolutionDir)$(ConfigurationName)\YourNativeCppProject.dll"PS:如果你查看属性(F4),VS会显示Debug的路径,即使你切换到Release配置,但如果你编译,你会发现复制到输出的dll是发布版本。 - Notoriousxl
@Notoriousxl,这基本上就是我最终所做的:定义了一堆MSBuild属性和目标,并使用它们来驱动位置,从而产生$(YourNativeCppProjectDLLPath)属性。请注意,如果在.csproj上运行MSBuild,则无法使用解决方案属性。你应该将此发布为答案,因为MSBuild属性绝对是正确的方法,而不是仅仅依赖于Visual Studio IDE。但是,MSBuild确实引入了另一层有趣的行为。 - WaffleSouffle
2
我更喜欢将“Build Action”设置为“Content”。这样,依赖的DLL会自动复制到安装和部署项目、依赖于包含原始DLL依赖项的库的二级应用程序等的输出目录中。这只是考虑到更多的使用情况。 - BTownTKD

38

Visual Studio不支持从托管的C#项目中引用非托管的C++项目,但是MSBuild支持从任何一个项目中引用其他项目。

您可以通过手动编辑.csproj文件来向您的项目添加引用。在文件中,找到现有的ProjectReference元素集(或者如果没有,则添加新的ItemGroup),并添加以下引用:

<ProjectReference Include="..\mycpproject.csproj">
  <Project>{b402782f-de0a-41fa-b364-60612a786fb2}</Project>
  <Name>mycppproject</Name>
  <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
  <OutputItemType>Content</OutputItemType>
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</ProjectReference>

当您执行构建时,引用将导致MSBuild首先构建被引用的项目。ReferenceOutputAssembly值告诉MSBuild不要复制构建的输出程序集(因为C++项目没有产生任何输出文件),但OutputItemTypeCopyToOutputDirectory值会指示它将输出内容复制到引用项目的输出文件夹中。

您可以在Visual Studio中看到该引用,但在那里不能做太多事情。

此答案基于Kirill Osenkov在他的MSDN博客上解决的类似问题:https://blogs.msdn.microsoft.com/kirillosenkov/2015/04/04/how-to-have-a-project-reference-without-referencing-the-actual-binary/


1
在我看来,这种方法比使用后期构建复制步骤或插入链接更符合惯用语。(1)与手动复制不同,它可以正确处理“传递”情况。如果A引用B,B引用C,C引用D,D引用native.dll,为什么我还要告诉A将native.dll复制到其输出目录中呢?A甚至不需要知道native.dll的存在。(2)它会根据需要自动选择本机dll的调试/发布等版本。使用链接没有简单的方法来实现这种行为。遗憾的是,VS不允许您在UI中以这种方式设置事物。 - dlf
2
但不幸的是,这种方法似乎会在传递依赖关系的情况下(托管A依赖于托管B依赖于本机C)使VS混淆——当您进行干净构建时,它将复制本机dll到最终输出目录,但在随后的构建中,它将主动从那里删除它。 :( - dlf
2
这对我不起作用。引用确实出现在Visual Studio中,但是没有任何程序集/文件被复制。 - chtenb
更正:内容文件已正确复制,但新编译的构件未能成功。 - chtenb
1
这拯救了我的生命!这三行是关键:<ReferenceOutputAssembly>false</ReferenceOutputAssembly> <OutputItemType>Content</OutputItemType> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - Ed Graham

20

我会遵循Slaks的第二个答案...

[...] 您可以在C#项目中将未管理的DLL作为文件添加,并将构建操作设置为“无”,将输出目录复制设置为“仅在新文件时复制”。

... 然后是我的评论,以区分调试和发布版本(即使有点“hackish”,因为它需要您手动编辑C#项目文件)

使用文本编辑器打开C#项目的csproj文件并搜索所有“YourNativeCppProject.dll”出现次数(不带“.dll”后缀,因此如果您也将pdb文件添加为链接,则会找到多个出现次数),并使用宏更改包含路径,例如:Include =“ $(SolutionDir)$(ConfigurationName)\ YourNativeCppProject.dll

PS:如果您查看属性(F4),VS会向您显示调试路径,即使您切换到发布配置,但是如果您编译,您将看到复制到输出的dll是发布版本*


5
在我看来,编辑.csproj文件并不是什么黑客行为,而更像是一种必要操作。 - WaffleSouffle
1
@WaffleSouffle:“有点被Visual Studio和属性窗口搞晕了”,所以 :D - Notoriousxl
这个很有效。唯一的问题是,当其代码发生更改时,本地DLL永远不会被构建,除非您手动构建C++项目,即使正确设置了项目依赖关系。你有解决办法吗? - jnm2
@jnm2 不好意思,但这很奇怪:我本来预料到C#项目会出现构建问题,但如果您修改了A项目,A应该可以构建(但是任何事情都有可能发生,因为这是一个黑客)。唯一想到的事情(但您可能已经检查过了):原生项目在配置管理器上是否选中了“构建”标志? - Notoriousxl
@Notoriousxl 实际上这是一个没有更新的第三个项目。更多细节请参考此链接。http://stackoverflow.com/q/34657743/521757 - jnm2
1
我看到的情况正好相反;构建“startup”C#项目确实会生成所依赖的本机dll,但是它不会复制它,因为C#项目已经是最新的。我在管理和本机插件(运行时依赖项)dll方面都遇到了这个问题长达十年。我希望微软能够改进对这种开发模式的支持。在VS2015中仍然存在这个问题 :( - Gaspode

2
只有.NET程序集、相同解决方案中的.NET项目或COM组件(将创建管理器包装器)才支持添加引用。
在C#代码中使用DLLImport访问C++ dll并不意味着Visual Studio知道外部dll从哪里复制到哪里。
可以将DLLImport看作是运行时选项,其中放置的路径在运行时用于查找dll,因此必须自己部署c++ dll并使其可用于.net应用程序,通常在同一bin文件夹中。

2
我希望它可以被设置为在本地复制未托管的DLL,因为这是Visual Studio对.NET类库DLL的标准操作,而且实际上这并没有太大的区别。最终它们也会在运行时加载。 - WaffleSouffle
1
.NET编译模型有两种,一种是“在全局程序集缓存中”,另一种是“全部在一个文件夹中”。如果非托管代码能够得到支持,这将会更加方便。虽然你说它都是在运行时进行的,DLL可以来自任何地方,但对于编写模块化库的开发人员来说,一些额外的工具来帮助管理位置会更好。 - WaffleSouffle

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