.NET 8의 DLL을 형 있게 VBA에서 쓰는 방법 - COM 공개 + dscom으로 TLB를 생성
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
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
VBA란 무엇인가 - 제약, 장래성, 교체해야 할 장면과 현실적인 이행 패턴
VBA의 강점과 제약을 정리하고, Excel for the web 미지원이나 매크로 차단 같은 현실을 전제로 어디까지 VBA에 남기고 무엇을 .NET·Office Scripts·Office Add-ins로 분리할지, 단계 이행의 판단 기준과 진행...
Excel 장표 출력을 어떻게 만들까 - COM 자동화 / Open XML / 템플릿 방식의 판단표
Excel 장표 출력에서 COM 자동화·Open XML·템플릿 차입·기존 VBA 병용을 어떻게 가려 쓸지를 정리합니다. 사용자 PC와 무인 실행, 레이아웃 재사용 같은 요건별로 권장 구성과 빠지기 쉬운 곳까지 짚어 방식 선정의 판단표로 활용할 수...
COM 컴포넌트나 OCX / ActiveX 개발에서 빠지기 쉬운 것 - Visual Studio의 32bit / 64bit, 등록, 관리자 권한의 덫을 정리
COM 컴포넌트와 ActiveX, OCX 개발에서 자주 만나는 0x80040154나 0x80070005를 비트 수, 등록 방식, HKCU와 HKLM 스코프, 관리자 권한이라는 네 축으로 풀어 Visual Studio 2022의 64bit화 시대에...
Windows 앱에서 자식 프로세스를 안전하게 다루기 위한 체크리스트 - Job Object, 종료 전파, 표준 입출력, watchdog의 베스트 프랙티스
Windows 앱이 자식 프로세스에 의존할 때, 기동 API보다 프로세스 트리의 소유권과 종료 절차의 설계가 안정성을 좌우합니다. Job Object로 수명을 묶고, 종료 전파를 분리하며, stdout/stderr를 비동기로 흘리고 watchdo...
시리얼 통신 앱의 함정 - 1 byte 단위, 타임아웃, 플로우 컨트롤, 재접속, USB 변환, UI 프리즈를 먼저 정리
시리얼 통신 앱이 가끔 멈추거나 응답이 어긋나는 진짜 원인은 byte stream의 메시지 경계, 타임아웃, 재접속, single writer 설계에 있습니다. 실무에서 무너지기 쉬운 함정과 먼저 정리할 체크리스트를 한 번에 정리했습니다.
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
ActiveX 이관
COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
기존 자산 활용 & 이관 지원
COM / ActiveX / OCX 자산, 네이티브 코드, 32비트 의존성을 유지하면서 단계적인 이관 계획을 지원합니다.