Call C# dll from F# - c#

Is there a simply way to load and call functions of a C# dll from F# both located in the same solution?
I've set up this very simple example:
My solution is called FSharpInterop and contains two project
One C# library called CSharpComm "CSharpComm\CSharpComm.csproj"
One F# project called FSharpInterop "FSharpInterop\FSharpInterop.fsproj"
The C# library provides this simple service:
using System;
namespace CSharpComm
{
public class CSharpService
{
public string Request()
{
return "Hello World provided by C#";
}
}
}
1st attempt: open command
After adding the C# project in the F# reference (right click on the References icon Add Reference... > Projects > Select CSharpComm), I tried to run this line in F#:
open CSharpComm
Error message: InteropHelloWorld.fsx(1,6): error FS0039: The namespace or module 'CSharpComm' is not defined.
2nd attempt: #r command
Tried to run:
#r "C:\src\FSharpInterop\CSharpComm\bin\Debug\CSharpComm.dll"
or
#r "C:\src\FSharpInterop\CSharpComm\bin\Debug\CSharpComm"
This ends up with the error message: Invalid directive. Expected '#r "<file-or-assembly>"'.
3rd attempt: Nuclear option
I've tried using the interop service library:
open System.Runtime.InteropServices
module InteropWithNative =
[<DllImport(#"C:\src\FSharpInterop\CSharpComm\bin\Debug\CSharpComm.dll", CallingConvention = CallingConvention.Cdecl)>]
extern void Request()
InteropWithNative.Request()
Error message: > System.EntryPointNotFoundException: Unable to find an entry point named 'Request' in DLL 'C:\src\FSharpInterop\CSharpComm\bin\Debug\CSharpComm.dll'. at FSI_0005.InteropWithNative.Request() at <StartupCode$FSI_0005>.$FSI_0005.main#()
Here I guess I need to instantiate a CSharpService object first but the Interop doesn't let me create a ctor in F#. Also, even if I manage to call the C# service that way, that looks a bit overkill for two technologies that supposes to cohabit easily.
Last attempt: Stack overflow
Following this question Import/open DLLs in F#, I tried to add an <ItemGroup> in my F# project but didn't find where and how I can do that (I thought Visual Studio would take care of that when adding the reference project).
Additional info on my solution
F# App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
</configuration>
F# packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="FSharp.Core" version="5.0.1" targetFramework="net48" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net48" />
</packages>
[Edit] FSharpInterop.fsproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>425bd4f6-3577-4c20-9ceb-3f95609706d8</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>FSharpInterop</RootNamespace>
<AssemblyName>FSharpInterop</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<UseStandardResourceNames>true</UseStandardResourceNames>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>bin\$(Configuration)\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).XML</DocumentationFile>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>bin\$(Configuration)\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).XML</DocumentationFile>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(FSharpTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets') ">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
<Import Project="$(FSharpTargetsPath)" />
<ItemGroup>
<Compile Include="AssemblyInfo.fs" />
<Compile Include="Program.fs" />
<None Include="App.config" />
<Content Include="packages.config" />
<None Include="InteropHelloWorld.fsx" />
</ItemGroup>
<ItemGroup>
<Reference Include="FSharp.Core">
<HintPath>..\packages\FSharp.Core.5.0.1\lib\netstandard2.0\FSharp.Core.dll</HintPath>
</Reference>
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.ValueTuple">
<Private>True</Private>
</Reference>
<Reference Include="CSharpComm">
<HintPath>..\CSharpComm\bin\Debug\CSharpComm.dll</HintPath>
</Reference>
</ItemGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Disclaimer: This is my first ever attempt at F#
Add a reference to the C# project from the F# project
Unsure why this "doesn't work" (reference the project, not the dll as #user1981 stated)
C# Library Project in Solution:
using System;
namespace SampleLibInCSharp
{
public class CsharpService
{
// a static function to do trivial test....
public static string Request()
{
return "Hello World provided by C#";
}
}
}
Trivial F# Console App (from VS 2019 template), set as startup app in VS: Program.fs
open System
open SampleLibInCSharp
// Define a function to construct a message to print
let from whom =
sprintf "from %s" whom
[<EntryPoint>]
let main argv =
let message = from "F#" // Call the function
printfn "Hello world %s" message
let cs = CsharpService.Request()
printfn "ola %s" cs
0 // return an integer exit code
Run the F# console app:
Hello world from F#
ola Hello World provided by C#
Note: I did see some odd red squiglies denoting some syntax error with the message you posted, but it seems more like a VS artifact or bug. Rebuilding and/or running is fine (as above).
As stated, this is a trivial F#/C# test only...
Hth...

Related

Multi-Targeting and Defined constants around usings

I have a project where I set up the project file as follows:
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net472</TargetFrameworks>
</PropertyGroup>
<!-- .NET Standard 2.0 references, compilation flags and build options -->
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
<DefineConstants>NETCORE;</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
</ItemGroup>
<!-- .NET 4.7.2 references, compilation flags and build options -->
<PropertyGroup Condition=" '$(TargetFramework)' == 'net472'">
<DefineConstants>NETFULL</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net472'">
<Content Include="3rd Party\SomeThirdParty.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Pack>true</Pack>
<PackagePath>lib\$(TargetFramework)</PackagePath>
</Content>
</ItemGroup>
And am trying to use it in a class like this:
using System;
#if NETFULL
using SomeThirdParty.Namespace;
#endif
using mynamespace
{
public class MyClass
{
}
}
However I get a compile error for the netstandard2.0 build complaining about
"type of namespace 'SomeThirdParty.Namespace' could not be found"
My understanding is that only the NETFULL build will even see the using statement and try to compile with this.
Can anyone help here?

Why do I have to go from x86 to msil back to x86 when I change signature of C# COM dll

I am trying to have a C# library expose it's classes/methods to a native C++ application via COM. I am using registration free com (i.e. with manifest files) so as to not have to register the COM library with Windows. I am running in to a problem where if I modify the C# library and do things like add a new class or change the name of a class then when that class is instantiated in the C++ application it throws a EETypeLoadException which my understanding means that the COM library doesn't match what the C++ app thinks it should look like. My projects are set to automatically generate the type library and manifest file for the C# library every time you build so that the C++ app will get the most recent version. To fix this error I have to modify the manifest file for the C++ app to say that the C# dll targets msil, build, then flip it back to x86 and build again. At that point the C++ program and C# program are in sync and throw no errors. I tried wiping out the output directory and obj directory to get rid of any possible cached files but this doesn't work, only toggling the manifest file back and forth.
Here is how I have my solution setup:
Create a new solution and add a C# Class Library (.Net Framework) called ExampleLib to it.
Add an interface that contains an arbitrary method:
namespace ExampleLib
{
public interface ITestClass
{
int Add(int a, int b);
}
}
Add a class with a guid attribute that uses that interface and implement the required method.
using System.Runtime.InteropServices;
namespace ExampleLib
{
[Guid("5054A38A-946A-4BB2-854B-E1A31633DD77")]
public class TestClass : ITestClass
{
public int Add(int a, int b)
{
return a + b;
}
}
}
Go in to the project properties. In the Application section click Assembly Information and check the Make assembly COM-Visible box. In the Build section set the platform target to x86 and change the Output Path to:
..\Debug\
In the Build Events section add the following to the Post-build event:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\tlbexp.exe" "$(TargetPath)" /out:"$(TargetDir)$(TargetName).tlb"
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x86\mt.exe" -managedassemblyname:"$(TargetPath)" -out:"$(TargetName).manifest" -nodependency
If you have a different version of the Windows SDK installed or to a different location you will need to alter the above commands to match the locations for tblexp.exe and mt.exe.
Add a C++ Console app to the project called ExampleClient.
In the Source Files section add a stdafx.cpp file with the following code (this is boilerplate):
#include "stdafx.h"
In the ExampleClient.cpp file replace to default code from the template with the following:
#include "stdafx.h"
#include "atlbase.h"
#include <conio.h>
#ifdef DEBUG
#import "..\Debug\ExampleLib.tlb" raw_interfaces_only
#else
#import "..\Release\ExampleLib.tlb" raw_interfaces_only
#endif
using namespace ExampleLib;
int main()
{
HRESULT coInitResult = CoInitialize(0);
try
{
ITestClassPtr pTest(__uuidof(TestClass));
}
catch (_com_error _com_err)
{
wprintf(L"\n %s", _com_err.ErrorMessage());
auto _ = _getch();
}
CoUninitialize();
return 0;
}
Also in the Source Files section add ExampleClient.exe.manifest file with the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32"
name="ExampleClient"
version="1.0.0.0">
</assemblyIdentity>
<dependency>
<dependentAssembly>
<assemblyIdentity name="ExampleLib" version="1.0.0.0" processorArchitecture="x86"/>
</dependentAssembly>
</dependency>
</assembly>
In the Header Files section add a stdafx.h file (boilerplate):
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
Also in the Header Files section add a targetver.h file (boilerplate):
#pragma once
#include <SDKDDKVer.h>
Right click on the C++ project and unload the project then edit the ExampleClient.vcxproj file. There are many changes to this file compared to what it currently is out of the box including precompiled header changes, not embedding the manifest, additional include directories, and copying the manifest to the output directory. To make reproducing simplier I will just include the entire file which should work fine
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{32A23FFD-3FD3-4191-8799-3A8DD34DCB94}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>ExampleClient</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<EmbedManifest>false</EmbedManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<EmbedManifest>false</EmbedManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\$(Configuration)</AdditionalIncludeDirectories>
<AdditionalUsingDirectories>
</AdditionalUsingDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
</Link>
<PostBuildEvent>
<Command>xcopy $(ProjectName).exe.manifest ..\$(Configuration)\ /Y</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\$(Configuration)</AdditionalIncludeDirectories>
<AdditionalUsingDirectories>
</AdditionalUsingDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
</Link>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>Use</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
<PostBuildEvent>
<Command>xcopy $(ProjectName).exe.manifest ..\$(Configuration)\ /Y</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>Use</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ExampleClient.cpp" />
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Manifest Include="ExampleClient.exe.manifest">
<SubType>Designer</SubType>
</Manifest>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
Now set the debugger to target x86, set your startup project to ExampleClient, and run the app. You can step through it and you will notice it runs without errors. Working as intended.
Now go in to TestClass.cs and change the class name to something like TestClass2 (doesnt matter what). And also go in to the ExampleClient.cpp and change the reference to TestClass to TestClass2. Rebuild the solution. When you step through the code you will get an error when trying to instantiate TestClass2. To fix it go to the ExampleClient.exe.manifest file and change the processorArchitecture to msil. Rebuild. Then change processorArchitecture back to x86. Rebuild. Now the app will work again.
You should be able to make changes to the C# library and as long as you change the C++ app to reflect the changes it should work. You shouldn't have to toggle processorArchitecture back and forth. There must be something getting cached somewhere but I can't figure out where.
On Windows 10, the OS seems to cache manifest loading. For an experiment I would try an experiment of "touch"-ing your exe and exe manifest files. If you don't have some kind of touch, this command line will work:
powershell (ls $1).LastWriteTime = Get-Date
where $1 is the name of the file. I have a doskey macro defined as
touch=powershell (ls $1).LastWriteTime = Get-Date
You might try just touching the exe or the manifest. On Windows 7, I used to be able to log off or restart the computer to clear the manifest cache, but Windows 10 seems to be harder to clear. So I use touch so that Windows ignores the manifest cache and loads from the new files.
(When I say "manifest cache", that's just a description of my deductions of what I think Windows is doing under the hood)

DebuggerDisplayAttribute has no effect in VS 2017

It seems like the DebuggerDisplayAttribute is not having any effect in Visual Studio 2017 (15.9.4) with Resharper 2018.2.3.
Placing the attribute in AssemblyInfo.cs, or in Program.cs, has no discernible effect.
I am targeting .Net Framework 4.7.2. Changing the framework version also has no discernible effect.
I have verified that "Show raw structure of objects in variables windows" is unchecked in Tools -> Options -> Debugging -> General. I even checked it, closed VS, reopened VS, and unchecked it, just to be sure. No effect.
Placing the attribute on a custom class does have an effect. It seems that the attribute is only being ignored when at the assembly level.
This works as expected in VS 2015 (with the same version of Resharper).
Starting VS 2017 in safe mode (using devenv.exe /safemode) has no effect.
Updating VS 15.9.4 -> 15.9.7 has no effect.
Here is a very basic console application. Despite the attribute, the dt variable appears as {2/14/2019 10:35:38 AM} in my watch window.
[assembly: DebuggerDisplay("Foo", Target = typeof(DateTime))]
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var dt = DateTime.Now;
Debugger.Break();
}
}
}
If I use ILSpy to decompile my console application, I can see these attributes applied:
[assembly: Debuggable(
DebuggableAttribute.DebuggingModes.Default |
DebuggableAttribute.DebuggingModes.DisableOptimizations |
DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints |
DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: DebuggerDisplay("Foo", Target = typeof(DateTime))]
Why isn't the attribute doing anything? How do I even begin to debug this?
Edit: Complete source files as requested.
ConsoleApp1.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EBBA72C6-5087-4D8E-9F2C-B968ABD59EAA}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>ConsoleApp1</RootNamespace>
<AssemblyName>ConsoleApp1</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
app.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
AssemblyInfo.cs:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("ConsoleApp1")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConsoleApp1")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("ebba72c6-5087-4d8e-9f2c-b968abd59eaa")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
I've built the project from the sources you provided, and can't reproduce the problem:
This means that on the fresh installation of VS 2017 the feature must work. So your installation of Visual Studio must be somehow corrupt.
After investigation in comments I'd suggest to do an installation repair, it should fix the problems.

