.NET 8의 DLL을 형 있게 VBA에서 쓰는 방법 - COM 공개 + dscom으로 TLB를 생성

· 업데이트: · · C#, .NET 8, VBA, COM, Office, dscom

수정 이력(1건, 최종 수정 2026년 09월 01일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

일본어 원문의 완역으로 다시 번역했습니다. 기존 한국어판은 원문의 일부만 옮긴 축약본이라 절, 표, Mermaid 그림, 그림 설명, FAQ가 빠져 있었습니다. 일본어 원문에 맞춰 이들을 모두 복원했고, 기술적인 주장은 일본어판과 같습니다. 수정 전 버전 보기 (DOI: 10.5281/zenodo.21635169)
최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635168)

이 글은 Zenodo에 보관되어 있습니다. 항상 최신 버전으로 연결되는 DOI와 지금 보고 있는 버전에 고정된 DOI를 아래에 함께 제시합니다.

小村 豪 (2026). 「.NET 8의 DLL을 형 있게 VBA에서 쓰는 방법 - COM 공개 + dscom으로 TLB를 생성」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635168 https://comcomponent.com/ko/blog/2026/03/16/007-dotnet8-dll-typed-vba-com-dscom-tlb/

DOI(최신 버전)
10.5281/zenodo.21635168
DOI(이 버전)
10.5281/zenodo.22217451

VBA에서 .NET 8의 처리를 호출하고 싶은 상황은 지금도 흔합니다. 특히 Excel이나 Access의 기존 자산은 그대로 남기면서, 무거운 처리, 문자열 처리, HTTP, 암호화, 업무 로직 같은 부분만 C#으로 옮기고 싶을 때입니다.

다만 CreateObject로 지연 바인딩에 치우치면 VBA 쪽은 Object투성이가 됩니다. IntelliSense는 약해지고, 메서드 이름의 오타는 실행 시까지 발견되지 않으며, 점점 문자열 의존의 수렁에 빠집니다.

지연 바인딩의 수렁CreateObject로 지연 바인딩에 치우치면 VBA 쪽은 Object투성이가 되고, IntelliSense가 약해지며, 메서드 이름의 오타가 실행 시까지 발견되지 않는다는 것을 보여주는 그림.CreateObject로 지연 바인딩VBA 쪽은 Object투성이IntelliSense가 약해진다typo가 실행 시까지 발견되지 않는다

그림 1: 지연 바인딩에 치우칠수록 문자열 의존의 수렁에 빠져든다.

그래서 이번에는 .NET 8의 DLL을 COM 공개하고, dscom으로 타입 라이브러리(TLB)를 생성하고, VBA에서 조기 바인딩으로 형 있게 이용하는 부분에 좁혀서 정리합니다.

.NET Framework + RegAsm의 옛날이야기, IDL을 손으로 써서 MIDL로 굳히는 이야기, Reg-Free COM 이야기는 이번에는 옆으로 치워 둡니다. 여기서는 .NET 8 / COM host / dscom / VBA early binding의 외길만 다룹니다.

또한 이 글에 나오는 코드는 빌드·검증할 수 있는 샘플 일체(COM 공개 라이브러리, TLB 생성·등록 스크립트, VBA 모듈, 단위 테스트)로 GitHub에 공개하고 있습니다.

dotnet8-dll-typed-vba-com-dscom-tlb - komurasoft-blog-samples (GitHub)

필요한 환경

항목 필요한 것
OS Windows. COM 등록을 하므로 regsvr32를 관리자 권한으로 실행할 수 있어야 합니다
.NET SDK .NET 8 SDK. EnableComHosting은 .NET 5 이후의 기능입니다
Office Excel 또는 Access. 32bit판인지 64bit판인지 먼저 확인합니다(3장)
TLB 생성 도구 dscom. 64bit용과 32bit용은 입수 방법이 다릅니다(6장)
클라이언트 PC Office와 같은 bitness의 .NET 8 런타임(9장)

순서에 들어가기 전에 자신의 환경 버전을 적어 두는 것을 강력히 권합니다. 나중에 「같은 순서인데 안 된다」가 되었을 때, 비교할 수 있는 정보가 여기에만 있기 때문입니다.

# .NET SDK와 런타임 목록(x64 / x86 중 어느 쪽이 들어 있는지도 알 수 있습니다)
dotnet --info

# Windows 빌드 번호
winver

Office의 버전과 비트는 Excel의 파일 > 계정 > Excel 정보에서 확인할 수 있습니다. 대화상자 제목 줄 끝에 32비트 또는 64비트라고 표시됩니다.

1. 먼저 결론

