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




Visual Studio - C# 프로젝트에서 레지스트리에 등록하지 않은 COM 개체 참조 및 사용 방법

테스트를 위해 ATL 프로젝트를 하나 생성했는데, 약간의 문제는 있었지만 HelloWorld 함수를 하나 추가한 COM 서버 예제를 만들었습니다.

// ...[생략]...

[
    object,
    uuid(53ad801e-eaa0-49b5-80b0-0a00222ec0a4),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IATLSimpleObject : IDispatch
{
    [id(1)] HRESULT HelloWorld([in] BSTR text);
};
[
    uuid(240e2ba0-1248-48d2-9165-0fdb5d40c710),
    version(1.0),
]
library ATLProject1Lib
{
    importlib("stdole2.tlb");
    [
        uuid(9818df4f-d77f-4d1d-a094-82632f6a0ffd)
    ]
    coclass ATLSimpleObject
    {
        [default] interface IATLSimpleObject;
    };
};

import "shobjidl.idl";

STDMETHODIMP CATLSimpleObject::HelloWorld(BSTR text)
{
    int needBytes = WideCharToMultiByte(CP_ACP, 0, text, -1, NULL, 0, NULL, NULL);
    char *ptr = new char[needBytes];

    WideCharToMultiByte(CP_ACP, 0, text, -1, ptr, needBytes, NULL, NULL);

    printf("%s HelloWorld\n", ptr);

    delete []ptr;
    return S_OK;
}

개인적으로 레지스트리를 어지럽히는 것을 싫어하기 때문에 위의 프로젝트를 빌드와 함께 등록하는 것을 원치 않으므로 프로젝트 설정에서 "Linker" / "General" 범주의 "Register Output" 속성을 "No"로 설정했습니다. (사실, Visual Studio를 관리자 권한으로 실행하지 않으면 빌드 시 오류가 발생합니다.)




자, 그럼 위의 ATL 프로젝트를 Windows Forms 프로젝트에서 참조해 볼까요? .NET 8 기본 프로젝트를 생성한 후, 솔루션 탐색기의 프로젝트 노드를 우클릭해 "Add" / "COM Reference..."를 선택, "Browse..." 버튼을 눌러 "./x64/Debug" 폴더에 있는 "ATLProject1.dll" 파일을 선택하면 COM 서버를 참조할 수 있습니다.

보통은 그렇게 돼야 하는데요, Visual Studio 2022 현재 버전에서는 "The reference is invalid or unsupported"라는 오류가 발생합니다. 반면, 해당 COM 개체를 "regsvr32"를 이용해 등록하면 정상적으로 csproj에는 다음과 같은 항목이 추가됩니다.

<ItemGroup>
    <COMReference Include="ATLProject1Lib">
    <VersionMinor>0</VersionMinor>
    <VersionMajor>1</VersionMajor>
    <Guid>240e2ba0-1248-48d2-9165-0fdb5d40c710</Guid>
    <Lcid>0</Lcid>
    <WrapperTool>tlbimp</WrapperTool>
    <Isolated>false</Isolated>
    <EmbedInteropTypes>true</EmbedInteropTypes>
    </COMReference>
</ItemGroup>

만약 레지스트리 등록을 피하고 싶다면? 어쩔 수 없습니다, 그런 경우 원칙으로 대응해야 합니다. 즉, tlbimp.exe를 이용해 직접 COM 서버를 위한 C# Interop DLL을 생성하면 됩니다.

c:\temp> tlbimp /machine:X64 ATLProject1.dll
Microsoft (R) .NET Framework Type Library to Assembly Converter 4.8.3928.0
Copyright (C) Microsoft Corporation.  All rights reserved.

TlbImp : Type library imported to ATLProject1Lib.dll

저렇게 생성한 ATLProject1Lib.dll 파일을 C# 프로젝트에 참조하면,

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net8.0-windows</TargetFramework>
        <Nullable>enable</Nullable>
        <UseWindowsForms>true</UseWindowsForms>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>

    <ItemGroup>
      <Reference Include="ATLProject1Lib">
        <HintPath>..\x64\Debug\ATLProject1Lib.dll</HintPath>
      </Reference>
    </ItemGroup>

</Project>

이제 다음과 같은 코딩이 인텔리센스의 도움을 받아 진행할 수 있습니다.

private void Form1_Load(object sender, EventArgs e)
{
    ATLProject1Lib.ATLSimpleObjectClass obj = new ATLProject1Lib.ATLSimpleObjectClass();
    obj.HelloWorld("TEST");
}




물론, 저렇게만 하고 실행하면 COM 개체를 찾을 수 없으므로 "System.Runtime.InteropServices.COMException" 예외가 발생합니다.

System.Runtime.InteropServices.COMException
  HResult=0x80040154
  Message=Retrieving the COM class factory for component with CLSID {9818DF4F-D77F-4D1D-A094-82632F6A0FFD} failed due to the following error: 80040154 Class not registered (0x80040154 (REGDB_E_CLASSNOTREG)).
  Source=WinFormsApp1
  StackTrace:
   at WinFormsApp1.Form1.Form1_Load(Object sender, EventArgs e)
   at System.Windows.Forms.Form.OnLoad(EventArgs e)
   ...[생략]...

이를 해결하려면, 예전에 정리했던 글에 따라,

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

manifest 파일을 내장해야 합니다. 오랜만이니 이 과정까지 마저 마무리를 해볼까요? ^^

우선, manifest 파일은 1) 수작업으로 직접 만들 수도 있고, 2) "COMRegFreeManifest"와 같은 도구를 이용할 수도 있습니다. 편의상 ^^ 도구를 이용하겠습니다.

