Search code examples
c#visual-studiovisual-studio-2015c++-clicil

LNK2022 between C++/CLI .obj and C# .netmodule with partial class


While toying with mixed-language assemblies (e.g. here or here) I came across a couple of bizarre link errors. I am not looking for "don't do that" advice or alternatives, but rather I'd like to understand what these errors are supposed to mean, since neither makes much sense at face value.

C# code:

// csppStub.cs
public static partial class To
{
    static partial void Do();
    extern public static bool Done();
}

C++ code:

// cspp.cpp
public ref class To abstract sealed
{
    // lnk2022 metadata operation failed (801311D6):
    // Differing number of methods in duplicated types (To): (0x02000002).
    static void Do() { }

    // lnk2022 metadata operation failed (801311D8):
    // Differing number of parameters in duplicated method (types: To; methods: Done): (0x06000001).
    public: static bool Done() { return true; }
};

VS 2015 project file:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup Label="ProjectConfigurations">
    <ProjectConfiguration Include="Debug|Win32">
      <Configuration>Debug</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
  </ItemGroup>
  <PropertyGroup Label="Globals">
    <ProjectName>cspp</ProjectName>
    <ProjectGuid>{A6FF913B-C27A-4CBC-A847-B15CFA7AE80F}</ProjectGuid>
    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <PlatformToolset>v140</PlatformToolset>
    <CLRSupport>true</CLRSupport>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
  <ImportGroup Label="ExtensionSettings">
  </ImportGroup>
  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <PropertyGroup>
    <_ProjectFileVersion>14.0.25123.0</_ProjectFileVersion>
  </PropertyGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <Link>
      <AdditionalDependencies>$(OutDir)\csppStub.netmodule;%(AdditionalDependencies)</AdditionalDependencies>
      <SubSystem>Windows</SubSystem>
      <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
    </Link>
    <ClCompile>
      <CompileAsManaged>true</CompileAsManaged>
    </ClCompile>
  </ItemDefinitionGroup>
  <ItemGroup>
    <ClCompile Include="cspp.cpp">
      <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</CompileAsManaged>
      <ExceptionHandling Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Async</ExceptionHandling>
    </ClCompile>
  </ItemGroup>
  <ItemGroup>
    <CustomBuild Include="csppStub.cs">
      <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">csc /target:module /out:$(OutDir)\csppStub.netmodule csppStub.cs</Command>
      <LinkObjects Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkObjects>
      <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(OutDir)\csppStub.netmodule</Outputs>
    </CustomBuild>
  </ItemGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  <ImportGroup Label="ExtensionTargets">
  </ImportGroup>
</Project>

Build output:

Error LNK2022 metadata operation failed (801311D8): Differing number of parameters in duplicated method (types: To; methods: Done): (0x06000001).

Error LNK2022 metadata operation failed (801311D6): Differing number of methods in duplicated types (To): (0x02000002).

Solution

  • The linker is pretty confounded by what you are trying to do, so its diagnostic is not stellar. A common issue in C++/CLI code is that it may encounter the same ref class declaration in multiple object files. Caused by the #include directive. Not something that ever happens in C#, the partial keyword is resolved at compile-time.

    The linker has to do something about it, it needs to filter down these duplicates to a single definition that it can emit in the metadata. It performs a test to ensure that the class definition are exactly the same. LNK2022 when they are not. Common causes for that is a macro or different compile options.

    But in this case the duplicates came from the C# and the C++/CLI declarations. Abandon all hope, that will never be a match. The extern keyword does not do what you think it does, it is a directive for the jitter and tells it that it needs to find the function elsewhere. Not a lot of other places it knows about, nor is it extensible, limited to functions that the CLR implements, [DllImport] declarations and COM interop.

    There is no mechanism at all in metadata that resembles partial. You'll have to consider getting ahead with the normal boring way you'd do this in .NET, inheritance and polymorphism. The C++/CLI class can use the C# class as its base and the C# class can be abstract.