Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 10개 있습니다.)
(시리즈 글이 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




Registry 등록 과정 없이 COM 개체 사용 - 두 번째 이야기


예전에, 이 주제와 관련해서 아래와 같이 2가지 글을 쓴 적이 있었는데요.

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

ClickOnce로 ActiveX를 같이 배포하는 방법
; https://www.sysnet.pe.kr/2/0/692

최근에 필요해서 다시 한번 실습해 볼 기회가 있었는데, 2가지 문제가 있음을 발견하게 되었습니다.

첫 번째는, COM 개체의 정의를 담고 있는 manifest 파일이 dll과 같은 경로에 있어야 한다는 점입니다.

보통, COM 프로젝트(예를 들어 결과물이 AtlTest.dll)를 Rebuild하면 대상 프로젝트가 깨끗하게 비워지게 되는데 이로 인해 해당 폴더에 AtlTest.dll.manifest 파일까지 삭제가 되어 버립니다. 이에 대한 문제를 해결하려면 COM 프로젝트에 다음과 같은 변경을 해주어야 합니다.

  • 예를 들어, AtlTest.dll.manifest 파일을 AtlTest.dll.manifest.bak 파일로 COM 프로젝트에 추가
  • 프로젝트 속성의 "Build Events"에 복사 명령어 추가: copy $(ProjectDir)AtlTest.dll.manifest.bak $(TargetDir)AtlTest.dll.manifest

문제는, 두 번째에 있습니다. 현재 웬일인지 Visual Studio 2010에서 manifest 참조를 하게 되면 다음과 같은 오류창이 뜨는 것을 볼 수 있습니다.

how_to_use_regfree_com_1.png

"
Could not load file or assembly '[FileName].dll.manifest' or one of its dependencies. An attempt was made to load a program with an incorrect format.

This file may not be a managed assembly.
"

(참고로, 위의 메시지는 제 컴퓨터에서만 발생하는 것일 수도 있습니다.)

그렇다고 해서 기능 제공이 안 되는 것은 아닙니다. 프로젝트 파일(csproj)에 다음과 같이 강제로 추가해 주면 정상적으로 동작을 합니다. ^^;

<ItemGroup>
    <NativeReference Include="AtlTest.dll, Version=1.0.0.1, ProcessorArchitecture=x86, Type=win32">
        <Name>AtlTest.dll.manifest</Name>
        <HintPath>..\AtlTest\Debug\AtlTest.dll.manifest</HintPath>
    </NativeReference>
</ItemGroup>

위의 과정이 처리된 예제 프로젝트(regfree_com_use_1.zip)를 첨부했으니 참고하십시오.




이번엔, 좀 더 다르게 해결을 해보고 싶었습니다. (이유는 묻지 마세요. ^^ 복잡합니다.) 예를 들어, AtlTest.dll.manifest 파일을 참조하는 경우, Visual Studio는 자동으로 "Interop.AtlTestLib.dll" 라이브러리 파일을 생성합니다.

혹시, 수작업으로 이 과정을 맞출 수 있지 않을까요? 즉, 우리가 직접 Interop 라이브러리를 생성하고 manifest 파일 정의만 적절하게 따라주는 것입니다.

이를 위해서 우선, COM 개체에 대한 Interop 라이브러리를 생성하는 방법을 살펴봐야 하는데요. 주의하셔야 할 것은 일정한 형식이 있다는 점입니다.

D:\...>"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\tlbimp" .\AtlTest\Debug\atltest.dll /out:Interop.AtlTestLib.dll /namespace:AtlTestLib
Microsoft (R) .NET Framework Type Library to Assembly Converter 3.5.30729.1
Copyright (C) Microsoft Corporation.  All rights reserved.

Type library imported to D:\...\bin\x86\Debug\Interop.AtlTestLib.dll

보시는 것처럼, COM 개체의 이름이 "AtlTest"라면, tlbimp.exe의 인자에 다음과 같이 넘겨주어야 합니다.

/out:Interop.[Name].dll
/namespace:[Name]Lib

이제 Interop.AtlTestLib.dll 파일을 C# 프로젝트에서 참조합니다. (.dll.manfest 참조 오류를 벗어날 수 있습니다.)

그다음 manifest 파일을 맞춰주어야 하는데요. 아래와 같이 2개의 파일을 준비해야 합니다.

===== AtlTest.dll.manifest =====

<?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.1" processorArchitecture="x86" 
     name="AtlTest.dll" type="win32">
  </assemblyIdentity>
  
  <file name="AtlTest.dll">
    <comClass clsid="{1DC804F4-7587-45F2-92C8-7470FE6C091B}" threadingModel="Apartment">
    </comClass>
    <typelib tlbid="{6ECA849F-8500-4539-9287-CFB9D07208A2}" version="1.0" 
             helpdir="AtlTest 1.0 Type Library">
    </typelib>
  </file>
  <comInterfaceExternalProxyStub name="ISimpleObject" 
                                 iid="{9E4A5819-446D-4CEE-ADBB-8D1CE6F1B43A}" 
                                 proxyStubClsid32="{00020424-0000-0000-C000-000000000046}" 
                                 baseInterface="{00000000-0000-0000-C000-000000000046}" 
                                 tlbid="{6ECA849F-8500-4539-9287-CFB9D07208A2}">
  </comInterfaceExternalProxyStub>
</assembly>

===== ConsoleApplication1.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="AtlTest.dll.manifest" asmv2:size="1007">
      <assemblyIdentity name="AtlTest.dll" version="1.0.0.1" processorArchitecture="x86" type="win32" />
    </dependentAssembly>
  </dependency>

</asmv1:assembly>

2가지 모두 Visual Studio의 속성창에서 "Copy to Output Directory" 값을 "Copy if newer"로 설정해 줍니다.

manifest 파일 참조가 아니라서 아쉬운 점이 있다면 COM 프로젝트의 빌드 결과물이 자동으로 C# 프로젝트의 산출물 폴더에 복사되지 않는다는 점입니다. 이 문제는 어쩔 수 없이 COM 프로젝트의 "Build Events"에 다음과 같이 설정해 주어야 합니다.

copy $(TargetPath) $(SolutionDir)ConsoleApplication1\bin\Debug

위와 같은 설정 사항들을 한 화면으로 정리해 보면 다음과 같습니다.

how_to_use_regfree_com_2.png

이제 ConsoleApplication1 프로젝트를 빌드하고 실행하면 레지스트리에 등록되지 않은 COM 개체를 정상적으로 사용할 수 있습니다. ^^

위의 구성을 완료한 예제 프로젝트(regfree_com_use_2.zip)도 첨부하였습니다.




요즘 같은 시대에, x64 지원을 빼놓을 수 없지요. 위의 두 번째 예제 환경에서 x64 지원을 할 수 있는 방법을 마저 살펴 보겠습니다.

COM 개체가 x86/x64에 대해 민감하기 때문에 C# 프로젝트까지도 "AnyCPU"로 둘 수는 없는데다, 이름 관리도 잘 해야 하는 문제가 있습니다. 예를 들어, 이번 예제에서는 다음과 같은 이름 규칙을 따른다고 가정하겠습니다.

x86 용 COM 개체: AtlTest.dll
x64 용 COM 개체: AtlTest64.dll

x86 용 C# EXE: ConsoleApplication1.exe
x64 용 C# EXE: ConsoleApplication164.exe

이를 위해서는, 컴파일 결과물에 대한 이름을 x64에 대해서 고려를 해주어야 하는데 C/C++ 프로젝트는 다음과 같이 해결이 간단하게 되지만,

C/C++ 프로젝트

Platform == x64
   Target Name: $(ProjectName)64

C#의 경우에는 csproj 파일을 직접 열어서 다음과 같이 편집해 주어야 합니다.

===== ConsoleApplication1.csproj =====

<AssemblyName>ConsoleApplication1</AssemblyName>
<AssemblyName Condition="'$(Platform)' == 'x64'">ConsoleApplication164</AssemblyName>

아울러 기존 C# 프로젝트에 포함된 2개의 manifest 파일도 별도로 복사해서 내부의 platform 정보를 수정한 후 추가해 주어야 합니다.


===== AtlTest64.dll.manifest =====

<?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.1" processorArchitecture="amd64" 
     name="AtlTest64.dll" type="win32">
  </assemblyIdentity>
  
  <file name="AtlTest64.dll">
    <comClass clsid="{1DC804F4-7587-45F2-92C8-7470FE6C091B}" threadingModel="Apartment">
    </comClass>
    <typelib tlbid="{6ECA849F-8500-4539-9287-CFB9D07208A2}" version="1.0" 
             helpdir="AtlTest 1.0 Type Library">
    </typelib>
  </file>
  <comInterfaceExternalProxyStub name="ISimpleObject" 
                                 iid="{9E4A5819-446D-4CEE-ADBB-8D1CE6F1B43A}" 
                                 proxyStubClsid32="{00020424-0000-0000-C000-000000000046}" 
                                 baseInterface="{00000000-0000-0000-C000-000000000046}" 
                                 tlbid="{6ECA849F-8500-4539-9287-CFB9D07208A2}">
  </comInterfaceExternalProxyStub>
</assembly>


===== ConsoleApplication164.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="AtlTest64.dll.manifest" asmv2:size="1007">
      <assemblyIdentity name="AtlTest64.dll" version="1.0.0.1" processorArchitecture="amd64" type="win32" />
    </dependentAssembly>
  </dependency>

</asmv1:assembly>
(참고로, processorArchitecture="*"로 설정한 경우에는 오류가 발생하였습니다.)

이제, 빌드 결과물에서 ConsoleApplication164.exe를 실행하면 레지스트리에 등록되지 않은 64비트 COM 개체를 정상적으로 로드하는 것을 확인할 수 있습니다.

위의 예제 프로젝트(regfree_com_x64_use_2.zip)도 첨부했으니 참고하시고, 설명은 생략했지만 처음의 manifest 참조방식으로 64비트 COM 개체를 사용하는 예제 프로젝트(regfree_com_x64_use_1.zip)도 첨부해 두었습니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/26/2023]

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

