Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 3개 있습니다.)
(시리즈 글이 4개 있습니다.)
.NET Framework: 127. ClickOnce로 ActiveX를 같이 배포하는 방법
; https://www.sysnet.pe.kr/2/0/692

개발 환경 구성: 133. Registry 등록 과정 없이 COM 개체 사용 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/1167

개발 환경 구성: 469. Reg-free COM 개체 사용을 위한 manifest 파일 생성 도구 - COMRegFreeManifest
; https://www.sysnet.pe.kr/2/0/12160

개발 환경 구성: 717. Visual Studio - C# 프로젝트에서 레지스트리에 등록하지 않은 COM 개체 참조 및 사용 방법
; https://www.sysnet.pe.kr/2/0/13693




Reg-free COM 개체 사용을 위한 manifest 파일 생성 도구 - COMRegFreeManifest

COM 객체를 regsvr32.exe로 등록하지 않아도, manifest 파일을 통해 직접 생성해 사용할 수 있다고 설명했었습니다.

Registry 등록 없이 COM 개체 사용
; https://www.sysnet.pe.kr/2/1/262

일례로 다음의 글들이 모두 그런 식으로,

C# - VLC(ActiveX) 컨트롤을 레지스트리 등록없이 사용하는 방법
; https://www.sysnet.pe.kr/2/0/1640

eBEST XingAPI의 C# 래퍼 버전 - XingAPINet Nuget 패키지
; https://www.sysnet.pe.kr/2/0/12134

C# - 키움 Open API+ 사용 시 Registry 등록 없이 KHOpenAPI.ocx 사용하는 방법
; https://www.sysnet.pe.kr/2/0/12129

COM 객체를 등록하지 않고 같은 폴더에 있는 dll로부터 로드하고 있습니다. 그런데... 가끔 해보는 거지만 그때마다 manifest 파일을 일일이 만들려니 꽤나... ^^; 귀찮습니다. 그래서 어차피, tlbimp.exe가 생성한 COM Interop DLL만 있으면 나머지는 Reflection으로 COM 객체의 정보를 열람할 수 있기 때문에 manifest 만드는 것을 자동화했습니다.

다음 코드는 간략하게 만들어 본 것이고,

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

