VBA에서 .NET 8의 처리를 호출하고 싶은 장면은 아직 평범하게 있습니다. 특히 Excel이나 Access의 기존 자산은 그대로 남기면서, 무거운 처리, 문자열 처리, HTTP, 암호화, 업무 로직 같은 부분만 C#으로 빼내고 싶을 때입니다.
다만 CreateObject로 지연 바인딩으로 치우치면 VBA 쪽에서는 Object투성이가 됩니다. IntelliSense는 약해지고, 메서드 이름의 오타는 실행 시까지 발견되지 않으며, 점점 문자열 의존의 진흙탕에 빠집니다.
그래서 이번에는 .NET 8의 DLL을 COM 공개하고, dscom으로 타입 라이브러리(TLB)를 생성하고, VBA에서 조기 바인딩으로 형 있게 이용하는 곳에 좁혀서 정리합니다.
.NET Framework + RegAsm의 옛날이야기, IDL을 손으로 쓰고 MIDL로 굳히는 이야기, Reg-Free COM의 이야기는 이번에는 옆에 둡니다. 여기서는 .NET 8 / COM host / dscom / VBA early binding의 외길만 다룹니다.
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를 보고 조기 바인딩한다는 구성입니다.
2. 이 구성의 전체상
우선 무엇이 무엇의 역할인지를 한 장으로 봅니다.
flowchart LR
VBA["VBA / Excel / Access"] -->|참조 설정한 TLB로 형 정보 취득| TLB["VbaTypedComSample.tlb"]
VBA -->|COM 호출| COMHOST["VbaTypedComSample.comhost.dll"]
COMHOST --> DOTNET["VbaTypedComSample.dll (.NET 8)"]
DOTNET --> RUNTIME[".NET 8 Runtime"]
각각의 역할은 다음과 같습니다.
| 파일 | 역할 |
|---|---|
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를 명시하는 편이 안전합니다.
이 글의 코드는 64bit Office용을 예로 합니다. 32bit Office라면 뒤에 나오는 x64를 x86, win-x64를 win-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한 인수 없는 생성자를 준비한다
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 64bit인 경우
우선 dscom을 넣습니다.
dotnet tool install --global dscom
다음으로 빌드한 어셈블리에서 TLB를 생성합니다.
dscom tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
6.2 32bit Office용의 경우
여기는 조금 덫입니다. 32bit Office용으로 TLB를 만든다면 dscom32.exe를 쓰는 것이 안전합니다.
.\tools\dscom32.exe tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.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"
여기서 하는 것은 2가지입니다.
regsvr32로*.comhost.dll을 COM 서버로서 등록tlbregister로*.tlb를 타입 라이브러리로서 등록
8. VBA에서 참조 설정해서 형 있게 쓴다
- Excel 또는 Access를 연다
- VBA 에디터를 연다
도구->참조 설정- 목록에 라이브러리가 나와 있으면 체크를 넣는다
- 목록에 안 보이면
참조...에서VbaTypedComSample.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
이것으로 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 Err.Description
End Sub
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한 운영이 됩니다.
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를 새로 만든다 - 클래스는 양쪽을 구현해도 된다
10.5 형은 수수하게 치우친다
VBA에 보이는 경계에서는 너무 폼을 잡지 않는 편이 안전합니다.
상성이 좋은 것은 우선 이쯤입니다.
intdoubleboolstringDateTimedecimalenum
10.6 Office를 연 채로 갱신하지 않는다
Excel이나 Access가 DLL을 붙잡은 채가 되어, 빌드나 재등록에서 번거로움이 일어날 수 있습니다.
- Office를 닫는다
- 필요하다면 등록 해제한다
- 빌드를 다시 한다
- 다시 한 번 등록한다
11. 정리
.NET 8의 DLL을 형 있게 VBA에서 쓴다는 이야기를 COM 공개 + dscom으로 TLB 생성에 좁히면, 할 일은 의외로 정리할 수 있습니다.
- .NET 8 쪽에서는
EnableComHosting=true - COM에 보일 명시 인터페이스를 만든다
- 클래스는
ClassInterfaceType.None - VBA용 인터페이스는
InterfaceIsDual dscom tlbexport로 TLB를 만든다regsvr32로*.comhost.dll을 등록dscom tlbregister로*.tlb를 등록- VBA에서 참조 설정을 넣고 조기 바인딩한다
요컨대 .NET 8 시대의 VBA 연계는 COM host와 TLB를 나누어 생각하는 것이 요령입니다.
- 기동 입구는
*.comhost.dll - 형 정보는
*.tlb - 구현 본체는
*.dll
12. 참고 자료
- Expose .NET components to COM - Microsoft Learn
- Qualify .NET types for interoperation - Microsoft Learn
- ComInterfaceType enumeration - Microsoft Learn
- ClassInterfaceType enumeration - Microsoft Learn
- COM callable wrapper - Microsoft Learn
- DispIdAttribute class - Microsoft Learn
- dscom - NuGet Gallery
- How to use the Regsvr32 tool and troubleshoot Regsvr32 error messages - Microsoft Support
- .NET 8 downloads
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
C#의 Excel 조작에서 EXCEL.EXE가 남는 문제 ── COM 참조 해제 패턴과 교체 판단
C#에서 Microsoft.Office.Interop.Excel로 Excel을 조작하면 EXCEL.EXE 프로세스가 남는 문제를, COM 참조 카운트와 RCW의 원리로 정리합니다. 2닷 규칙의 함정, Marshal.ReleaseComObject파...
Windows의 프로세스 간 통신을 어떻게 선택할까 ── 네임드 파이프 / TCP / gRPC / 공유 메모리 / COM 판단표
Windows 앱 사이의 연동 수단을 어떻게 선택할지 정리합니다. 네임드 파이프, 로컬 TCP, gRPC, 공유 메모리, 파일 연동, COM 각각의 강점과 함정을 판단표로 정리하고, UI+서비스 분리・32bit/64bit 브리지・권한 경계 같은 ...
Power Automate로 업무 자동화하기 ── 클라우드 플로우・데스크톱 플로우 구분과 오류 처리 설계
Power Automate의 클라우드 플로우와 데스크톱 플로우의 차이, PowerShell/VBA와의 역할 분담, 라이선스, 오류 처리, UI 자동화 안정화, 자격 증명의 안전한 처리까지, 업무 자동화를 운영에 올리기 위한 실전 설계 지침을 정리...
Windows 앱 외주·수탁 개발을 의뢰하기 전에 정리해야 할 것
Windows 앱의 외주·수탁 개발을 의뢰하기 전에, 기존 소프트웨어 개수, 장치 연계, COM/ActiveX, 배포·업데이트, 보수와 관련해 정리해야 할 포인트를 설명합니다.
Office 2024/Microsoft 365에서 ActiveX가 작동하지 않는 원인과 확인 절차
Office 2024/Microsoft 365에서 ActiveX가 작동하지 않을 때, 기본값 비활성화, 32bit/64bit, COM 등록, 종속 DLL, IE 모드, Click-to-Run 로그를 확인하는 순서를 정리합니다.
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
ActiveX 이관
COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
기존 자산 활용 & 이관 지원
COM / ActiveX / OCX 자산, 네이티브 코드, 32비트 의존성을 유지하면서 단계적인 이관 계획을 지원합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- VBA에서 .NET 8 DLL을 호출하려면 어떻게 하나요?
- .NET 8 클래스 라이브러리를 EnableComHosting=true로 빌드해 COM 공개하고, dscom으로 타입 라이브러리(TLB)를 생성합니다. 그다음 regsvr32로 *.comhost.dll을, dscom tlbregister로 *.tlb를 등록하고, VBA에서 참조 설정을 추가하면 조기 바인딩으로 형 있게 호출할 수 있습니다. 요컨대 기동 입구는 comhost.dll, 형 정보는 TLB, 구현 본체는 .NET DLL이라는 구성입니다.
- dscom이란 어떤 도구인가요?
- .NET 어셈블리에서 타입 라이브러리(TLB)를 생성·등록하는 도구입니다. dotnet tool install --global dscom으로 설치하고, dscom tlbexport로 TLB를 만든 뒤 dscom tlbregister로 등록합니다. 32bit Office용 TLB를 만들 때는 dscom32.exe를 쓰는 것이 안전합니다.
- 'ActiveX 컴포넌트는 오브젝트를 작성할 수 없습니다' 에러가 나는 원인은 무엇인가요?
- 대표적인 원인은 Office/VBA와 COM 서버의 bitness(32bit/64bit) 불일치입니다. 64bit Office라면 x64/win-x64, 32bit Office라면 x86/win-x86으로 명시해 빌드하고, 등록도 대응하는 regsvr32를 써야 합니다. .NET 5+ 이후의 COM host에서는 AnyCPU 그대로 두면 comhost.dll이 64bit 쪽으로 치우쳐 32bit Office와 맞물리지 않는 경우가 있습니다.
- 만든 COM DLL을 다른 PC에 배포할 때 무엇이 필요한가요?
- DLL 단체가 아니라 .dll, .comhost.dll, .deps.json, .runtimeconfig.json, .tlb 등 빌드 출력 일식을 함께 배치해야 합니다. 클라이언트 PC에는 대응하는 .NET 8 런타임도 필요합니다. 또한 배치 장소를 나중에 바꾸면 regsvr32와 tlbregister 등록을 다시 해야 합니다.
저자 프로필
기사 저자의 프로필 페이지입니다.
Go Komura
합동회사 코무라소프트 대표
Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.
공개 링크