먼저 결론만 나열하면 흐름은 이렇습니다.

  • .NET 8의 클래스 라이브러리를 EnableComHosting=true로 빌드한다
  • COM에 보일 명시적인 인터페이스클래스를 만든다
  • 클래스는 ClassInterfaceType.None으로 하고, AutoDual로 얼버무리지 않는다
  • VBA에서 쓸 인터페이스는 InterfaceIsDual로 한다
  • 빌드 후 생긴 *.dll에서 dscom tlbexport*.tlb를 만든다
  • regsvr32*.comhost.dll을 등록한다
  • dscom tlbregister*.tlb를 등록한다
  • VBA에서 참조 설정을 추가하고, Dim x As 라이브러리명.IYourInterface처럼 형 있게 쓴다

요컨대 COM의 입구는 .NET SDK가 만드는 *.comhost.dll, 형 정보는 dscom이 만드는 *.tlb, VBA는 그 TLB를 보고 조기 바인딩한다는 구성입니다.

형 있게 쓰기까지의 외길EnableComHosting으로 빌드하고, dscom tlbexport로 TLB를 만들고, regsvr32로 comhost를, dscom tlbregister로 TLB를 등록하고, VBA의 참조 설정에서 형 있게 쓴다는 흐름을 보여주는 그림.EnableComHosting으로 빌드dscom tlbexport로 TLB 생성regsvr32로 comhost 등록dscom tlbregister로 TLB 등록VBA에서 참조 설정해서 형 있게 쓴다

그림 2: 빌드, TLB 생성, 두 개의 등록, 참조 설정 순으로 진행하면 형 있게 호출할 수 있다.

이 글의 지식 맵

.NET 8 클래스 라이브러리를 VBA에서 형 있게 이용하려면 COM의 형 정보인 타입 라이브러리가 필수이며, 이는 dscom이라는 도구가 .NET Framework에서 폐지된 tlbexp.exe나 RegAsm.exe의 후속으로 생성·등록합니다. .NET 쪽은 EnableComHosting으로 빌드한 COM host를 COM의 기동 입구로 삼으며, regsvr32에 의한 등록과 Office와의 bitness 일치가 전제가 됩니다. VBA 쪽은 타입 라이브러리를 참조 설정으로 읽어들임으로써 조기 바인딩을 쓸 수 있고, CreateObject에 의한 지연 바인딩보다 형에 안전하게 쓸 수 있습니다. 공개 후의 호환성은 IID나 CLSID의 처리와 ClassInterfaceType의 선택 방식에 좌우되며, ClassInterfaceType.None과 InterfaceIsDual, DispId의 조합이 VBA 참조의 손상을 피하는 정석입니다.

.NET 8 DLL을 VBA에서 형 있게 쓰는 방법VBA가 조기 바인딩으로 COM을 형 있게 쓰려면 타입 라이브러리가 필요하고, dscom이 그 TLB 생성과 등록을 담당하며, .NET 8 쪽은 COM host로 공개된다는 것, ClassInterfaceType과 IID·CLSID의 처리가 VBA 참조의 호환성에 어떻게 영향을 미치는지를 보여주는 그림전제로 한다전제로 한다이용한다이용한다사용은 비권장구현을 담당한다의 후속에서 구성할 수 있다에서 구성할 수 있다전제로 한다구현을 담당한다이용한다전제로 한다전제로 한다원인이 될 수 있다원인이 될 수 있다원인이 될 수 있다사용은 비권장권장되는 대응완화한다권장되는 대응이용한다에서 확인할 수 있다에서 구성할 수 있다전제로 한다전제로 한다전제로 한다VBA(Visual Basic for Applications)dscom타입 라이브러리(TLB)조기 바인딩(VBA)지연 바인딩(CreateObject)tlbexp.exe / RegAsm.exeCOM host(*.comhost.dll)regsvr32.NET(Core 이후)COM(컴포넌트 오브젝트 모델)IID(인터페이스 식별자)CLSID(Class ID)VBA 참조·등록 손상ClassInterfaceType.AutoDualClassInterfaceType.NoneDispIdAttributeInterfaceIsDual(듀얼 인터페이스)HRESULT.NET 예외ComVisibleAttribute비트수 일치 요건

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 27건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. 이 구성의 전체상

먼저 무엇이 무엇의 역할인지를 그림 한 장으로 봅니다.

참조 설정한 TLB로 형 정보 취득COM 호출VBA / Excel / AccessVbaTypedComSample.tlbVbaTypedComSample.comhost.dllVbaTypedComSample.dll (.NET 8).NET 8 Runtime

그림 3: VBA는 TLB에서 형 정보를 얻고, comhost를 경유해 .NET 8의 구현 본체를 호출한다.

각각의 역할은 다음과 같습니다.

파일 역할
VbaTypedComSample.dll .NET 8의 구현 본체
VbaTypedComSample.comhost.dll COM에서 호출되는 입구
VbaTypedComSample.tlb VBA가 보는 형 정보
VbaTypedComSample.deps.json 의존 관계 해결 정보
VbaTypedComSample.runtimeconfig.json .NET 런타임 기동 정보