C:\temp> COMRegFreeManifest.exe ATLProject1.dll

C:\temp> dir *.manifest /b
ATLProject1.dll.manifest
Sample.exe.manifest

이렇게 생성한 ATLProject1.dll.manifest 파일과 ATLProject1.dll을 C# 프로젝트에 "Add as Link"로 추가해 CopyToOutputDirectory를 설정하면,

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net8.0-windows</TargetFramework>
        <Nullable>enable</Nullable>
        <UseWindowsForms>true</UseWindowsForms>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>

    <ItemGroup>
      <None Include="..\x64\Debug\ATLProject1.dll" Link="ATLProject1.dll">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>
      <None Include="..\x64\Debug\ATLProject1.dll.manifest" Link="ATLProject1.dll.manifest">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>
    </ItemGroup>

    <ItemGroup>
      <Reference Include="ATLProject1Lib">
        <HintPath>..\x64\Debug\ATLProject1Lib.dll</HintPath>
      </Reference>
    </ItemGroup>

</Project>

빌드 시마다 출력 폴더(예를 들어, "bin\Debug\net8.0-windows")에 DLL, manifest 파일이 복사됩니다. 물론, 이전의 과정은 수작업으로 했으므로 이후 변경 과정에서 불편함을 겪을 수 있으므로 적절하게 Build Events 등을 이용해 COMRegFreeManifest 등의 실행을 자동화하는 것이 좋습니다.

// "Build Events" / "Post-Build Event"

pushd $(OutDir)
COMRegFreeManifest $(TargetFileName)
tlbimp /machine:X64 $(TargetFileName)
popd




그래도 아직, 마지막 작업이 남았지요. ^^ 바로 Windowns Forms EXE 프로젝트의 manifest 파일에 ATLProject1.dll.manifest 파일을 참조하도록 설정해야 합니다.

이를 위해 "Add" / "New Item..."을 선택하고 "Application Manifest File (Windows Only)" 항목을 "app.manifest" 이름으로 생성하고 다음의 내용을 추가합니다.

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>

    <!-- ...[생략]... -->

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

</assembly>

그런데, 이렇게 하고 나서 빌드하면 이런 경고가 나올 것입니다.

warning MSB3270: There was a mismatch between the processor architecture of the project being built "MSIL" and the processor architecture of the reference "ATLProject1Lib", "AMD64". This mismatch may cause runtime failures. Please consider changing the targeted processor architecture of your project through the Configuration Manager so as to align the processor architectures between your project and references, or take a dependency on references with a processor architecture that matches the targeted processor architecture of your project.


무시해도 되지만, 그래도 COM DLL이 native DLL이므로 x64로 빌드된 것이니, C# 프로젝트 역시 AnyCPU보다는 x64로 명시하는 것이 좋습니다. 이후 다시 빌드하면 경고도 없고, 실행 시 (레지스트리 등록이 없음에도) COM 개체 사용에 문제없이 잘 동작합니다.

