엑셀을 64비트로 설치하고 나서 예전 매크로를 돌리면 CreateObject("ScriptControl") 줄에서 "ActiveX 구성 요소는 개체를 만들 수 없습니다" 오류가 납니다. ScriptControl(msscript.ocx)은 32비트용으로만 제공되기 때문에 64비트 엑셀 프로세스 안에서는 만들 수가 없어요. VBA에서 JSON을 파싱하거나 eval을 흉내 내려고 ScriptControl에 JScript를 돌리던 코드가 대부분 여기서 막힙니다.
가장 깔끔한 해결은 ScriptControl을 쓰지 않는 방향으로 코드를 바꾸는 겁니다. 하지만 당장 고칠 매크로가 수십 개라면 그럴 시간이 없죠. 64비트 윈도우에는 32비트 실행 환경(WOW64)이 그대로 들어 있고, 거기에는 ScriptControl도 등록돼 있습니다. 그러니 32비트 프로세스를 하나 띄워서 그쪽에서 개체를 만들고, 그 참조만 엑셀로 받아 오면 됩니다.
32비트 mshta를 대리인으로 쓰는 코드
아래 코드는 script-coding 포럼에 공개된 방법으로, 64비트 VBA에서 32비트 COM 개체를 만들어야 할 때 널리 쓰이는 우회책입니다. CreateObject 대신 CreateObjectx86을 호출하면 됩니다.
Sub Test()
Dim oSC As Object
Set oSC = CreateObjectx86("ScriptControl") ' x86 mshta 호스트를 통해 ActiveX 생성
Debug.Print TypeName(oSC) ' ScriptControl
' 여기서 필요한 작업 수행
CreateObjectx86 Empty ' 작업이 끝나면 mshta 호스트 창 닫기
End Sub
Function CreateObjectx86(sProgID)
Static oWnd As Object
Dim bRunning As Boolean
#If Win64 Then
bRunning = InStr(TypeName(oWnd), "HTMLWindow") > 0
If IsEmpty(sProgID) Then
If bRunning Then oWnd.Close
Exit Function
End If
If Not bRunning Then
Set oWnd = CreateWindow()
oWnd.execScript "Function CreateObjectx86(sProgID): Set CreateObjectx86 = CreateObject(sProgID): End Function", "VBScript"
End If
Set CreateObjectx86 = oWnd.CreateObjectx86(sProgID)
#Else
If Not IsEmpty(sProgID) Then Set CreateObjectx86 = CreateObject(sProgID)
#End If
End Function
Function CreateWindow()
' source http://forum.script-coding.com/viewtopic.php?pid=75356#p75356
Dim sSignature, oShellWnd, oProc
On Error Resume Next
Do Until Len(sSignature) = 32
sSignature = sSignature & Hex(Int(Rnd * 16))
Loop
CreateObject("WScript.Shell").Run "%systemroot%\syswow64\mshta.exe about:""<head><script>moveTo(-32000,-32000);document.title='x86Host'</script><hta:application showintaskbar=no /><object id='shell' classid='clsid:8856F961-340A-11D0-A96B-00C04FD705A2'><param name=RegisterAsBrowser value=1></object><script>shell.putproperty('" & sSignature & "',document.parentWindow);</script></head>""", 0, False
Do
For Each oShellWnd In CreateObject("Shell.Application").Windows
Set CreateWindow = oShellWnd.GetProperty(sSignature)
If Err.Number = 0 Then Exit Function
Err.Clear
Next
Loop
End Function
어떻게 동작하나
흐름을 따라가 보면 생각보다 단순합니다.
CreateWindow는 먼저 16진수 32자리 난수 문자열을 만듭니다. 이게 나중에 띄운 창을 찾아낼 "이름표" 역할을 해요. 그다음 syswow64 폴더의 32비트 mshta.exe를 실행하는데, 넘기는 HTML 안에서 창을 화면 밖(-32000, -32000)으로 보내고 작업 표시줄에도 안 나오게 합니다. 사용자 눈에는 아무것도 보이지 않습니다.
그 HTML에 심은 object는 RegisterAsBrowser 옵션이 켜진 브라우저 컨트롤이라 Shell.Application.Windows 목록에 나타납니다. 이 컨트롤에 putproperty로 이름표와 함께 자신의 window 개체를 걸어 둡니다. VBA 쪽은 Shell 창 목록을 계속 돌면서 GetProperty(이름표)로 그 window를 꺼낼 수 있을 때까지 기다리는 거죠.
window를 손에 넣으면 execScript로 32비트 프로세스 안에 VBScript 함수 하나를 정의합니다. 이 함수가 그쪽에서 CreateObject를 대신 호출하고, 만들어진 개체 참조가 프로세스 경계를 넘어 엑셀로 돌아옵니다. Static oWnd 덕분에 두 번째 호출부터는 이미 떠 있는 mshta를 재사용하고, #If Win64 조건부 컴파일 때문에 32비트 오피스에서는 그냥 평범한 CreateObject로 동작합니다. 같은 파일을 32비트, 64비트 PC에 모두 배포해도 된다는 점이 배포할 때 편합니다.
쓰면서 조심할 부분
다 쓰고 나면 CreateObjectx86 Empty를 꼭 호출하세요. 안 그러면 보이지 않는 mshta 프로세스가 엑셀을 닫은 뒤에도 남아 있습니다. 작업 관리자에 mshta.exe가 여러 개 쌓여 있다면 이 호출이 빠진 겁니다.
CreateWindow의 바깥 Do ... Loop에는 탈출 조건이 없습니다. 보안 프로그램이 mshta 실행을 막거나 그룹 정책으로 차단된 PC라면 창을 영원히 찾지 못하고 엑셀이 멈춥니다. 회사 PC에 배포할 거라면 시작 시간을 재서 몇 초가 지나면 빠져나오도록 시간 제한을 넣어 두는 걸 권합니다. 또 개체가 별도 프로세스에 있으므로 메서드 호출 하나하나가 프로세스 간 통신이 됩니다. 반복문 안에서 수천 번 호출하면 같은 코드라도 32비트 엑셀에서 직접 쓸 때보다 확실히 느립니다.
애초에 32비트와 64비트 중 무엇을 깔아야 하나
VBA를 많이 쓰는 환경이라면 32비트 오피스가 속 편합니다. Microsoft도 Office 2013 배포 가이드에서 대부분의 사용자에게 32비트를 권장했는데, 기존 ActiveX 컨트롤이나 COM 추가 기능과의 호환성이 이유였습니다(이후 버전부터는 64비트가 기본 설치로 바뀌었으니 새로 받을 때 버전 선택을 직접 확인해야 합니다). 당시 문서에서 정리한 차이를 추리면 이렇습니다.
| 구분 | 내용 |
|---|---|
| 64비트가 유리한 경우 | 2GB 가상 주소 공간 제한을 넘는 대용량 통합 문서나 큰 Project 파일을 다룰 때, 64비트용 사내 솔루션을 개발·테스트해야 할 때 |
| 보안 | 64비트 설치는 하드웨어 DEP가 항상 적용됨(32비트는 그룹 정책으로 설정) |
| VBA | Declare 문을 쓰는 코드는 64비트용으로 수정 필요 |
| ActiveX/COM | 32비트용 컨트롤과 추가 기능은 64비트 프로세스에서 동작하지 않음 |
| Access | 소스가 제거된 .mde, .ade, .accde 파일은 32/64비트 간 호환 안 됨 |
| Outlook | 32비트 MAPI 애플리케이션은 다시 작성해야 함 |
| 기타 | Word 레거시 수식 편집기와 WLL 추가 기능은 64비트에서 지원 안 됨, OLE 서버의 비트 수가 오피스와 다르면 개체 삽입이 실패할 수 있음 |
| 설치 | 64비트 오피스는 64비트 윈도우에서만 가능, 같은 PC에 32비트와 64비트 오피스 병행 설치 불가, 전환하려면 제거 후 재설치 |
이미 64비트가 깔려 있고 바꿀 수 없다면 위 우회 코드로 버티면서, 장기적으로는 ScriptControl 의존을 걷어 내는 쪽(JSON 파싱은 순수 VBA 파서를 쓰는 식)으로 옮겨 가는 게 맞습니다.
VBA를 처음부터 다시 정리하고 싶을 때 찾아볼 만한 책들입니다.
참고: script-coding 포럼 원본 코드 | Microsoft - Office 2013의 64비트 버전 선택