여기서 중요한 것은 VBA가 형을 알기 위해 필요한 것은 TLB이고, COM의 기동 입구로 필요한 것은 comhost라는 점입니다.

.dll 하나만 건네고 끝, 이라고는 되지 않는 부분이 COM 세계의 순순하지 않은 점입니다.

3. 처음에 정할 것 - 32bit / 64bit를 맞춘다

여기를 놓치면 상당한 확률로 ActiveX 컴포넌트는 오브젝트를 작성할 수 없습니다. 쪽으로 굴러갑니다.

Office / VBA와 COM 서버의 bitness는 맞춰 주세요.

이용 측 .NET 측 기준 TLB 생성 등록 명령
64bit Office x64 / win-x64 dscom C:\Windows\System32\regsvr32.exe
32bit Office(64bit Windows 상) x86 / win-x86 dscom32.exe C:\Windows\SysWOW64\regsvr32.exe

.NET 5+ 이후의 COM host에서는 AnyCPU 그대로 두면 *.comhost.dll이 64bit 쪽으로 치우치기 쉬워, 32bit Office와 맞물리지 않는 경우가 있습니다. 그래서 Office에 맞춰 x86 / x64를 명시하는 편이 안전합니다.

AnyCPU 방치가 부르는 불일치AnyCPU 그대로 두면 comhost가 64bit 쪽으로 치우치기 쉬워 32bit Office와 맞물리지 않는 경우가 있으므로, Office에 맞춰 x86이나 x64를 명시하는 것이 안전함을 보여주는 그림.AnyCPU 그대로comhost가 64bit 쪽으로 치우치기 쉽다32bit Office와 맞물리지 않는다Office에 맞춰 비트를 명시

그림 4: bitness의 어긋남은 「오브젝트를 작성할 수 없습니다」로 가는 지름길이 된다.

이 글의 코드는 64bit Office용을 예로 합니다. 32bit Office라면 뒤에 나오는 x64x86, win-x64win-x86으로 바꿔 읽어 주세요.

4. .NET 8 쪽을 만든다

여기서는 VBA에서 Add, Divide, Hello를 호출할 수 있는 최소 샘플로 합니다.

4.1 .csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <EnableComHosting>true</EnableComHosting>
    <PlatformTarget>x64</PlatformTarget>
    <NETCoreSdkRuntimeIdentifier>win-x64</NETCoreSdkRuntimeIdentifier>
  </PropertyGroup>
</Project>

포인트는 EnableComHosting입니다. 이것을 붙이면 빌드 시에 VbaTypedComSample.comhost.dll이 생성됩니다.

4.2 어셈블리 전체는 기본값으로 COM 비공개로 해 둔다

COM에 보일 형만 ComVisible(true)로 하고 싶으므로, 어셈블리 전체는 false로 해 두는 것이 편합니다.

using System.Runtime.InteropServices;

[assembly: ComVisible(false)]

4.3 공개할 인터페이스와 클래스를 작성한다

using System.Runtime.InteropServices;

namespace VbaTypedComSample;

[ComVisible(true)]
[Guid("2A1BBEDE-DE6E-4C34-AD60-2E9E0E33E999")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICalculator
{
    [DispId(1)]
    int Add(int x, int y);

    [DispId(2)]
    double Divide(double x, double y);

    [DispId(3)]
    string Hello(string name);
}

[ComVisible(true)]
[Guid("FAD1C752-0BB6-4DDD-889F-FE446350847A")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(ICalculator))]
public class Calculator : ICalculator
{
    public Calculator()
    {
    }

    public int Add(int x, int y) => checked(x + y);

    public double Divide(double x, double y)
    {
        if (y == 0)
        {
            throw new ArgumentOutOfRangeException(nameof(y), "0으로는 나눌 수 없습니다.");
        }

        return x / y;
    }

    public string Hello(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            return "Hello";
        }

        return $"Hello, {name}";
    }
}

이 코드에서 짚어 둘 점은 다음과 같습니다.

  • Guid인터페이스클래스에 각각 따로 부여한다
  • ClassInterfaceType.None으로 해서 자동 생성 클래스 인터페이스에 의존하지 않는다
  • VBA에서 다루기 쉽도록 InterfaceIsDual로 한다
  • DispId를 붙여 두면 공개 후에 메서드 순서를 바꿨을 때의 사고를 줄이기 쉽다
  • COM에서 New되므로 public한 인수 없는 생성자를 준비한다
공개할 형을 구성하는 방법명시적인 인터페이스 ICalculator에 InterfaceIsDual과 DispId를 붙이고, 클래스 Calculator는 ClassInterfaceType.None으로 구현하며, Guid는 인터페이스와 클래스에 각각 따로 부여한다는 구성을 보여주는 그림.구현한다ICalculator〔명시 인터페이스〕InterfaceIsDual과 DispIdCalculator〔클래스〕ClassInterfaceType.NoneGuid는 각각 따로 부여

