Quantcast
Viewing latest article 4
Browse Latest Browse All 8

error C3510: cannot locate dependent type library ” {bed7f4ea-1a96-11d2-8f08-00a0c9a6186d} v.2.4

Problem

I have recently migrated a C# 2.0 project registered for COM interop to .NET 4.5 and when I imported the type library in a C++ project with no_registry, suddenly I got some errors because the type library could not be imported. Here are the steps to reproduce:

  • create a .NET Class Library project and set platform target to .NET framework 4.5
  • check Register for COM interop
  • build the project
  • import the type library in a C++ project:
    #import "DemoClassLibrary.tlb" no_dual_interfaces no_registry

The result is the following error:

1>[...]: error C3510: cannot locate dependent type library '' {bed7f4ea-1a96-11d2-8f08-00a0c9a6186d} v.2.4
1>[...]: fatal error C1083: Cannot open type library file: '[...]\democlasslibrary.tlb': Error loading type library/DLL.

Solution

Searching for the solution I found that this was a known issue when you have both CLR 2.0 and 4.0 installed on the same machine. See this KB article: VC++ 2010 #import with no_registry fails with error C1083 or C3510. Unfortunately I was unable to fix the problem with the solution indicated there.

There are two tools that can generate a type library from an assembly:

  • tlbexp.exe: generates a type library from a specified .NET assembly
  • regasm.exe: registers metadata from an assembly to the Windows Registry, but in addition can create a type library from for the input assembly when /tlb switch is used.

When a project specifies to register for COM interop what Visual Studio does is similar to calling regasm.exe with /codebase switch specified. Since I had before problems with interop assemblies automatically generated by Visual Studio (with tlbimp.exe) I though it would be the same (only the other way around). Therefore I unchecked “register for COM interop” and added as a custom build step registration with regasm.exe, like this:

c:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe DemoClassLibrary.dll /tlb:DemoClassLibrary.tlb

Not very surprisingly, the generated file was different and the #import command executed without problems.

Problem solved!

Cause

The question that arises is why are the two files, generated with Visual Studio and with regasm.exe, different? You can see they are different if you open them in an hex editor. But if you just use oleview.exe, the disassembled type library looks identical.

The obvious answer that occurred to me, but eventually proved wrong, was that Visual Studio is not actually using regasm.exe to register the assembly and generate the type library. It actually uses a MSBuild task for that.

When the RegisterForComInterop property is set in a .csproj an MSBuild task is executed.

1>Using "RegisterAssembly" task from assembly "Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
1>Task "RegisterAssembly" (TaskId:32)
1>  Task Parameter:Assemblies=[...]\DemoClassLibrary.dll (TaskId:32)
1>  Task Parameter:TypeLibFiles=[...]\DemoClassLibrary.tlb (TaskId:32)
1>  Task Parameter:AssemblyListFile=obj\DemoClassLibrary.csproj.UnmanagedRegistration.cache (TaskId:32)
1>  Task Parameter:CreateCodeBase=True (TaskId:32)
1>  Registering assembly "[...]\DemoClassLibrary.dll" for COM Interop. (TaskId:32)
1>  Exporting and registering type library "[...]\DemoClassLibrary.tlb". (TaskId:32)
1>  Type 'P' exported. (TaskId:32)
1>  Type 'P' exported. (TaskId:32)
1>Done executing task "RegisterAssembly". (TaskId:32)

The task can be found in Microsoft.Common.targets (in c:\Windows\Microsoft.NET\Framework\v4.0.30319\)

