Visual Studio Community 2022에서 DLL 만든 후 Excel에서 사용

배경: 64비트 Windows 11 및 Office 환경에서의 C++ DLL 제작

예전 Visual Studio 2008에서 Simple DLL 만든 후 Excel에서 사용하기 글을 통해 DLL 생성 후 엑셀에서 Declare 문을 통해 잘 사용했었다.

그러나!!!

당시 사용하던 Windows OS는 Windows XP, 그것도 32bit OS 이었고 Office 역시 32bit 버전이 대세였으나 현재는 Windows 10 64bit에 Office 역시 64bit가 대세를 형성하고 있다. 1

이와 같은 상황에서 예전 글을 참고해서, 그것도 10여 년 전 버전의 Visual Studio 2008 버전으로 DLL을 만들어 2022년 현재의 Windows 11 64bit + Office 2016 64bit 조합에 들이대기 영 껄쩍지근하던 차에 아래의 글을 발견하고 차례로 적용하여 성공한 기억을 잊어 먹지 않기 위해 글로 남긴다.


사전 주의사항: C++ 함수 직접 호출 시 엑셀 Crash 방지 (래퍼 함수 필수)

참고로 본인은 본 글 제목에서처럼 Visual Studio Community 2022 버전으로 시도했으니 참고하기를. 아, 또 하나는 상기 글을 작성한 저자가 아래와 같은 주의사항을 강한 늬앙스로 명기하고 있으니 이 역시 참고하기를.

Note: Be sure to change the path after Lib to the location of .dll on your own computer.

Above we define a function get_square_cpp which exposes our .dll file’s get_square function. Notice that we pass the argument by reference, as our C++ code expects a pointer. We then define a wrapper function named get_square which ensures that any data passed to our get_square_cpp is the expected “double” data type. Using get_square_cpp directly without the help of the explicitly typed parameters of the wrapper function can result in data being incorrectly marshalled to our C++ function, causing Excel to crash.

본인이 시도하여 성공한 순서대로 적고자 한다.


1단계: Visual Studio Community 2022에서 C++ DLL 프로젝트 생성

  1. 프로그램을 시작하여 ‘새 프로젝트 만들기(N)‘ 클릭
    Visual Studio 2022 시작 화면 — '새 프로젝트 만들기' 선택
  2. ‘새 프로젝트 만들기‘ 창 상단 검색 바에서 ‘라이브러리‘ 입력 후 나타나는 목록 중 ‘DLL(동적 연결 라이브러리)‘ 선택 후 ‘다음‘ 클릭
    새 프로젝트 만들기 — '라이브러리' 검색 후 'DLL(동적 연결 라이브러리)' 템플릿 선택, 다음 클릭
  3. ‘새 프로젝트 구성‘ 창 내 ‘프로젝트 이름(J)‘에 ‘MyUtil‘ 입력 후 ‘만들기(C)‘ 클릭
    새 프로젝트 구성 — 프로젝트 이름 MyUtil, 위치 C:UsersKiinsourcerepos, 만들기 클릭

2단계: C++ 헤더(MyUtil.h) 및 소스(MyUtil.cpp) 구현

  1. 메인 메뉴에서 ‘프로젝트(P)‘ ⇒ ‘새 항목 추가(W)…‘ 클릭
    Visual Studio 프로젝트 메뉴 — '새 항목 추가...(Ctrl+Shift+A)' 선택
  2. ‘새 항목 추가‘ 창에서 ‘헤더 파일(.h)‘ 선택하고 하단 ‘이름(N)‘ 입력란에 ‘MyUtil.h‘ 입력 후 ‘추가(A)‘ 클릭
    새 항목 추가 — Visual C++ 헤더 파일(.h) 선택, 이름 MyUtil.h, 추가 클릭
  3. 메인 창 ‘MyUtil.h‘ 탭 안에 아래 내용 입력
    // MyUtil.h - 함수들에 대한 선언 포함
    #pragma once
    
    #ifdef MYUTIL_EXPORTS
    #define MYUTIL_API __declspec(dllexport)
    #else
    #define MYUTIL_API __declspec(dllimport)
    #endif
    
    extern "C" MYUTIL_API double GetSquare(double* x);
    extern "C" MYUTIL_API double MyX(double* a, double* b);

    MyUtil.h 편집 화면 — MYUTIL_EXPORTS 조건부 dllexport/dllimport 매크로와 extern "C" MYUTIL_API double get_square(double* x); 선언

  4. 우측 ‘솔루션 탐색기‘ 창에서 마우스 우클릭으로 ‘소스 파일‘ 클릭 후 ‘추가‘ ⇒ ‘새 항목(W)…‘ 클릭
    솔루션 탐색기 소스 파일 우클릭 — 추가 > 새 항목 메뉴 선택
  5. ‘새 항목 추가‘ 창에서 ‘C++ 파일(.cpp)‘ 선택하고 하단 ‘이름(N)‘ 입력란에 ‘MyUtil.cpp‘ 입력 후 ‘추가(A)‘ 클릭
    새 항목 추가 — C++ 파일(.cpp) 선택, 이름 MyUtil.cpp, 추가 클릭
  6. 메인 창 ‘MyUtil.cpp‘ 탭 안에 아래 내용 입력
    // MyUtil.cpp : DLL로 내보낸 (exported) 함수 정의.
    #include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier
    #include "MyUtil.h"
    #include <cmath>
    
    double WINAPI GetSquare(double* x)
    {
        return *x * *x;
    }
    
    double WINAPI MyX(double* a, double* b)
    {
        if (*a > *b)
            return pow(*a, *b);
        else
            return pow(*b, *a);
    }

    MyUtil.cpp 편집 화면 — GetSquare(제곱 반환)와 MyX(큰 값의 거듭제곱 반환) 함수 정의