그림 5: 형 있는 공개의 핵심은 명시 인터페이스와 None을 지정한 클래스의 조합이다.

5. 빌드한다

Release로 빌드합니다.

dotnet build -c Release

빌드 후 출력 폴더에는 적어도 다음과 같은 파일이 놓입니다.

bin/
  Release/
    net8.0-windows/
      VbaTypedComSample.dll
      VbaTypedComSample.comhost.dll
      VbaTypedComSample.deps.json
      VbaTypedComSample.runtimeconfig.json

배포나 등록에 쓰는 것은 이 폴더입니다. 나중에 배치 장소를 바꾸면 등록도 다시 해야 합니다.

6. dscom으로 TLB를 생성한다

6.1 dscom이란 무엇인가

dscom은 .NET 어셈블리에서 COM 타입 라이브러리(TLB)를 생성·등록하기 위한 오픈소스 명령줄 도구입니다. dSPACE사가 공개하고 있으며, 라이선스는 Apache-2.0입니다.

왜 필요한가 하면, .NET 5 이후에는 tlbexp.exeRegAsm.exe가 폐지되었기 때문입니다. .NET Framework 시대에는 이 둘로 TLB 생성과 어셈블리 등록을 할 수 있었지만, .NET 5+에는 그 후속 도구가 표준으로 들어 있지 않습니다. dscom은 그 공백을 메우기 위해 만들어진 도구입니다.

dscom이 메우는 공백.NET Framework 시대는 tlbexp.exe와 RegAsm.exe로 TLB 생성과 어셈블리 등록을 할 수 있었지만, .NET 5 이후에는 폐지되어 후속 도구가 표준에 없으므로 dscom이 그 공백을 메운다는 것을 보여주는 그림..NET Framework 시대tlbexp.exe와 RegAsm.exe.NET 5 이후둘 다 폐지되고 후속 도구 없음dscom이 공백을 메운다

그림 6: TLB 생성 도구는 .NET 5 이후에는 dscom으로 대체되었다.

주요 서브커맨드는 이것만 기억해 두면 충분합니다.

서브커맨드 역할
tlbexport 어셈블리에서 TLB를 내보낸다
tlbregister TLB를 시스템에 등록한다
tlbunregister TLB 등록을 해제한다
tlbdump TLB의 내용을 출력해 확인한다
tlbembed TLB를 파일에 임베드한다

tlbdump는 생성한 TLB에 의도한 형이 들어 있는지를 VBA를 열기 전에 확인하는 데 편리합니다.

6.2 64bit인 경우

64bit TLB만 만들 거라면 dotnet tool로 설치할 수 있습니다.

dotnet tool install --global dscom

다음으로 빌드한 어셈블리에서 TLB를 생성합니다.

dscom tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb

6.3 32bit Office용의 경우 - dscom32.exe는 어디서 입수하는가

여기가 32bit Office 대응에서 가장 막히는 부분입니다.

dotnet tool install로 설치되는 dscom은 AnyCPU 또는 64bit 어셈블리만 다룰 수 있고, 생성할 수 있는 것은 64bit TLB뿐입니다. 32bit TLB를 만들려면 별도의 실행 파일인 dscom32.exe가 필요하며, 이것은 NuGet이 아니라 GitHub의 릴리스 페이지에서 다운로드합니다.

다운로드한 dscom32.exe는 이 글의 예시에서는 프로젝트 바로 아래의 tools 폴더에 두고 있습니다. 두는 위치는 자유지만, 빌드 출력물과 함께 배포하지 않도록 해 주세요. 개발 시 도구일 뿐 실행 시에는 필요 없습니다.

또 하나, 놓치기 쉬운 전제가 있습니다. dscom32.exe를 실행하려면 x86판 .NET 런타임이 설치되어 있어야 합니다. dscom이 hostfxr.dll을 로드하는 사정 때문이며, x64판만 설치된 환경에서는 동작하지 않습니다. dotnet --info 출력에 있는 목록에서 x86 런타임이 있는지 확인해 주세요.

32bit TLB를 만들 준비dotnet tool로 설치되는 dscom은 64bit TLB만 만들 수 있으므로, 32bit TLB는 GitHub의 릴리스 페이지에서 입수한 dscom32.exe로 만들고, 실행하려면 x86판 .NET 런타임이 필요함을 보여주는 그림.32bit TLB가 필요하다릴리스 페이지에서 dscom32.exe 입수x86판 런타임 유무 확인dscom32.exe로 tlbexportdotnet tool판은 64bit TLB 전용

그림 7: 32bit 대응은 도구를 입수하는 경로부터 다르므로 여기서 막히기 쉽다.