namespace COMRegFreeManifest
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length < 1)
            {
                Console.WriteLine("COMRegFreeManifest.exe [com_dll_path]");
                return;
            }

            string path = args[0];

            string currentPath = Environment.CurrentDirectory;
            string dllManifestPath = Path.Combine(currentPath, Path.GetFileName(path));
            string exeManifestPath = Path.Combine(currentPath, "Sample.exe");

            string impLibPath = Path.Combine(Path.GetTempPath(), "test.dll");

            try
            {
                if (RunTlbImp(path, impLibPath) == false)
                {
                    Console.WriteLine("TLBIMP not work");
                    return;
                }

                byte[] buf = File.ReadAllBytes(impLibPath);
                Assembly asm = Assembly.ReflectionOnlyLoad(buf);

                ExportDllManifest(asm, impLibPath, dllManifestPath);
                ExportExeManifest(asm, impLibPath, dllManifestPath, exeManifestPath);
            }
            finally
            {
                if (File.Exists(impLibPath) == true)
                {
                    File.Delete(impLibPath);
                }
            }
        }
        private static void ExportExeManifest(Assembly asm, string impLibPath, string dllManifestPath, string exeManifestPath)
        {
            string manifestTemplate = @"<?xml version=""1.0"" encoding=""utf-8""?>
<asmv1:assembly manifestVersion=""1.0"" xmlns=""urn:schemas-microsoft-com:asm.v1"" xmlns:asmv1=""urn:schemas-microsoft-com:asm.v1"" xmlns:asmv2=""urn:schemas-microsoft-com:asm.v2"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">
  <assemblyIdentity version=""1.0.0.0"" name=""MyApplication.app""/>

  <trustInfo xmlns=""urn:schemas-microsoft-com:asm.v2"">
    <security>
      <requestedPrivileges xmlns=""urn:schemas-microsoft-com:asm.v3"">
        <requestedExecutionLevel level=""asInvoker"" uiAccess=""false"" />
      </requestedPrivileges>
    </security>
  </trustInfo>
  
  <compatibility xmlns=""urn:schemas-microsoft-com:compatibility.v1"">
    <application>
    </application>
  </compatibility>
  
  <dependency>
    <dependentAssembly asmv2:dependencyType=""install"" asmv2:codebase=""{0}.manifest"">
      <assemblyIdentity name=""{0}"" version=""{1}"" type=""win32"" />
    </dependentAssembly>
  </dependency>

</asmv1:assembly>
";


            string dllName = Path.GetFileName(dllManifestPath);
            string version = asm.GetName().Version.ToString();

            string manifestText = string.Format(manifestTemplate, dllName, version);
            File.WriteAllText(exeManifestPath + ".manifest", manifestText);
        }

        private static void ExportDllManifest(Assembly asm, string impLibPath, string dllManifestPath)
        {
            string txt = GetManifestContents(asm, dllManifestPath);
            File.WriteAllText(dllManifestPath + ".manifest", txt);
        }

        static string GetManifestContents(Assembly asm, string dllManifestPath)
        { 
            string manifestTemplate = @"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">

  <assemblyIdentity version=""{0}"" name=""{1}"" type=""win32"">
  </assemblyIdentity>
  
  <file name=""{1}"">
    <comClass clsid=""{2}"" threadingModel=""Apartment"" tlbid=""{3}"" />
    <typelib tlbid=""{3}"" version=""{4}"" helpdir="""" resourceid=""0"" />
  </file>

  {5}
</assembly>
";
            string interfaceTemplate = @"
  <comInterfaceExternalProxyStub name=""{0}"" 
                                 iid=""{1}"" 
                                 proxyStubClsid32=""{{00020424-0000-0000-C000-000000000046}}"" 
                                 baseInterface=""{2}"" 
                                 tlbid=""{3}"">
  </comInterfaceExternalProxyStub>
";

            string version = asm.GetName().Version.ToString();
            string dllName = Path.GetFileName(dllManifestPath);
            string clsid = $"{{{GetClsid(asm)}}}";
            string tlbid = $"{{{GetAssemblyAttr(asm, typeof(GuidAttribute))}}}";
            string tlbVersion = GetTypeLibVersion(asm);

            StringBuilder sb = new StringBuilder();

            foreach (Type type in asm.GetTypes())
            {
                if (type.IsInterface == false || HasCoClassAttribute(type))
                {
                    continue;
                }

                string interfaceName = type.Name;
                string interfaceIid = $"{{{GetGuid(type)}}}";
                string baseInterfaceIid = GetBaseInterfaceIID(type);

                string interfaceText = string.Format(interfaceTemplate, interfaceName, interfaceIid, baseInterfaceIid, tlbid);
                sb.AppendLine(interfaceText);
            }

            string manifestText = string.Format(manifestTemplate, version, dllName, clsid, tlbid, tlbVersion, sb.ToString());

            return manifestText;
        }

        private static string GetBaseInterfaceIID(Type type)
        {
            foreach (CustomAttributeData attr in type.GetCustomAttributesData())
            {
                if (attr.Constructor.DeclaringType == typeof(TypeLibTypeAttribute))
                {
                    TypeLibTypeFlags flags = (TypeLibTypeFlags)attr.ConstructorArguments[0].Value;
                    if (flags.HasFlag(TypeLibTypeFlags.FDual))
                    {
                        return "{00020400-0000-0000-C000-000000000046}";
                    }
                    else
                    { 
                        return "{00000000-0000-0000-C000-000000000046}";
                    }
                }
            }
            return "";
        }

        private static string GetTypeLibVersion(Assembly asm)
        {
            foreach (CustomAttributeData attr in asm.GetCustomAttributesData())
            {
                if (attr.Constructor.DeclaringType == typeof(TypeLibVersionAttribute))
                {
                    return $"{attr.ConstructorArguments[0].Value}.{attr.ConstructorArguments[1].Value}";
                }
            }

            return "";
        }

        private static bool HasCoClassAttribute(Type type)
        {
            foreach (CustomAttributeData attr in type.GetCustomAttributesData())
            {
                if (attr.Constructor.DeclaringType == typeof(CoClassAttribute))
                {
                    return true;
                }
            }

            return false;
        }

        private static string GetClsid(Assembly asm)
        {
            foreach (Type type in asm.GetTypes())
            {
                foreach (CustomAttributeData attr in type.GetCustomAttributesData())
                {
                    if (attr.Constructor.DeclaringType == typeof(CoClassAttribute))
                    {
                        return GetGuid(attr.ConstructorArguments[0].Value as Type);
                    }
                }
            }

            return "";
        }

        private static string GetGuid(Type type)
        {
            foreach (CustomAttributeData attr in type.GetCustomAttributesData())
            {
                if (attr.Constructor.DeclaringType == typeof(GuidAttribute))
                {
                    return attr.ConstructorArguments[0].Value as string;
                }
            }

            return "";
        }

        private static string GetAssemblyAttr(Assembly asm, Type targetType)
        {
            foreach (CustomAttributeData attr in asm.GetCustomAttributesData())
            {
                if (attr.Constructor.DeclaringType == targetType)
                {
                    return attr.ConstructorArguments[0].Value as string;
                }
            }

            return "";
        }

        private static bool RunTlbImp(string path, string impLibPath)
        {
            ProcessStartInfo psi = new ProcessStartInfo();
            psi.FileName = GetTlbImpPath("tlbimp.exe");
            psi.Arguments = $"\"{path}\" /out:\"{impLibPath}\"";
            psi.CreateNoWindow = true;
            psi.UseShellExecute = false;

            Process newProc = Process.Start(psi);
            newProc.WaitForExit();

            return File.Exists(impLibPath);
        }

        private static string GetTlbImpPath(string toolName)
        {
            using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\NETFXSDK", false))
            {
                foreach (string version in regKey.GetSubKeyNames())
                {
                    using (RegistryKey versionKey = regKey.OpenSubKey(version, false))
                    {
                        foreach (string tool in versionKey.GetSubKeyNames())
                        {
                            using (RegistryKey toolKey = versionKey.OpenSubKey(tool))
                            {
                                string path = toolKey.GetValue("InstallationFolder") as string;

                                string tlbimpPath = Path.Combine(path, toolName);
                                if (File.Exists(tlbimpPath) == true)
                                {
                                    return tlbimpPath;
                                }
                            }
                        }
                    }
                }
            }

            return null;
        }
    }
}

실행은 단순하게, COM DLL 파일을 전달하면 됩니다.

c:\temp> COMRegFreeManifest.exe c:\test\my_atl.dll

그럼 my_atl.dll.manifest와 Sample.exe.manifest 파일이 각각 다음과 같은 식으로 생성됩니다.


[my_atl.dll.manifest]

(2024-07-21 업데이트) 참고로, DLL에 대한 manifest는 TLB 파일이 있는 경우 Windows SDK에 포함된 공식 도구인 mt.exe에 의해 생성하는 것도 가능합니다. 예를 들어, 이번 예제의 경우 다음과 같은 식으로 실행할 수 있습니다.

// 현재 디렉터리에 tlb 파일이 있어야 하지만, DLL 파일은 없어도 됨. (DLL 파일 이름을 주는 것은 manifest 내에 필요한 문자열이기 때문임)
c:\temp> mt -tlb:my_atl.tlb -dll:my_atl.dll -out:test.manifset

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

    <assemblyIdentity version="1.0.0.0" name="my_atl.dll" type="win32">
    </assemblyIdentity>

    <file name="my_atl.dll">
        <comClass clsid="{2F71B57A-B6DF-4733-A29A-6ECA13FAE34F}" threadingModel="Apartment" tlbid="{0B2AAC68-8E4B-4BAA-85D7-4DF62A224D9F}" />
        <typelib tlbid="{0B2AAC68-8E4B-4BAA-85D7-4DF62A224D9F}" version="1.0" helpdir="" resourceid="0" />
    </file>


    <comInterfaceExternalProxyStub name="IATLSimpleObject"
                                   iid="{CB82A462-8F49-4434-987B-CB8FBC8A9115}"
                                   proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
                                   baseInterface="{00020400-0000-0000-C000-000000000046}"
                                   tlbid="{0B2AAC68-8E4B-4BAA-85D7-4DF62A224D9F}">
    </comInterfaceExternalProxyStub>


</assembly>


[Sample.exe.manifest]

<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>

    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
        <security>
            <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
                <requestedExecutionLevel level="asInvoker" uiAccess="false" />
            </requestedPrivileges>
        </security>
    </trustInfo>

    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
        </application>
    </compatibility>

    <dependency>
        <dependentAssembly asmv2:dependencyType="install" asmv2:codebase="my_atl.dll.manifest">
            <assemblyIdentity name="my_atl.dll" version="1.0.0.0" type="win32" />
        </dependentAssembly>
    </dependency>

</asmv1:assembly>

오~~~ 제법 폼 나는군요. ^^ 이제 my_atl.dll.manifest 파일은 EXE 프로젝트에 포함해 "Copy to Output Directory" 옵션을 "Copy if newer"로 설정하고 Sample.exe.manifest 파일은 EXE 프로젝트 속성 창의 "Application" 범주의 "Resources" 영역에 "Manifest:" 콤보 박스에 등록해 주시면 됩니다.

(전체 프로젝트는 github에 올렸습니다. - https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/COMRegFreeManifest)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/21/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...
NoWriterDateCnt.TitleFile(s)
1303정성태6/26/201227406개발 환경 구성: 152. sysnet DB를 SQL Azure 데이터베이스로 마이그레이션
1302정성태6/25/201229470개발 환경 구성: 151. Azure 웹 사이트에 사용자 도메인 네임 연결하는 방법
1301정성태6/20/201225767오류 유형: 156. KB2667402 윈도우 업데이트 실패 및 마이크로소프트 Answers 웹 사이트 대응
1300정성태6/20/201231790.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 [1]파일 다운로드2
1299정성태6/18/201232899제니퍼 .NET: 21. 제니퍼 닷넷 - Ninject DI 프레임워크의 성능 분석 [2]파일 다운로드2
1298정성태6/14/201234415VS.NET IDE: 72. Visual Studio에서 pfx 파일로 서명한 경우, 암호는 어디에 저장될까? [2]
1297정성태6/12/201231059VC++: 63. 다른 프로세스에 환경 변수 설정하는 방법파일 다운로드1
1296정성태6/5/201227702.NET Framework: 328. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기 [4]파일 다운로드1
1295정성태6/5/201225088.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기파일 다운로드1
1294정성태5/27/201248549.NET Framework: 326. 유니코드와 한글 - 유니코드와 닷넷을 이용한 한글 처리 [7]파일 다운로드2
1293정성태5/24/201229777.NET Framework: 325. System.Drawing.Bitmap 데이터를 Parallel.For로 처리하는 방법 [2]파일 다운로드1
1292정성태5/24/201223757.NET Framework: 324. First-chance exception에 대해 조건에 따라 디버거가 멈추게 할 수는 없을까? [1]파일 다운로드1
1291정성태5/23/201230289VC++: 62. 배열 초기화를 위한 기계어 코드 확인 [2]
1290정성태5/18/201235085.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [7]파일 다운로드1
1289정성태5/17/201239242.NET Framework: 322. regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항 [5]파일 다운로드2
1288정성태5/17/201226468.NET Framework: 321. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library파일 다운로드1
1287정성태5/17/201229305.NET Framework: 320. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0 [2]
1286정성태5/17/201238241.NET Framework: 319. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU [5]
1285정성태5/16/201233270.NET Framework: 318. gacutil.exe로 어셈블리 등록 시 시스템 변경 사항파일 다운로드1
1284정성태5/15/201225704오류 유형: 155. Windows Phone 연결 상태에서 DRIVER POWER STATE FAILURE 블루 스크린 뜨는 현상
1283정성태5/12/201233320.NET Framework: 317. C# 관점에서의 Observer 패턴 구현 [1]파일 다운로드1
1282정성태5/12/201226113Phone: 6. Windows Phone 7 Silverlight에서 Google Map 사용하는 방법 [3]파일 다운로드1
1281정성태5/9/201233197.NET Framework: 316. WPF/Silverlight의 그래픽 단위와 Anti-aliasing 처리를 이해하자 [1]파일 다운로드1
1280정성태5/9/201226161오류 유형: 154. Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'System.ServiceModel, ...'.
1279정성태5/9/201224919.NET Framework: 315. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 [1]파일 다운로드1
1278정성태5/8/201226151오류 유형: 153. Visual Studio 디버깅 - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...