3단계: x64(64비트) 빌드 및 DLL 생성

  1. 메인 메뉴에서 ‘빌드‘ ⇒ ‘솔루션 빌드(B)‘ 클릭
    빌드 메뉴 — '솔루션 빌드(Ctrl+Shift+B)' 선택 (우측 하단에 Windows 보안 알림 표시)
  2. 상단 도구 모음에서 ‘x64‘ 확인 후 하단 ‘출력‘ 창에서 빌드 성공 메시지 확인
    x64 Debug 빌드 결과 — 출력 창에 MyUtil.lib·MyUtil.exp 생성과 MyUtil.dll 빌드 성공(1 성공, 0 실패) 메시지

4단계: Excel VBA에서 PtrSafe Declare 및 래퍼 함수 작성

엑셀을 시작하여 ‘MyUtil.xlsb’란 새 이름으로 저장하고 ‘ALT + F11’ 이용하여 나타난 Microsoft Visual Basic for Application 창에 아래 내용을 입력한다. 2

Option Explicit
	
' Win64는 64비트 오피스인지 판별하는 상수
' VBA7은 오피스 2010 이상인지 판별하는 상수
#If Win64 Then
    Private Declare PtrSafe Function GetSquareCpp Lib "C:UsersKiinsourcereposMyUtilx64DebugMyUtil.dll" _
        Alias "GetSquare" (ByRef Arg As Double) As Double

    Private Declare PtrSafe Function MyXCpp Lib "C:UsersKiinsourcereposMyUtilx64DebugMyUtil.dll" _
        Alias "MyX" (ByRef Arg1 As Double, ByRef Arg2 As Double) As Double
#Else ' x86으로 Build한 DLL 선언
    Private Declare Function GetSquareCpp Lib "C:UsersKiinsourcereposMyUtilx86DebugMyUtil.dll" _
        Alias "GetSquare" (ByRef Arg As Double) As Double

    Private Declare Function MyXCpp Lib "C:UsersKiinsourcereposMyUtilx86DebugMyUtil.dll" _
        Alias "MyX" (ByRef Arg1 As Double, ByRef Arg2 As Double) As Double
#End If

' ByRef가 기본이나 하나의 셀에 있는 값을 인수로 받기 때문에 명확하게 하기 위해 ByVal 사용
Public Function GetSquare(ByVal x As Double) As Double
    GetSquare = GetSquareCpp(x)
End Function

' ByRef가 기본이나 하나의 셀에 있는 값을 각각 인수로 받기 때문에 명확하게 하기 위해 ByVal 사용
Public Function MyX(ByVal Arg1 As Double, ByVal Arg2 As Double) As Double
    MyX = MyXCpp(Arg1, Arg2)
End Function

Sub tmp()
    MsgBox GetSquare(0.2)
    MsgBox MyX(0.8, 0.2)
End Sub

VBA 편집기 Module1 — #If Win64 분기로 MyUtil.dll의 GetSquareCpp·MyXCpp를 Declare PtrSafe 선언하고 GetSquare·MyX 공용 함수로 감싼 코드


5단계: 엑셀 워크시트 연동 및 결과 검증

실제로 엑셀 시트에 함수를 적용하여 정상 작동을 확인했다.

Excel 시트에서 DLL 함수 테스트 — GetSquare(D5)=0.04(POWER와 동일), MyX(D5,D6)=0.956352(POWER와 동일), FORMULATEXT()로 수식 확인

예전과는 다른 방법으로 엑셀에서 사용하려는 DLL을 생성했으며 또한 과거 종종 DLL 사용하던 엑셀이 Crash로 죽던 경우가 본 방법을 통해 생성한 DLL 사용 시 아직까지는 발생하지 않고 있다.

조회수: 28

Footnotes

  1. 본인 주변을 돌아봤을 때를 전제로 한 판단이다. 아니면 말고.
  2. 캡처 이미지 상으로 붉은 부분이 있어서 오류라고 착각할 수도 있으나 실제 사용하는 데에는 전혀 지장을 주지 않는다.

댓글 남기기