.\tools\dscom32.exe tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb

덧붙여 dscom 측 문서에서도, AnyCPU 그대로 두면 *.comhost.dll이 64bit로 생성되므로, 32bit로 쓸 거라면 어셈블리 자체를 32bit로 컴파일하는 것이 권장되고 있습니다. 3장의 이야기와 같은 결론입니다.

빌드할 때마다 손으로 입력하는 것이 번거롭다면, dSPACE.Runtime.InteropServices.BuildTasks 패키지를 넣으면 컴파일 시에 TLB를 자동 생성할 수 있습니다.

7. COM host와 TLB를 등록한다

여기는 관리자 권한의 명령 프롬프트 / PowerShell에서 실행해 주세요.

7.1 64bit Office / 64bit COM의 경우

$out = Resolve-Path .\bin\Release\net8.0-windows

C:\Windows\System32\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
dscom tlbregister "$out\VbaTypedComSample.tlb"

7.2 32bit Office(64bit Windows 상)의 경우

$out = Resolve-Path .\bin\Release\net8.0-windows

C:\Windows\SysWOW64\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
.\tools\dscom32.exe tlbregister "$out\VbaTypedComSample.tlb"

여기서 하고 있는 것은 두 가지입니다.

  • regsvr32*.comhost.dll을 COM 서버로 등록한다
  • tlbregister*.tlb를 타입 라이브러리로 등록한다
등록은 두 갈래관리자 권한으로 regsvr32가 comhost를 COM 서버로 등록하고, dscom tlbregister가 TLB를 타입 라이브러리로 등록한다는 두 개의 등록을 보여주는 그림.관리자 권한으로 실행regsvr32로 comhost 등록tlbregister로 TLB 등록COM 기동 입구의 등록VBA가 보는 형 정보의 등록

그림 8: 기동 입구와 형 정보는 별개이므로, 등록도 둘이 한 쌍을 이룬다.

8. VBA에서 참조 설정해서 형 있게 쓴다

  1. Excel 또는 Access를 연다
  2. Alt + F11로 VBA 편집기(VBE)를 연다. 리본에서 열려면 개발 도구 탭 > Visual Basic입니다. 개발 도구 탭이 보이지 않으면 파일 > 옵션 > 리본 사용자 지정에서 개발 도구에 체크를 넣습니다
  3. VBE 메뉴에서 도구 > 참조
  4. 사용 가능한 참조 목록은 알파벳순으로 나열됩니다. 등록이 성공했다면 그 안에 라이브러리 이름(기본값으로는 어셈블리 이름과 같은 VbaTypedComSample)이 나타나므로, 왼쪽 체크박스를 체크하고 확인
  5. 목록에 보이지 않으면 찾아보기... 버튼에서 VbaTypedComSample.tlb를 직접 선택한다

목록에 나오지 않는 경우, 원인은 대개 3장의 bitness 불일치이거나 7장의 tlbregister가 통과하지 않은 것 중 하나입니다. 32bit Office에서는 64bit로 등록한 TLB가 보이지 않습니다.

참조 설정에 나오지 않을 때의 원인 분리참조 목록에 라이브러리가 나오지 않는 원인은 대개 bitness 불일치이거나 tlbregister가 통과하지 않은 것 중 하나이며, 32bit Office에서는 64bit로 등록한 TLB가 보이지 않음을 보여주는 그림.목록에 라이브러리가 안 나온다3장의 bitness 불일치를 의심7장의 tlbregister 실패를 의심32bit Office에서는 64bit TLB가 안 보인다

그림 9: 목록에 나오지 않는 원인은 거의 bitness나 등록 중 하나로 좁혀진다.

참조 설정이 들어갔는지는 보기 > 개체 브라우저(F2)를 열어, 왼쪽 위의 라이브러리 선택에서 VbaTypedComSample을 고를 수 있는지로 확인할 수 있습니다. 여기서 ICalculatorCalculator, 그리고 Add / Divide / Hello가 보인다면 TLB는 올바르게 만들어진 것입니다.

Option Explicit

Public Sub UseCalculator()
    Dim calc As VbaTypedComSample.ICalculator
    Set calc = New VbaTypedComSample.Calculator

    Debug.Print calc.Add(10, 20)
    Debug.Print calc.Divide(10, 4)
    Debug.Print calc.Hello("VBA")
End Sub

이 프로시저에 커서를 놓고 F5로 실행한 뒤 Ctrl + G로 직접 실행 창을 열면, 4장의 구현대로 3줄이 출력됩니다.

30
2.5
Hello, VBA

여기가 예상대로라면 참조 설정·COM 등록·런타임 기동·인수와 반환값의 마샬링까지 전부 통과한 것입니다. 반대로 여기서 값이 맞지 않는다면 .NET 쪽 구현을, 애초에 실행할 수 없다면 3장과 7장을 의심해 주세요.

