The company I work for uses wix toolset to create a msi, that should install our extension in Visual Studio.
Everything was working fine until Visual Studio 2017.
When there are a few IDEs of VS installed on the client's computer, for example Professional and Enterprise, our extension would be installed only in one of them.
Does anyone know how to fix this issue? Is there a way to iterate VS2017's IDEs?
EDIT: the code of the installation in VS2017 looks like this:
<Directory Id="VS2017_IDE_DIR">
<Directory Id="VS2017_EXTENSIONS_DIR">
<Directory Id="VS2017_MYCOMPANY_EX" Name="MYCOMPANY">
<Directory Id="VS2017_AUTORUNNER_EX" Name="MYCOMPANY Extension">
<Directory Id="VS2017_AUTORUNNER_EX_VERSION" Name="$(var.MajorAndMinorVersion)">
<Component Id="VS2017_AUTORUNNER_EXTENSIONSHORTCUTS" Guid="">
<Condition>VS2017DEVENV</Condition>
<CreateFolder />
<util:RestartResource Path="[VS2017DEVENV]"/>
<RemoveFolder Id="REMOVE_VS_VERSION_VS2017" On="uninstall" Directory="VS2017_MYCOMPANY_EX" />
<RemoveFolder Id="REMOVE_VS2017_EXTENSIONS" On="uninstall" Directory="VS2017_AUTORUNNER_EX" />
<RemoveFolder Id="REMOVE_VS2017_MYCOMPANY_EX" On="uninstall" Directory="VS2017_AUTORUNNER_EX_VERSION" />
<File Source="$(var.ManagedBinariesDir)VisualStudioExtension\extension.vsixmanifest" Name="extension.vsixmanifest"
Id="extension.vsixmanifest_VS2017" />
<File Source="$(var.ManagedBinariesDir)VisualStudioExtension\MYCOMPANY.VSExtension.pkgdef"
Name="MYCOMPANY.VSExtension.pkgdef" Id="MYCOMPANY.AutoRunner.pkgdef_VS2017" />
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
As written above, this code installs the extension only in one IDE of VS 2017, and I want the extension to be installed in all VS 2017 IDEs.
Disclaimer: I work at Typemock - the unit testing company
We had the same issue with our Wix installer, here's what you need to do:
• create properties for each VS 2017 instance:
<Property Id="VS2017_INSTANCE_1" />
<Property Id="VS2017_INSTANCE_2" />
<Property Id="VS2017_INSTANCE_3" />
• assign values to them with the help of vswhere (a tool that locates all installed VS 2017 paths):
add a CustomAction that activates vswhere and assign the paths to the properties.
• create a piece of code like you wrote above for each one of these properties:
<Directory Id="VS2017_INSTANCE_1">
...
</Directory>
<Directory Id="VS2017_INSTANCE_2">
...
</Directory>
<Directory Id="VS2017_INSTANCE_3">
...
</Directory>
• Most important part: put the CustomAction before CostFinalize at the InstallUISequence
Related
I am trying to create an appx/appxupload package for my QT GUI application so that it can be published to the Windows Store. Here are the list of steps I followed to generate an appx package:
Generate a Visual Studio project file using my QT .pro file by using the qmake -tp vc command in the directory containing my .pro file. I have used the x86 msvc-2017 kit in QT
Opened the .vcproj file using Visual Studio 2017 ,Version 15.9.10 and built the project to check for any errors.
Added a new project Windows Application Packaging Project (Visual C++) to the same Visual Studio solution.
Added my QT project (now converted to Visual Studio project) as a reference to Applications in my new Packaging project
Added all the Visual Assets, app name and other configuration settings in the Designer mode and then built the project
Package the application to upload to Windows Store
When, I try to install and run the application, I get an error saying : This application failed to start because it could not find or load the Qt platform plugin "windows" in "".. All the QT plugins are present in the installation folder, however my application exe file is present inside an another folder in the installation directory like AppName\AppName.exe. If I copy the AppName.exe outside the AppName folder and run, the app runs fine.
I went through the various xml files and I see under Application section, Executable = "AppName\AppName.exe" instead of Executable = AppName.exe. I believe there is a configuration setting where one can configure the Application target path. If the exe file is at the same level as that of all the other QT dll's, the app will run fine without errors.
Can someone please help resolve this issue?
Thanks
The Package.appxmanifest generated by Visual Studio
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap mp rescap">
<Identity Name="CybernetyxTechnikPvt.Ltd.AirMindThinker" Publisher="CN=FD00F14F-4E0A-4328-8B66-60A5777EDBFE" Version="1.0.24.0" />
<Properties>
<DisplayName>AirMind Thinker</DisplayName>
<PublisherDisplayName>Cybernetyx Technik Pvt. Ltd.</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
<uap:VisualElements DisplayName="AirMind Thinker" Description="AirMind Thinker Application" BackgroundColor="transparent" Square150x150Logo="Images\Square150x150Logo.png" Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square310x310Logo="Images\LargeTile.png" Square71x71Logo="Images\SmallTile.png">
</uap:DefaultTile>
<uap:SplashScreen Image="Images\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
This is the AppxManifest.xml generated in the output folder (bin/x86/Release/)
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap mp rescap build" xmlns:build="http://schemas.microsoft.com/developer/appx/2015/build">
<!--
THIS PACKAGE MANIFEST FILE IS GENERATED BY THE BUILD PROCESS.
Changes to this file will be lost when it is regenerated. To correct errors in this file, edit the source .appxmanifest file.
For more information on package manifest files, see http://go.microsoft.com/fwlink/?LinkID=241727
-->
<Identity Name="CybernetyxTechnikPvt.Ltd.AirMindThinker" Publisher="CN=FD00F14F-4E0A-4328-8B66-60A5777EDBFE" Version="1.0.24.0" ProcessorArchitecture="x86" />
<Properties>
<DisplayName>AirMind Thinker</DisplayName>
<PublisherDisplayName>Cybernetyx Technik Pvt. Ltd.</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.14393.0" MaxVersionTested="10.0.17763.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.17763.0" />
<PackageDependency Name="Microsoft.VCLibs.140.00.UWPDesktop" MinVersion="14.0.26905.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
</Dependencies>
<Resources>
<Resource Language="EN-US" />
</Resources>
<Applications>
<Application Id="App" Executable="AirMindThinker\AirMindThinker.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="AirMind Thinker" Description="AirMind Thinker Application" BackgroundColor="transparent" Square150x150Logo="Images\Square150x150Logo.png" Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square310x310Logo="Images\LargeTile.png" Square71x71Logo="Images\SmallTile.png"></uap:DefaultTile>
<uap:SplashScreen Image="Images\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
</Capabilities>
<build:Metadata>
<build:Item Name="Microsoft.Build.DesktopBridge.Tasks.dll" Version="4.6.30319.200" />
<build:Item Name="TargetFrameworkMoniker" Value=".NETCore,Version=v5.0" />
<build:Item Name="VisualStudio" Version="15.0" />
<build:Item Name="VisualStudioEdition" Value="Microsoft Visual Studio Community 2017" />
<build:Item Name="OperatingSystem" Version="10.0.18362.1 (WinBuild.160101.0800)" />
<build:Item Name="Microsoft.Build.AppxPackage.dll" Version="15.0.28307.104" />
<build:Item Name="ProjectGUID" Value="e995a5ab-e99d-4e1c-864d-a5fbbfc73ceb" />
<build:Item Name="MakePri.exe" Version="10.0.17763.132 (WinBuild.160101.0800)" />
</build:Metadata>
</Package>
In my C++ VS project I added a custom target to compile shader files and set it as a initial target. This is the project xml
<Project InitialTargets="CompileShaders" DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
..... Normal Default VS C++ content here ......
<ItemGroup>
<GLSLShader Include="SPIR-V\canvas2D.vert" />
<GLSLShader Include="SPIR-V\canvas2D.frag" />
</ItemGroup>
<Target Name="CompileShaders" Inputs="#(GLSLShader)" Outputs="SPIR-V\shaders_bytecode.h" >
<PropertyGroup>
<OriginalFileName>%(GLSLShader.Filename)%(GLSLShader.Extension)</OriginalFileName>
</PropertyGroup>
<Message Text="Start Compiling GSLANG #(GLSLShader) " />
<Message Condition="'$(VULKAN_SDK)'==''" Text="Error, cant find environment variable VULKAN_SDK, Make sure that the Lunar Vulkan SDK is installed" />
<Message Condition="'$(VULKAN_SDK)'!=''" Text="$(VULKAN_SDK)\Bin\glslangValidator.exe %(GLSLShader.Filename)%(GLSLShader.Extension) -V --vn $([System.String]::Copy('%(GLSLShader.Filename)%(GLSLShader.Extension)').Replace('.','_')) -o %(GLSLShader.Filename)%(GLSLShader.Extension).h" />
<Exec Condition="'$(VULKAN_SDK)'!=''" Command="$(VULKAN_SDK)\Bin\glslangValidator.exe %(GLSLShader.Filename)%(GLSLShader.Extension) -V --vn $([System.String]::Copy('%(GLSLShader.Filename)%(GLSLShader.Extension)').Replace('.','_')) -o %(GLSLShader.Filename)%(GLSLShader.Extension).h" WorkingDirectory="$(ProjectDir)\SPIR-V" />
<Exec Condition="'$(VULKAN_SDK)'!=''" Command="del shaders_bytecode.h" WorkingDirectory="$(ProjectDir)\SPIR-V" />
<Exec Condition="'$(VULKAN_SDK)'!=''" Command="type %(GLSLShader.Filename)%(GLSLShader.Extension).h >> shaders_bytecode.h" WorkingDirectory="$(ProjectDir)\SPIR-V" />
<Exec Condition="'$(VULKAN_SDK)'!=''" Command="del %(GLSLShader.Filename)%(GLSLShader.Extension).h" WorkingDirectory="$(ProjectDir)\SPIR-V" />
</Target>
</Project>
if I change any .cpp .h file and build the solution, the shaders are compiled together with the rest of the project, but if I change only the shader files (i.e SPIR-V\canvas2D.vert) the project is not built. VS says that the project is up to date.
Now the strange thing, If I run the project using msbuild on the terminal ouside VS, the shader files changes are enough to trigger the rebuild. Go figure....!!!
It looks like something related to how VS build projects. It is outside the msbuild.
Aha!! Found it in this article. It turns out visual studio bypass msbuild and uses some other criteria to verify if a project is up to date or not. If the criteria fails it then runs the msbuild on that project.
To override the visual studio behavior set the property DisableFastUpToDateCheck as true in the Globals property group of your project's xml:
<PropertyGroup Label="Globals">
<!-- Other Global Property Settings -->
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup>
And it is done... I wonder if there is a away to tell Visual Studio FastUpToDateCheck Mechanism to also pay attention to the custom build files ????? The solution above will suffice for now.
I've looked at this answer:
Editing the .csproj file and correcting the relative path to the
solution folder (which contains the packages folder) solved the
problem for me.
but I'm not sure what to edit.
I'm making a C++ project in Visual Studio 2013. I have one solution with three projects. One of the projects needs a NuGet package.
I used Manage NuGet packages for solution on my solution to install the NuGet package. From there I selected the one project which needs the package. I also used Enable NuGet Package Restore.
By doing this I end up with a .nuget and packages folder in the solution directory.
When I try to build the project requiring the package I get the error:
This project references NuGet package(s) that are missing on this
computer. Enable NuGet Package Restore to download them. For more
information, see http://go.microsoft.com/fwlink/?LinkID=322105. The
missing file is
....\packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets.
If I follow the mentioned path, assuming that the directory it is relative from is the project directory of the project, the "missing file" is there.
The project having this problem, was a project I used alone (with its own solution) before, and has been copy pasted to this solution with three projects.
That's why I mentioned the answer of an another question and the top of the post. That answer suggest editing a project file. If I open my .vcxproj file I see this:
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets" Condition="Exists('..\packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Import Project="..\..\packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets" Condition="Exists('..\..\packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets')" />
<Import Project="..\..\packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets" Condition="Exists('..\..\packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
<Error Condition="!Exists('..\..\packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets'))" />
<Error Condition="!Exists('..\..\packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets'))" />
<Error Condition="!Exists('..\..\packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets'))" />
</Target>
I'm very unsure what to edit.
I can guess that maybe some of the paths should be changed from being relative of the project directory, to being relative of the solution directory. But by editing to:
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets" Condition="Exists('packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Import Project="packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets" Condition="Exists('packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets')" />
<Import Project="packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets" Condition="Exists('packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
<Error Condition="!Exists('packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets'))" />
<Error Condition="!Exists('packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets'))" />
<Error Condition="!Exists('packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets'))" />
</Target>
Visual studio will give me an error stating: An item with the same key has already been added. The project is then marked as (load failed) in the solution browser.
What am I doing wrong here?
I found my solution through this answer.
In the .vcxproj file I removed the following:
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
<Error Condition="!Exists('packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.winapp.msvcstl.dyn.rt-dyn.targets'))" />
<Error Condition="!Exists('packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets'))" />
<Error Condition="!Exists('packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v120.windesktop.msvcstl.dyn.rt-dyn.targets'))" />
</Target>
and I can now build the project.
It is probably not the best solution, as there is a reason for these Error Conditions to exist. But editing the paths did not help.
How is the way to add a C++ Library in a .NET Core project (Class Library). I tried creating a nuget package but doesn't work. I got this error:
An unhandled exception of type 'System.DllNotFoundException' occurred in "NameOfDll.dll"
When I add the nuget package the project.json add the following reference:
"dependencies": {
"Core": "1.0.0-*",
"NETStandard.Library": "1.6.0",
"NameOfDll.dll": "1.0.0"
},
dependencies attribute in project.json specifies package dependencies, because of this NuGet handles NameOfDll.dll as a package ID, but not as a dll name.
To add native C++ dlls into you NuGet package for .xproj library you should do the following steps:
Put your NameOfDll.dll in \lib directory near MyClassLibrary.xproj
Open project.json file and add there:
"packOptions": {
"files" : {
"includeFiles" : "lib/NameOfDll.dll"
}
}
Execute dotnet pack
NameOfDll.dll will be included into NuGet package under the path lib\NameOfDll.dll.
To add native C++ dlls into your NuGet package for .csproj library you should do the following steps:
I assume you have managed project with name MyClassLibrary.csproj.
Create new NuGet package for you class library with nuget spec command.
Create \lib, \build, \content and \tools directories near MyClassLibrary.nuspec.
Copy all your native dlls into the \build folder.
Change extension of copied native dlls to .native.
Create MyClassLibrary.targets with the following content inside \build folder:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AvailableItemName Include="NativeBinary" />
</ItemGroup>
<ItemGroup>
<NativeBinary Include="$(MSBuildThisFileDirectory)*">
<TargetPath></TargetPath>
</NativeBinary>
</ItemGroup>
<PropertyGroup>
<PrepareForRunDependsOn>
$(PrepareForRunDependsOn);
CopyNativeBinaries
</PrepareForRunDependsOn>
</PropertyGroup>
<Target Name="CopyNativeBinaries" DependsOnTargets="CopyFilesToOutputDirectory">
<Copy SourceFiles="#(NativeBinary)"
DestinationFiles="#(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).dll')"
Condition="'%(Extension)'=='.native'">
<Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
</Copy>
<Copy SourceFiles="#(NativeBinary)"
DestinationFiles="#(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).%(Extension)')"
Condition="'%(Extension)'!='.native'">
<Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
</Copy>
</Target>
</Project>
Hint: The .targets content is taken from this question.
The above .targets file will be injected on an installation of the NuGet package into the target project file.
Add the following lines into your MyClassLibrary.nuspec:
<files>
<file src="lib\" target="lib" />
<file src="tools\" target="tools" />
<file src="content\" target="content" />
<file src="build\" target="build" />
</files>
Execute nuget pack MyClassLibrary.csproj to build your NuGet package.
Here is a one-click way to do it in .net core 2. I used it in my website and it works.
Right click your project in Solution Explorer, select Add->Existing Item, browse and choose your dll (select show all extensions). Then your dll will appear in Solution explorer.
In the Solution Explorer, right-click your dll -> Properties. Set build action: Content, and Copy to Output Directory: Copy if newer (or always copy).
It's done. Use your dll in your code staightforward like this:
[DllImport("my_dll.dll", CallingConvention = CallingConvention.Cdecl)]
I have a mixed mode C++ project producing a managed dll assembly exporting some CLR classes (call it Managed.dll). This project is using a native dll, (call it Native.dll).
When I reference the Managed.dll from another project producing Client.exe, everything works as expected, except than I need to manually copy the Native.dll in the same folder as Client.exe.
If there a way to convince VS to copy locally (in the bin folder of Client.exe) not only Managed.dll but Native.dll as well?
I have tried to include Native.dll as a dependency assembly in the manifest but this didn't help.
Edit
Managed.dll is going to be a redistributable assembly. It will be installed in a folder in "C:\Program Files.....". When a developer using Visual Studio adds a reference to Managed.dll, Native.dll should be also copied in the \bin folder of his project.
There are several ways to tell the VS to copy dlls to the destination folder:
1.Add the dll as a resource of the project. And tell the VS to copy it if the dll is newer
2.Add a new project that reference to the dll project, and set the OutDir to the folder you want. This project does nothing but copy the dll.
3.Use a PostBuildEvent in vcxproj file
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
</ClCompile>
<Link>
</Link>
<PostBuildEvent>
<Command>
echo off
mkdir "$(ProjectDir)..\..\bin\$(Configuration)\"
copy "$(OutDir)xxx.dll" "$(ProjectDir)..\..\lib\$(Configuration)\"
echo on
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
4.Use a PreBuildEvent in vcxproj file
5.Use CustomBuild in vcxproj file
<ItemGroup>
<CustomBuild Include="..\..\xxx.dll">
<FileType>Document</FileType>
<Command>
call mkdir "$(OutDir)" 2>nul &
copy /Y "..\..\xxx.dll" "$(OutDir)xxx.dll"
</Command>
<Message>Copying xxx.dll to $(OutDir)\xxx.dll</Message>
<Outputs>$(OutDir)\xxx.dll</Outputs>
</CustomBuild>
</ItemGroup>
6.Use a makefile and copy dll in makefile. and use nmake to build
7.Write a bat file that do the copy job, and invoke the bat file as in 3-6
8.Use script such as python, which can also download the dll from internet. And invoke the py file as in 3-6.
9.Other build tools can help too, such as gradle
10.Make it a NuGet plugin
11.Sometimes I just write a bat, and execute the bat manually.
Update 01 (Self extract dll example):
1.Add you native dll as resource of managed dll
2.Add this init() method
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace DllSelfExtract
{
public class SelfExtract
{
public static void Init()
{
String managedDllPath = System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
String nativeDllPath = managedDllPath.Replace("file:///", "").Replace("DllSelfExtract.DLL", "TestDll.dll");
if(!File.Exists(nativeDllPath))
{
Stream dllIn = Assembly.GetExecutingAssembly().GetManifestResourceStream("DllSelfExtract.TestDll.dll");
if (dllIn == null) return;
using (Stream outFile = File.Create(nativeDllPath))
{
const int sz = 4096;
byte[] buf = new byte[sz];
while (true)
{
int nRead = dllIn.Read(buf, 0, sz);
if (nRead < 1)
break;
outFile.Write(buf, 0, nRead);
}
}
}
//LoadLibrary Here
}
}
}
3.In project that use your managed dll, invoke init() method first
SelfExtract.Init();
Update 02 (NuGet example):
1.Create a new NuGet project
2.Place the managed assemblies in the /lib directory
3.Place the non-managed shared libraries and related files in the /build subdirectory and rename all non-managed *.dll to *.dl_
4.Add a custom .targets file in the /build subdirectory with something like the following contents :
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AvailableItemName Include="NativeBinary" />
</ItemGroup>
<ItemGroup>
<NativeBinary Include="$(MSBuildThisFileDirectory)*">
<TargetPath></TargetPath>
</NativeBinary>
</ItemGroup>
<PropertyGroup>
<PrepareForRunDependsOn>
$(PrepareForRunDependsOn);
CopyNativeBinaries
</PrepareForRunDependsOn>
</PropertyGroup>
<Target Name="CopyNativeBinaries" DependsOnTargets="CopyFilesToOutputDirectory">
<Copy SourceFiles="#(NativeBinary)"
DestinationFiles="#(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).dll')"
Condition="'%(Extension)'=='.dl_'">
<Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
</Copy>
<Copy SourceFiles="#(NativeBinary)"
DestinationFiles="#(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).%(Extension)')"
Condition="'%(Extension)'!='.dl_'">
<Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
</Copy>
</Target>
</Project>
5.Add build rule for build folder in Package.nuspec
<files>
<file src="lib\" target="lib" />
<file src="tools\" target="tools" />
<file src="content\" target="content" />
<file src="build\" target="build" />
</files>
6.Build the package
7.In your other C# project just add this NuGet package.
Using /ASSEMBLYLINKRESOURCE option in linker properties seems to be the simplest solution. It makes Visual Studio consider the native dll as a part of the assembly. Also, according to documentation provided by Microsoft, allows the native dll to be installed in the Global Assembly Cache
To set this linker option in a Visual C++ project:
Right click on the project name and select Properties
Select the Linker folder
In the Input property page find the Assembly Link Resource option
Write the file name of the native assembly e.g. MyNative.dll
You will need a Post Build event to copy the native dll to the output folder.
Referencing the managed assembly from any other Visual Project, forces the native dll to be copied together with the managed assembly in the /bin folder.