Assembly.LoadFrom works differently in .NET Core and .NET Framework

I have two .csproj projects, with the exact same source code:
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
Assembly jsonAssembly = Assembly.LoadFrom(#"C:\path\to\Newtonsoft.Json.dll");
Type reflectionType = jsonAssembly.GetType("Newtonsoft.Json.Linq.JObject");
Console.Write("Types match: " + (reflectionType == typeof(JObject)));
Console.Write("Typenames match: " + (reflectionType.FullName == typeof(JObject).FullName));
JObject cast = (JObject)Activator.CreateInstance(reflectionType);
Console.Write("Success!");
}
}
Both .csproj files have an identical reference to Newtonsoft.Json:
<Reference Include="Newtonsoft.Json">
<HintPath>C:\path\to\Newtonsoft.Json.dll</HintPath>
</Reference>
The only difference between the two projects is that I created the first one using .NET Core 2.0, and the second one using .NET Framework 4.6.1.
Framework:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A6D2C332-B60D-41F1-9983-DE10065973A1}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>ConsoleApp1</RootNamespace>
<AssemblyName>ConsoleApp1</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>C:\path\to\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Core:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>C:\path\to\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
When I run the above code using .NET Core, I get the following response:
Types match: True
Typenames match: True
Success!
When I run the above code using .NET Framework, I get the following response:
Types match: False
Typenames match: True
Unhandled Exception: System.InvalidCastException: [A]Newtonsoft.Json.Linq.JObject cannot be cast to [B]Newtonsoft.Json.Linq.JObject
Why do .NET Framework and .NET Core work differently when loading assemblies via reflection?
The problem maybe is, that in .NET Framework you use 2 different assemblies (2 different files on disk). Try to run the .NET Framework project in debug mode and see where these types are coming from or add these lines.
Console.Write("Reflection type module: " + (reflectionType.Module.FullyQualifiedName));
Console.Write("Static type module: " + (typeof(JObject).Module.FullyQualifiedName));
Inspired by Walter Branson's answer, I did some research and found how some of the Load methods behave.
/* Loads closest assembly given by FullName loaded by default, in this case it is from the same location as executable */
Assembly jsonAssembly1 = Assembly.Load(#"Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed");
/* Loads assembly exaclty from the file specified by path */
Assembly jsonAssembly2 = Assembly.LoadFile($#"{pathToAssembly}\Newtonsoft.Json.dll");
/* In NET.Framework loads same assembly as LoadFrom, but in .NET Core loads same assembly as Load */
Assembly jsonAssembly3 = Assembly.LoadFrom($#"{pathToAssembly}\Newtonsoft.Json.dll");
I believe this is occurring because the reference compare fails for .NET. When you look at the objects there is a different handle in .NET. When you look at the objects in core they both have same handle.
I think this occurs because you are loading the assembly from file. Try loading the assembly by its fully qualified name.
Assembly jsonAssembly = Assebmly.Load("NewtonSoft.Json,Version=12.0.0.0,Culture=neutral,PublicKeyToken=30adfe6b2a6aeed")
.NET Framework one copies the .dll that you are referencing to its executable directory. So the loaded .dlls are actually different.
Try to change the path from "C:\path\to\Newtonsoft.Json.dll" in .NET Framework project to "Newtonsoft.Json.dll", the result will be the same as in .NET Core one.

C# Unmanaged Export not working

I am trying to get the Unmanaged Export Basic sample working.
The steps I am following:
Create a new classlibrary project.
Add the UnmanagedExports with
nuget.
Change CPU target to x86.
Add the code from the tutorial I am following to the .cs file.
Build
The project is built sucesfully, but when I inspect my dll with DLL Export Viewer I can not see any of my functions.
I am using 32 bits OS and SharpDevelop 4.4 (I also have tried with other SharpDevelop versions and with 64bits OS with the same result).
My sln file:
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test\test.csproj", "{AFAA816C-65B2-4B58-9FB2-EB7482AA0F5F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AFAA816C-65B2-4B58-9FB2-EB7482AA0F5F}.Debug|x86.ActiveCfg = Debug|x86
{AFAA816C-65B2-4B58-9FB2-EB7482AA0F5F}.Debug|x86.Build.0 = Debug|x86
{AFAA816C-65B2-4B58-9FB2-EB7482AA0F5F}.Release|x86.ActiveCfg = Release|x86
{AFAA816C-65B2-4B58-9FB2-EB7482AA0F5F}.Release|x86.Build.0 = Release|x86
EndGlobalSection
EndGlobal
My csproj file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<ProjectGuid>{AFAA816C-65B2-4B58-9FB2-EB7482AA0F5F}</ProjectGuid>
<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<OutputType>Library</OutputType>
<RootNamespace>test</RootNamespace>
<AssemblyName>test</AssemblyName>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<AppDesignerFolder>Properties</AppDesignerFolder>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'x86' ">
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>bin\Debug\</OutputPath>
<DebugSymbols>True</DebugSymbols>
<DebugType>Full</DebugType>
<Optimize>False</Optimize>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>bin\Release\</OutputPath>
<DebugSymbols>False</DebugSymbols>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Reference Include="RGiesecke.DllExport.Metadata">
<HintPath>..\packages\UnmanagedExports.1.2.7\lib\net\RGiesecke.DllExport.Metadata.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="MyClass.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
My cs file:
using System;
using System.Collections.Generic;
using System.Text;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
namespace Testme
{
class Test
{
[DllExport("Add", CallingConvention = CallingConvention.StdCall)]
public static int Add(int left, int right)
{
return left + right;
}
........
Dll Export Viewer of my dll
Dll Export Viewer of a working dll (downloaded from the tutorial web)
Both dlls (mine and the downloaded) have the same size for my OS but not for the DLL Export Viewer.
What I am missing??
I had the same problem and I solved it this way,
You need add the follow line below this line csproj file
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- Remove this comment and insert This line-->
<Import Project="packages/UnmanagedExports.1.2.7/tools/RGiesecke.DllExport.targets" Condition="Exists('packages/UnmanagedExports.1.2.7/tools/RGiesecke.DllExport.targets')"/>
Check file exist in your folder "packages/UnmanagedExports.1.2.7/tools/RGiesecke.DllExport.targets"

Categories