실행 결과에 따른 원인 분리샘플의 실행 결과가 예상대로라면 참조 설정부터 마샬링까지 전부 통과한 것이고, 값이 맞지 않으면 .NET 쪽 구현을, 애초에 실행할 수 없으면 3장의 bitness와 7장의 등록을 의심한다는 원인 분리를 보여주는 그림.예상대로값이 맞지 않는다실행할 수 없다VBA 샘플을 실행결과는 어떤가경로는 전부 통과했다.NET 쪽 구현을 의심bitness와 등록을 의심

그림 10: 3줄의 출력만으로 어느 계층을 의심해야 할지 정해진다.

이것으로 VBA 쪽에는 이런 이점이 있습니다.

  • IntelliSense가 동작한다
  • 메서드 이름의 typo를 실행 전에 발견하기 쉽다
  • Object Browser로 공개 API를 확인할 수 있다
  • Object로만 작성하는 것보다 읽기 쉽다

8.1 예외는 VBA 쪽에서는 COM 오류가 된다

예를 들어 Divide(10, 0)처럼 .NET 쪽에서 예외가 던져지면, VBA 쪽에서는 COM 오류로 보입니다.

Option Explicit

Public Sub UseCalculatorWithErrorHandling()
    On Error GoTo EH

    Dim calc As VbaTypedComSample.ICalculator
    Set calc = New VbaTypedComSample.Calculator

    Debug.Print calc.Divide(10, 0)
    Exit Sub

EH:
    Debug.Print Err.Number
    Debug.Print Hex$(Err.Number)
    Debug.Print Err.Description
End Sub

여기서 나오는 값을 읽는 법을 알아 두면 원인 분리가 빨라집니다.

항목 무엇이 들어가는가
Err.Number .NET 예외에 대응하는 HRESULT가 부호 있는 Long으로 들어갑니다. 10진수 그대로는 읽기 어려우므로 Hex$(Err.Number)로 16진수로 바꿉니다
Err.Description COM의 IErrorInfo를 통해 .NET 예외 메시지가 그대로 들어갑니다. 위 코드라면 0으로는 나눌 수 없습니다.를 포함한 문자열입니다

HRESULT 값은 예외 형마다 정해져 있습니다. ArgumentOutOfRangeException에 대응하는 것은 COR_E_ARGUMENTOUTOFRANGE이고, 값은 0x80131502입니다. 즉 Hex$(Err.Number)80131502로 되어 있으면, 예상대로 .NET 쪽의 ArgumentOutOfRangeException이 도착한 것이 됩니다.

주요한 것을 나열하면 이렇습니다.

.NET 예외 HRESULT 상수
ArgumentException COR_E_ARGUMENT 0x80070057
ArgumentOutOfRangeException COR_E_ARGUMENTOUTOFRANGE 0x80131502
InvalidOperationException COR_E_INVALIDOPERATION 0x80131509
NotSupportedException COR_E_NOTSUPPORTED 0x80131515
그 밖의 일반적인 예외 COR_E_EXCEPTION 0x80131500

VBA 쪽에서 예외 종류별로 처리를 나누고 싶다면, 이 HRESULT로 분기하게 됩니다. 다만 HRESULT로 분기하는 설계는 .NET 쪽 예외 형의 변경에 취약하므로, 업무적인 실패는 예외가 아니라 반환값이나 오류 코드로 돌려주는 편이 경계로서는 안정적입니다.

.NET 예외가 VBA에 도달하기까지.NET 쪽에서 던져진 예외는 COM 경계에서 HRESULT로 변환되고, VBA에서는 Err.Number에 그 HRESULT가 부호 있는 Long으로, Err.Description에 IErrorInfo를 통한 예외 메시지가 들어감을 보여주는 그림..NET 쪽에서 예외가 throw된다COM 경계에서 HRESULT로 변환Err.Number에 부호 있는 값으로 들어간다Err.Description에 메시지Hex$로 16진수로 바꿔서 읽는다

그림 11: 예외는 COM 경계에서 HRESULT로 모습을 바꿔 VBA의 Err에 도달한다.

9. 배포할 때의 사고방식

배포 시에 중요한 것은 DLL 하나만 배포하는 것이 아니라 출력물 일체를 두는 것입니다.

VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(필요하다면 의존 DLL 일체)

게다가 클라이언트 PC에는 대응하는 .NET 8 런타임이 필요합니다. COM host는 self-contained 배포가 아니라 기본적으로 framework-dependent한 운영이 됩니다.