비밀번호

댓글 작성자
 



2011-11-08 01시43분
[스포너] 유용한 정보 감사합니다~ ^^
[guest]
2016-07-11 08시23분
[초보개발자] 안녕하세요. 요번에 ocx룰 사용해야 해서 웹서핑를 하다 여기까지 와서 좋은 정보를 보고 있습니다. 다만 위에 내용을 보다 모르는 부분이 있어 글 남겨요. clsid tlbid 등등 manifest에 입력해야 하는 정보는 어디서 볼수있나요??
[guest]
2016-07-11 08시42분
oleview 도구 등을 통해서 알아내실 수 있습니다. ATL로 만들어진 dll이라면 대개의 경우 리소스로 내장되어 있어 그것을 통해 확인할 수도 있습니다.
정성태
2025-01-15 05시16분
안녕하세요.
WinForm 앱을 만들면서 OCX 등록하지 않고 하는방법은 해결했습니다.
그런데 WinForm DLL 에 OCX를 참고하여 만들어서 할 경우엔 안되네요.
구성은 WinForm EXE 에서 OCX를 참고하여 만든 WinForm DLL을 Assembly.LoadFile()을 이용하여 로드한후 함수 호출하면 COMException: 클래스가 등록되지 않았습니다. 라는 오류가 발생합니다.
mt.exe 로 WinForm DLL에 manifest를 합쳐도 해결이 안되네요.
방법이 없을까요?
미디블루
2025-01-15 09시32분
WinForm DLL에 했던 것을 WinForm EXE 측에 하시면 됩니다. (마치 WinForm DLL이 없다고 생각하세요.)
정성태

... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12211정성태4/27/202019268개발 환경 구성: 486. WSL에서 Makefile로 공개된 리눅스 환경의 C/C++ 소스 코드 빌드
12210정성태4/20/202020712.NET Framework: 903. .NET Framework의 Strong-named 어셈블리 바인딩 (1) - app.config을 이용한 바인딩 리디렉션 [1]파일 다운로드1
12209정성태4/13/202017416오류 유형: 614. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우 (2)
12208정성태4/12/202015983Linux: 29. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우
12207정성태4/2/202015839스크립트: 19. Windows PowerShell의 NonInteractive 모드
12206정성태4/2/202018448오류 유형: 613. 파일 잠금이 바로 안 풀린다면? - The process cannot access the file '...' because it is being used by another process.
12205정성태4/2/202015111스크립트: 18. Powershell에서는 cmd.exe의 명령어를 지원하진 않습니다.
12204정성태4/1/202015112스크립트: 17. Powershell 명령어에 ';' (semi-colon) 문자가 포함된 경우
12203정성태3/18/202017959오류 유형: 612. warning: 'C:\ProgramData/Git/config' has a dubious owner: '...'.
12202정성태3/18/202021211개발 환경 구성: 486. .NET Framework 프로젝트를 위한 GitLab CI/CD Runner 구성
12201정성태3/18/202018441오류 유형: 611. git-credential-manager.exe: Using credentials for username "Personal Access Token". [1]
12200정성태3/18/202018532VS.NET IDE: 145. NuGet + Github 라이브러리 디버깅 관련 옵션 3가지 - "Enable Just My Code" / "Enable Source Link support" / "Suppress JIT optimization on module load (Managed only)"
12199정성태3/17/202016179오류 유형: 610. C# - CodeDomProvider 사용 시 Unhandled Exception: System.IO.DirectoryNotFoundException: Could not find a part of the path '...\f2_6uod0.tmp'.
12198정성태3/17/202019533오류 유형: 609. SQL 서버 접속 시 "Cannot open user default database. Login failed."
12197정성태3/17/202018830VS.NET IDE: 144. .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성 - 두 번째 이야기 [1]
12196정성태3/17/202015950오류 유형: 608. The ServicedComponent being invoked is not correctly configured (Use regsvcs to re-register).
12195정성태3/16/202018269.NET Framework: 902. C# - 프로세스의 모든 핸들을 열람 - 세 번째 이야기
12194정성태3/16/202021000오류 유형: 607. PostgreSQL - Npgsql.NpgsqlException: sorry, too many clients already
12193정성태3/16/202017923개발 환경 구성: 485. docker - SAP Adaptive Server Enterprise 컨테이너 실행 [1]
12192정성태3/14/202019947개발 환경 구성: 484. docker - Sybase Anywhere 16 컨테이너 실행
12191정성태3/14/202021055개발 환경 구성: 483. docker - OracleXE 컨테이너 실행 [1]
12190정성태3/14/202015635오류 유형: 606. Docker Desktop 업그레이드 시 "The process cannot access the file 'C:\Program Files\Docker\Docker\resources\dockerd.exe' because it is being used by another process."
12189정성태3/13/202021238개발 환경 구성: 482. Facebook OAuth 처리 시 상태 정보 전달 방법과 "유효한 OAuth 리디렉션 URI" 설정 규칙
12188정성태3/13/202026026Windows: 169. 부팅 시점에 실행되는 chkdsk 결과를 확인하는 방법
12187정성태3/12/202015605오류 유형: 605. NtpClient was unable to set a manual peer to use as a time source because of duplicate error on '...'.
12186정성태3/12/202017404오류 유형: 604. The SysVol Permissions for one or more GPOs on this domain controller and not in sync with the permissions for the GPOs on the Baseline domain controller.
... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...