.NET 8 DLL을 VBA에서 타입이 지정된 상태로 쓰는 방법 - COM 공개와 dscom TLB

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

수정 이력(6건, 최종 수정 2026년 09월 03일)

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

permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
일본어 원문의 완역으로 다시 번역했습니다. 기존 한국어판은 원문의 일부만 옮긴 축약본이라 절, 표, Mermaid 그림, 그림 설명, FAQ가 빠져 있었습니다. 일본어 원문에 맞춰 이들을 모두 복원했고, 기술적인 주장은 일본어판과 같습니다. 수정 전 버전 보기 (DOI: 10.5281/zenodo.21635169)
COM 공개부터 TLB 생성·등록·배포까지의 흐름과 원인 파악을 그림으로도 따라갈 수 있도록 Mermaid 그림 13점을 추가했습니다(본문 500〜750자당 1그림 규약에 맞춘 것입니다). 본문 문장은 바꾸지 않았습니다.
글 맨 앞에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건) 대응으로 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하십시오.
dscom이 무엇인지와 32bit용 `dscom32.exe`를 구하는 방법을 새로 넣었습니다(`dotnet tool` 버전에서는 64bit TLB만 만들 수 있습니다). VBA 실행 결과의 기대값과 `Err.Number` 읽는 법, .NET 예외와 HRESULT 대응표, 참조 설정의 구체적 조작 절차, 등록 해제 절차, .NET 런타임을 구하는 안내를 추가하고, 맨 앞에 필요한 환경 표를 두었습니다.
최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635168)

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

Go Komura (2026). 「.NET 8 DLL을 VBA에서 타입이 지정된 상태로 쓰는 방법 - COM 공개와 dscom TLB」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635168 https://comcomponent.com/ko/blog/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)이 나오므로, 왼쪽 확인란을 켜고 OK
  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은 “나중에 메서드 하나 더한 것뿐”이어도 평화롭게 끝나지 않는 경우가 있습니다.

  • 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 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.

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

Windows 앱 개발

VBA, COM, Office, .NET 8, 형식 라이브러리 생성까지 포함한 연결면 설계는 Windows 앱 개발과 강하게 맞물리므로, Windows 앱 개발과 잘 맞는 주제입니다.

자주 묻는 질문

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

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

블로그 목록으로 돌아가기