구체적으로 넣을 것은 이렇습니다.

  • 배포 페이지는 .NET 8 다운로드입니다
  • 필요한 것은 SDK가 아니라 런타임입니다. 이 글의 샘플은 화면이 없는 클래스 라이브러리이므로 .NET Runtime으로 충분합니다. WPF나 Windows Forms의 형을 사용하는 경우는 .NET Desktop Runtime이 필요합니다
  • bitness는 Office에 맞춥니다. 64bit Office라면 x64, 32bit Office라면 x86 런타임입니다. 3장에서 x64 / x86을 명시한 것과 같은 이유로, 여기도 맞지 않으면 실행되지 않습니다
  • 도입되었는지는 클라이언트 PC에서 dotnet --list-runtimes를 실행해 Microsoft.NETCore.App 8.x 줄이 있는지 보면 알 수 있습니다

배포 자료에는 이 「필요한 런타임의 종류·버전·bitness」를 반드시 적어 두세요. 도입 절차에 적는 것을 잊으면, 현장에서 ActiveX 컴포넌트는 오브젝트를 작성할 수 없습니다.를 보고 bitness를 의심하느라 시간을 낭비하게 됩니다.

배포 시에 갖출 것배포는 DLL 단체가 아니라 출력물 일체를 두고, 클라이언트 PC에는 Office와 같은 bitness의 .NET 8 런타임을 설치하며, 필요한 런타임의 종류와 버전과 bitness를 배포 자료에 적어 둔다는 것을 보여주는 그림.출력물 일체를 배치클라이언트에서 동작같은 bitness의 .NET 8 런타임배포 자료에 런타임 정보를 명기

그림 12: DLL 하나만으로는 동작하지 않고, 일체와 런타임이 갖춰져야 비로소 동작한다.

10. 빠지기 쉬운 함정

10.1 AnyCPU 그대로 방치하지 않는다

VBA / Office의 bitness와 COM host의 bitness가 어긋나면 상당히 찜찜한 방식으로 실패합니다.

  • 64bit Office라면 x64 / win-x64
  • 32bit Office라면 x86 / win-x86

10.2 ClassInterfaceType.AutoDual을 쓰지 않는다

얼핏 편해 보이지만, 공개 후에 멤버 순서나 구성을 건드리면 망가지기 쉽습니다.

VBA에서 형 있게 안정적으로 쓰고 싶다면, 명시 인터페이스를 정의하고 클래스는 ClassInterfaceType.None으로 해 두는 것이 정석입니다.

10.3 GUID를 경솔하게 재생성하지 않는다

COM에서는 GUID가 계약 그 자체입니다. IID나 CLSID를 공개 후에 경솔하게 바꾸면 기존 VBA 참조나 등록이 망가집니다.

10.4 공개된 인터페이스를 망가뜨리지 않는다

COM은 「나중에 메서드를 1개 더했을 뿐」이어도 평온하게 끝나지 않는 경우가 있습니다.

  • ICalculator는 남긴다
  • 변경이 크다면 ICalculator2를 새로 만든다
  • 클래스는 양쪽을 구현해도 된다
공개된 인터페이스를 지키는 방법공개된 ICalculator는 그대로 남기고, 변경이 크다면 ICalculator2를 새로 만들어, 클래스는 양쪽을 구현해도 된다는 호환성 유지 방법을 보여주는 그림.공개된 ICalculator그대로 남긴다큰 변경을 하고 싶다ICalculator2를 새로 만든다클래스는 양쪽을 구현할 수 있다

그림 13: 기존 계약은 남겨 둔 채 새 계약을 옆에 추가하는 것이 COM 방식이다.

10.5 형은 수수한 쪽으로 맞춘다

VBA에 보이는 경계에서는 너무 멋을 부리지 않는 편이 안전합니다.

궁합이 좋은 것은 우선 이 정도입니다.

  • int
  • double
  • bool
  • string
  • DateTime
  • decimal
  • enum

10.6 Office를 연 채로 갱신하지 않는다

Excel이나 Access가 DLL을 붙잡은 채로 있어, 빌드나 재등록에서 번거로움이 생길 수 있습니다.

  • Office를 닫는다
  • 필요하다면 등록을 해제한다
  • 다시 빌드한다
  • 다시 한번 등록한다

등록 해제는 등록했을 때와 역순으로, 등록에 쓴 것과 같은 bitness의 명령으로 수행합니다. 관리자 권한이 필요한 것도 등록 시와 같습니다.

# 64bit Office / 64bit COM의 경우
$out = Resolve-Path .\bin\Release\net8.0-windows

dscom tlbunregister "$out\VbaTypedComSample.tlb"
C:\Windows\System32\regsvr32.exe /u "$out\VbaTypedComSample.comhost.dll"
# 32bit Office(64bit Windows 상)의 경우
$out = Resolve-Path .\bin\Release\net8.0-windows

.\tools\dscom32.exe tlbunregister "$out\VbaTypedComSample.tlb"
C:\Windows\SysWOW64\regsvr32.exe /u "$out\VbaTypedComSample.comhost.dll"

