C# - Unhandled exception. System.Runtime.InteropServices.COMException (0x800080A5)
해당 오류를 재현하기 위해, 지난 글에서 실습한 (.NET 8로 만든) C# COM DLL을,
.NET Core 3/5+ 기반의 COM Server를 기존의 regasm처럼 등록하는 방법
; https://www.sysnet.pe.kr/2/0/13459
using System.Runtime.InteropServices;
using System;
using System.Windows.Forms;
namespace ClassLibrary1
{
[ComVisible(true)]
[Guid("23172f2f-a3d3-4180-97ae-7805f74a5a46")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMyNetCode
{
void ShowDialog();
}
[ComVisible(true)]
[Guid("41AC8568-9230-4E63-B7C5-CAAD997EE207")]
public class MyNetCode : IMyNetCode
{
public void ShowDialog()
{
string platform = (IntPtr.Size == 4) ? "x86" : "x64";
MessageBox.Show($" {RuntimeEnvironment.GetSystemVersion()}: {platform}");
}
}
}
동일한 .NET 8 C# Console Application에서 활성화시켜 실행하면,
using ClassLibrary1;
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
Guid guid = new Guid("{41AC8568-9230-4E63-B7C5-CAAD997EE207}");
Type type = Type.GetTypeFromCLSID(guid);
object comObject = Activator.CreateInstance(type); // 예외 발생
}
}
}
이런 오류가 발생합니다.
C:\temp\net45_from_net80\x64\Debug> ConsoleApp1.exe
Unhandled exception. System.Runtime.InteropServices.COMException (0x800080A5): Retrieving the COM class factory for component with CLSID {41AC8568-9230-4E63-B7C5-CAAD997EE207} failed due to the following error: 800080a5 0x800080A5.
at System.RuntimeTypeHandle.AllocateComObject(Void* pClassFactory)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
at ConsoleApp1.Program.Main(String[] args) in C:\temp\net45_from_net80\ConsoleApp1\Program.cs:line 16
오류 코드가 0x800080A5 (-2147450715)인데, 검색해도 잘 안 나오는 꽤나 낯선 오류입니다. ^^;
원인은 프로젝트 설정에서 기인합니다. C# COM DLL 측에서 Windows Forms의 MessageBox.Show를 사용했기 때문에 해당 프로젝트는 다음과 같이 구축한 상태인데요,
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<EnableComHosting>true</EnableComHosting>
<EnableRegFreeCom>true</EnableRegFreeCom>
<BaseOutputPath>..\x64</BaseOutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<Platform>x64</Platform>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>test.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="dscom tlbexport "$(TargetPath)"" />
</Target>
<ItemGroup Condition="Exists('$(AssemblyName).tlb')">
<ComHostTypeLibrary Include="$(AssemblyName).tlb" Id="1"/>
</ItemGroup>
</Project>
따라서 호출 측에서도, 비록 Windows Forms 관련 코드를 직접적으로 사용하지 않았지만 마찬가지로 UseWindowsForms 옵션을 추가해야 합니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Exe</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<BaseOutputPath>..\x64</BaseOutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<Platform>x64</Platform>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
</Project>
이후 다시 실행하면, 정상적으로 실행이 됩니다. 그런데, 여기서 재미있는 건, COM Client 역할을 하는 측의 프로젝트를 그냥 일반적인 .NET Framework Console Application으로 만들거나, 단순히 C/C++ 클라이언트로 만든 경우에는 저런 설정과 무관하게 잘 실행이 된다는 점입니다.
한 가지 더 재미있는 점이 있는데요, COM Server와 클라이언트의 TargetFramework을 아래와 같이 바꾸면,
[C# COM 서버 역할을 하는 ClassLibrary1]
<TargetFramework>net7.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
[C# COM Client 역할을 하는 ConsoleApp1]
<TargetFramework>net7.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
당연히 잘 동작하겠지만, RuntimeEnvironment.GetSystemVersion()의 결괏값이 "v4.0.30319"라고 나온다는 점입니다. .NET 8 환경에서는 정상적으로 "v8.0.0"이 나왔던 반면, .NET 7인 경우에는 마치 Desktop용의 .NET Framework을 가리키는 버전이 나오는 것입니다.
아울러, 다음과 같이 버전이 서로 일치하지 않는 경우에도,
[C# COM 서버 역할을 하는 ClassLibrary1]
<TargetFramework>net7.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
[C# COM Client 역할을 하는 ConsoleApp1]
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
혹은 반대로 하는 경우에도,
[C# COM 서버 역할을 하는 ClassLibrary1]
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
[C# COM Client 역할을 하는 ConsoleApp1]
<TargetFramework>net7.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
이유는 알 수 없지만, 동일하게 "0x800080A5" 오류가 발생합니다. (제가 테스트한 것은 .NET 5부터 한 것인데, 모두 COM 서버와 클라이언트의 프로젝트 버전이 동일해야 합니다. 아마도 이전의 .NET Core 런타임에서도 마찬가지 현상이 있을 듯합니다.) 이런 조합의 수까지 고려해 테스트를 하려면, 마이크로소프트도 참 골치 아플 것 같습니다. ^^;
(2023-12-02 업데이트) 정식 문서에 보면,
Hosting layer error and exit codes
; https://github.com/dotnet/runtime/blob/main/docs/design/features/host-error-codes.md
0x800080a5에 대해,
Error returned by hostfxr_initialize_for_runtime_config if the component being initialized requires framework which is not available or incompatible with the frameworks loaded by the runtime already in the process. For example trying to load a component which requires 3.0 into a process which is already running a 2.0 runtime.
라는 설명이 나오는군요. ^^ 결국
지난번의 결론에 해당합니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]