<!--
    ============================================================
                                        UnmanagedRegistration

    Registers the main assembly for COM interop.
    ============================================================
    -->
  <PropertyGroup>
    <UnmanagedRegistrationDependsOn></UnmanagedRegistrationDependsOn>
  </PropertyGroup>
  <Target
      Name="UnmanagedRegistration"
      Condition="'$(RegisterForComInterop)'=='true' and '$(OutputType)'=='library'"
      DependsOnTargets="$(UnmanagedRegistrationDependsOn)"
        >

    <PropertyGroup>
      <RegisterAssemblyMSBuildArchitecture Condition="'$(RegisterAssemblyMSBuildArchitecture)' == ''">$(PlatformTargetAsMSBuildArchitecture)</RegisterAssemblyMSBuildArchitecture>
    </PropertyGroup>

    <PropertyGroup Condition="'$(TargetFrameworkAsMSBuildRuntime)' != '' and '$(RegisterAssemblyMSBuildArchitecture)' != ''">
      <!-- Falling back to the current runtime if we are targeting CLR2 and the task host doesn't exist will lead to 
           incorrect behavior in some cases, but it's the same incorrect behavior as Visual Studio 2010, and thus better
           than causing build breaks on upgrade to Win8 the way not doing so would.  For more details, see the 
           corresponding comment in GenerateResource. -->
      <RegisterAssemblyMSBuildRuntime 
          Condition="'$(RegisterAssemblyMSBuildRuntime)' == '' and 
                     $([MSBuild]::DoesTaskHostExist(`$(TargetFrameworkAsMSBuildRuntime)`, `$(RegisterAssemblyMSBuildArchitecture)`))">$(TargetFrameworkAsMSBuildRuntime)</RegisterAssemblyMSBuildRuntime>

      <!-- If the targeted runtime doesn't exist, fall back to current -->
      <RegisterAssemblyMSBuildRuntime Condition="'$(RegisterAssemblyMSBuildRuntime)' == ''">CurrentRuntime</RegisterAssemblyMSBuildRuntime>
    </PropertyGroup>

    <RegisterAssembly
        Assemblies="@(_OutputPathItem->'%(FullPath)$(TargetFileName)')"
        TypeLibFiles="@(_OutputPathItem->'%(FullPath)$(TargetName).tlb')"
        AssemblyListFile="@(_UnmanagedRegistrationCache)"
        CreateCodeBase="true"
        MSBuildRuntime="$(RegisterAssemblyMSBuildRuntime)"
        MSBuildArchitecture="$(RegisterAssemblyMSBuildArchitecture)"
        Condition="!Exists('@(_UnmanagedRegistrationCache)')"/>

    <ItemGroup>
      <FileWrites Include="@(_OutputPathItem->'%(FullPath)$(TargetName).tlb')"/>
    </ItemGroup>
  </Target>

To check if I can reproduce, I have created MSBuild file (explicitreg.xml) with some hard-coded values that only runs that registration task.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="UnmanagedRegistration" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <UnmanagedRegistrationDependsOn></UnmanagedRegistrationDependsOn>
  </PropertyGroup>
  <Target
      Name="UnmanagedRegistration"
      DependsOnTargets="$(UnmanagedRegistrationDependsOn)"
        >

    <PropertyGroup>
      <RegisterAssemblyMSBuildArchitecture Condition="'$(RegisterAssemblyMSBuildArchitecture)' == ''">$(PlatformTargetAsMSBuildArchitecture)</RegisterAssemblyMSBuildArchitecture>
    </PropertyGroup>

    <PropertyGroup Condition="'$(TargetFrameworkAsMSBuildRuntime)' != '' and '$(RegisterAssemblyMSBuildArchitecture)' != ''">
      <RegisterAssemblyMSBuildRuntime 
          Condition="'$(RegisterAssemblyMSBuildRuntime)' == '' and 
                     $([MSBuild]::DoesTaskHostExist(`$(TargetFrameworkAsMSBuildRuntime)`, `$(RegisterAssemblyMSBuildArchitecture)`))">$(TargetFrameworkAsMSBuildRuntime)</RegisterAssemblyMSBuildRuntime>

      <!-- If the targeted runtime doesn't exist, fall back to current -->
      <RegisterAssemblyMSBuildRuntime Condition="'$(RegisterAssemblyMSBuildRuntime)' == ''">CurrentRuntime</RegisterAssemblyMSBuildRuntime>
    </PropertyGroup>

    <RegisterAssembly
        Assemblies="bin\Debug\DemoClassLibrary.dll"
        TypeLibFiles="bin\Debug\DemoClassLibrary.tlb"
        AssemblyListFile="obj\DemoClassLibrary.csproj.UnmanagedRegistration.cache"
        CreateCodeBase="true" />

    <ItemGroup>
      <FileWrites Include="bin\Debug\DemoClassLibrary.tlb"/>
    </ItemGroup>
  </Target>
</Project>

c:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe explicitreg.xml

But surprise: this produced the exact same output as the regasm.exe command. Comparing the diagnose logs from MSBuild (for the build of the .csproj and my custom file) I couldn’t spot any difference in the execution of the task. Also using the Process Monitor (procmon.exe from Sysinternals) to check access to the TLB file, I could clearly see the difference for the file writing, because different lengths were produced from Visual Studio build and explicit MSBuild run, though again I could not see any difference in the call stacks.

So, the actual cause for this behavior is still unknown to me and I would appreciate if anyone that knows the answer clarifies it.


Viewing latest article 4
Browse Latest Browse All 8

Trending Articles