regsvr32/u가 등록 해제 옵션입니다. 등록할 때와 다른 regsvr32를 쓰면 해제할 수 없습니다(64bit로 등록한 것을 SysWOW64regsvr32로는 뗄 수 없습니다). 폴더째 이동·삭제하기 전에 해제해 두지 않으면, 레지스트리에 존재하지 않는 경로의 등록이 남습니다.

갱신 전후의 등록 정리 방법갱신 시에는 Office를 닫고, 필요하다면 등록했을 때와 역순이며 같은 bitness의 명령으로 등록을 해제한 뒤 다시 빌드하고, 다시 한번 등록한다는 흐름을 보여주는 그림.Office를 닫는다필요하다면 역순으로 등록 해제다시 빌드한다다시 한번 등록한다등록 시와 같은 bitness의 명령으로

그림 14: 붙잡힌 DLL과 잔류 등록을 피하기 위해 해제와 재등록을 한 쌍으로 수행한다.

11. 정리

.NET 8의 DLL을 형 있게 VBA에서 쓴다는 이야기는 COM 공개 + dscom으로 TLB 생성에 좁혀 버리면, 그렇게 무서운 절차는 아닙니다. .NET 8 쪽은 EnableComHosting=true로 하고 명시 인터페이스(클래스는 ClassInterfaceType.None, VBA용은 InterfaceIsDual)를 준비하며, dscom tlbexport로 TLB를 만들고, regsvr32*.comhost.dll을, dscom tlbregister*.tlb를 등록합니다. 나머지는 VBA에서 참조 설정을 넣고 조기 바인딩하기만 하면 됩니다.

헷갈릴 때는 COM host와 TLB를 나누어 생각하는 것이 요령입니다.

  • 기동 입구는 *.comhost.dll
  • 형 정보는 *.tlb
  • 구현 본체는 *.dll

12. 참고 자료

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

ActiveX 이관

COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

.NET 8 DLL을 VBA에서 형 있게(조기 바인딩으로) 쓰려면 무엇이 필요합니까?
.NET 8 클래스 라이브러리를 EnableComHosting=true로 빌드해서 *.comhost.dll을 생성하고, dscom tlbexport로 *.tlb를 만듭니다. 다음으로 regsvr32로 *.comhost.dll을, dscom tlbregister로 *.tlb를 등록하고, VBA의 참조 설정에서 그 TLB를 추가하면 Dim x As 라이브러리명.IYourInterface 형태로 형 있게 쓸 수 있습니다. 역할 분담으로 말하면, COM의 기동 입구가 *.comhost.dll, VBA가 보는 형 정보가 *.tlb, 구현 본체가 *.dll입니다.
「ActiveX 컴포넌트는 오브젝트를 작성할 수 없습니다」가 나오는 원인은 무엇입니까?
전형적인 원인은 Office/VBA와 COM 서버의 bitness 불일치입니다. 64bit Office라면 x64/win-x64로 빌드해서 System32의 regsvr32로 등록하고, 32bit Office(64bit Windows 상)라면 x86/win-x86으로 빌드해서 SysWOW64의 regsvr32로 등록하며, TLB 생성도 dscom32.exe를 씁니다. .NET 5+의 COM host에서는 AnyCPU 그대로 두면 *.comhost.dll이 64bit 쪽으로 치우치기 쉬워 32bit Office와 맞물리지 않는 경우가 있으므로, Office에 맞춰 x86/x64를 명시하는 것이 안전합니다.
ClassInterfaceType.AutoDual을 쓰면 안 됩니까?
얼핏 편해 보이지만, 공개 후에 멤버 순서나 구성을 건드리면 망가지기 쉬우므로 피해야 합니다. VBA에서 형 있게 안정적으로 쓰고 싶다면, 명시적인 인터페이스를 정의하고 클래스는 ClassInterfaceType.None으로 하며, VBA에서 쓸 인터페이스는 InterfaceIsDual로 하는 것이 정석입니다. DispId를 붙여 두면 메서드 순서를 바꿀 때의 사고를 줄일 수 있습니다. 또한 COM에서는 GUID가 계약 그 자체이므로, IID나 CLSID를 공개 후에 경솔하게 재생성하면 기존 VBA 참조나 등록이 망가집니다.
배포할 때는 DLL 단체만 건네면 됩니까?
DLL 단체로는 동작하지 않습니다. 구현 본체인 *.dll, *.comhost.dll, *.deps.json, *.runtimeconfig.json, *.tlb, 필요하다면 의존 DLL 일체를 함께 배치합니다. 게다가 클라이언트 PC에는 대응하는 .NET 8 런타임이 필요하며, COM host는 self-contained 배포가 아니라 기본적으로 framework-dependent한 운영이 됩니다. 배치 장소를 나중에 바꾸면 등록도 다시 해야 한다는 점에도 주의가 필요합니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기