VB.NET 프로젝트를 만들면 AssemblyInfo.vb 에 <Assembly: Guid("...")> 로 GUID가 하나 박혀 있습니다. COM에 노출할 때 쓰는 식별자인데, 프로그램을 구분하는 고유 키가 필요할 때 이 값을 읽어 쓰기도 합니다. 그런데 이걸 읽는다고 Assembly.GetType().GUID 를 쓰면 전혀 다른 값이 나옵니다.
Imports System.Runtime.InteropServices
Module _ASM_GUID_
Public Function ASM_GUID() As String
Dim asm As Reflection.Assembly = Reflection.Assembly.GetExecutingAssembly()
''The following line (part of the original answer) is misleading.
''**Do not** use it unless you want to return the System.Reflection.Assembly type's GUID.
'getGUID = assembly.[GetType]().GUID.ToString()
'//fea961de-2778-4732-8de0-9689305de95b
' The following is the correct code.
Dim attribute = CType(asm.GetCustomAttributes(GetType(GuidAttribute), True)(0), GuidAttribute)
Return attribute.Value
End Function
End Module
GetType().GUID가 틀린 이유
Reflection.Assembly.GetExecutingAssembly() 는 지금 실행 중인 어셈블리 객체를 돌려줍니다. 여기서 asm.GetType() 을 부르면 "asm 변수의 타입", 즉 System.Reflection.Assembly(혹은 그 내부 구현 타입) 자체를 가리킵니다. 그 타입에도 GUID 속성이 있어서 값이 나오긴 하는데, 그건 .NET 런타임 타입의 GUID지 내 프로젝트의 GUID가 아닙니다. 그래서 주석에 "오해를 부르니 쓰지 말라"고 적어 둔 겁니다. 컴파일도 되고 실행도 돼서 맞는 줄 알고 넘어가기 쉬운 함정입니다.
올바른 방법
내 프로젝트의 GUID는 <Assembly: Guid(...)> 라는 속성(attribute) 으로 어셈블리에 붙어 있습니다. 그러니 그 속성을 직접 꺼내야 합니다. asm.GetCustomAttributes(GetType(GuidAttribute), True) 가 어셈블리에 붙은 GuidAttribute 들을 배열로 돌려주고, 보통 하나뿐이라 (0) 으로 첫 번째를 꺼내 GuidAttribute 로 형변환합니다. 그 .Value 가 AssemblyInfo.vb 에 적힌 GUID 문자열입니다. 주석의 fea961de-2778-4732-8de0-9689305de95b 가 그렇게 나오는 값의 예시입니다.
쓸 때 참고
이 GUID는 소스에 고정된 값이라 빌드를 다시 해도 바뀌지 않습니다. 그래서 설정 파일 이름이나 레지스트리 키처럼 "이 프로그램"을 가리키는 고정 식별자로 쓰기 좋습니다. 반대로 실행마다 다른 값이 필요하면 Guid.NewGuid() 를 쓰는 거라, 둘을 헷갈리면 안 됩니다.
항목이 없을 가능성도 생각해 두면 좋습니다. AssemblyInfo 에 Guid 속성이 없는 프로젝트라면 GetCustomAttributes 가 빈 배열을 돌려주고, (0) 에서 인덱스 초과 예외가 납니다. 라이브러리처럼 어디에 붙을지 모르는 코드라면 길이를 먼저 확인하세요.
Dim attrs = asm.GetCustomAttributes(GetType(GuidAttribute), True)
If attrs.Length = 0 Then Return String.Empty
Return CType(attrs(0), GuidAttribute).Value
그리고 GetExecutingAssembly 는 이 코드가 들어 있는 어셈블리를 돌려줍니다. 실행 파일(exe)의 GUID를 원한다면 Assembly.GetEntryAssembly() 를, 특정 타입이 속한 어셈블리라면 GetType(어떤클래스).Assembly 를 쓰는 식으로 상황에 맞게 골라야 합니다.
더 짧게 쓰는 방법
.NET Framework 4.5부터는 GetCustomAttribute(Of T) 라는 제네릭 확장 메서드가 있어서 형변환과 배열 인덱스 없이 바로 꺼낼 수 있습니다. 속성이 없으면 예외 대신 Nothing 을 돌려주니 빈 배열 처리도 따로 필요 없습니다.
Imports System.Reflection
Imports System.Runtime.InteropServices
Module AsmGuid
Public Function ASM_GUID2() As String
Dim attr = Assembly.GetExecutingAssembly().GetCustomAttribute(Of GuidAttribute)()
Return If(attr Is Nothing, String.Empty, attr.Value)
End Function
End Module
Imports System.Reflection 이 있어야 이 확장 메서드가 보입니다. 참고로 My.Application.Info 에서 제목, 버전, 회사 이름 같은 어셈블리 정보는 바로 읽을 수 있지만 GUID는 들어 있지 않아서 이렇게 Reflection을 거쳐야 합니다.
이 GUID를 어디에 쓰나
자주 쓰는 곳이 프로그램 중복 실행 방지입니다. New Threading.Mutex(True, "Global\" & ASM_GUID(), createdNew) 처럼 뮤텍스 이름에 넣으면, 같은 프로그램이 이미 떠 있는지를 이름 충돌 없이 판별할 수 있습니다. 프로그램 이름은 다른 프로그램과 겹칠 수 있지만 GUID는 겹칠 일이 사실상 없으니까요.
다만 기존 프로젝트 폴더를 통째로 복사해서 새 프로그램을 만들면 AssemblyInfo.vb 의 GUID도 같이 복사됩니다. 그러면 두 프로그램이 같은 GUID를 갖게 되어 하나를 켜 두면 다른 하나가 "이미 실행 중"이라며 안 뜨는 일이 생깁니다. 프로젝트를 복사했다면 Visual Studio의 도구 메뉴에 있는 GUID 만들기로 새 값을 받아 바꿔 두세요. SDK 형식(.NET 5 이상)의 새 프로젝트는 AssemblyInfo.vb 가 기본으로 없어서 GUID 속성도 없으니, 이 경우엔 빈 문자열이 나오는 게 정상입니다.