(첨부 파일은 이 글의 예제 프로젝트를 포함합니다.)




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

[연관 글]






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

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

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  52  53  54  55  [56]  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12538정성태2/15/202118688.NET Framework: 1023. C# - GC 힙이 아닌 Native 힙에 인스턴스 생성 - 0SuperComicLib.LowLevel 라이브러리 소개 [2]
12537정성태2/11/202119359.NET Framework: 1022. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! - 두 번째 이야기 [2]
12536정성태2/9/202118211개발 환경 구성: 542. BDP(Bandwidth-delay product)와 TCP Receive Window
12535정성태2/9/202117294개발 환경 구성: 541. Wireshark로 확인하는 LSO(Large Send Offload), RSC(Receive Segment Coalescing) 옵션
12534정성태2/8/202117812개발 환경 구성: 540. Wireshark + C/C++로 확인하는 TCP 연결에서의 closesocket 동작 [1]파일 다운로드1
12533정성태2/8/202116812개발 환경 구성: 539. Wireshark + C/C++로 확인하는 TCP 연결에서의 shutdown 동작파일 다운로드1
12532정성태2/6/202118011개발 환경 구성: 538. Wireshark + C#으로 확인하는 ReceiveBufferSize(SO_RCVBUF), SendBufferSize(SO_SNDBUF) [3]
12531정성태2/5/202116788개발 환경 구성: 537. Wireshark + C#으로 확인하는 PSH flag와 Nagle 알고리듬파일 다운로드1
12530정성태2/4/202120597개발 환경 구성: 536. Wireshark + C#으로 확인하는 TCP 통신의 Receive Window
12529정성태2/4/202118488개발 환경 구성: 535. Wireshark + C#으로 확인하는 TCP 통신의 MIN RTO [1]
12528정성태2/1/202118087개발 환경 구성: 534. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 윈도우 환경
12527정성태2/1/202118169개발 환경 구성: 533. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 리눅스 환경파일 다운로드1
12526정성태2/1/202114993개발 환경 구성: 532. Azure Devops의 파이프라인 빌드 시 snk 파일 다루는 방법 - Secure file
12525정성태2/1/202113947개발 환경 구성: 531. Azure Devops - 파이프라인 실행 시 빌드 이벤트를 생략하는 방법
12524정성태1/31/202115146개발 환경 구성: 530. 기존 github 프로젝트를 Azure Devops의 빌드 Pipeline에 연결하는 방법 [1]
12523정성태1/31/202116077개발 환경 구성: 529. 기존 github 프로젝트를 Azure Devops의 Board에 연결하는 방법
12522정성태1/31/202118275개발 환경 구성: 528. 오라클 클라우드의 리눅스 VM - 9000 MTU Jumbo Frame 테스트
12521정성태1/31/202117346개발 환경 구성: 527. 이더넷(Ethernet) 환경의 TCP 통신에서 MSS(Maximum Segment Size) 확인 [1]
12520정성태1/30/202116103개발 환경 구성: 526. 오라클 클라우드의 VM에 ping ICMP 여는 방법
12519정성태1/30/202114833개발 환경 구성: 525. 오라클 클라우드의 VM을 외부에서 접근하기 위해 포트 여는 방법
12518정성태1/30/202132942Linux: 37. Ubuntu에 Wireshark 설치 [2]
12517정성태1/30/202120648Linux: 36. 윈도우 클라이언트에서 X2Go를 이용한 원격 리눅스의 GUI 접속 - 우분투 20.04
12516정성태1/29/202117108Windows: 188. Windows - TCP default template 설정 방법
12515정성태1/28/202118762웹: 41. Microsoft Edge - localhost에 대해 http 접근 시 무조건 https로 바뀌는 문제 [3]
12514정성태1/28/202118919.NET Framework: 1021. C# - 일렉트론 닷넷(Electron.NET) 소개 [1]파일 다운로드1
12513정성태1/28/202116077오류 유형: 698. electronize - User Profile 디렉터리에 공백 문자가 있는 경우 빌드가 실패하는 문제 [1]
... 46  47  48  49  50  51  52  53  54  55  [56]  57  58  59  60  ...