1. LUCENE.NET 


Lucene.Net is a port of the Lucene search engine library, written in C# and targeted at .NET runtime users. The Lucene search library is based on an inverted index. Lucene.Net has three primary goals:


Maintain the existing line-by-line port from Java to C#, fully automating and commoditizing the process such that the project can easily synchronize with the Java Lucene release schedule;

Maintaining the high-performance requirements expected of a first class C# search engine library;

Maximize usability and power when used within the .NET runtime. To that end, it will present a highly idiomatic, carefully tailored API that takes advantage of many of the special features of the .NET runtime.


자바의 유명한 검색 엔진인 Lucene을 이용하여 라인 단위 포팅한 프로젝트 

언어적인 특성으로 인해 C#의 언어의 특성에 맞게 구현 됨 


http://lucenenet.apache.org/


해당 사이트에서 바이너리를 받던가 소스를 받아서 컴파일 하면 됨 



2. Lucene 인덱싱 특징


(1) 디렉토리 단위로 다큐먼트라고 불리는 인덱싱 파일들이 생성 되며, 여러 정의된 스키마를 중복으로 통합 할 수 있음 


(2) 다큐먼드는 파일시스템과 같은 디스크와 메모리에 생성 할 수 있음 

- 메모리에 생성 함으로서 속도를 개선 할 수 있다고는 하나, 경험상 체감할 정도를 아니었음 


(3) 다큐먼트의 필드 특징 

- 기본적으로 텍스트 기반으로 필드를 저장함 


- Field.Index.NO : 필드에 대해서 단순 저장용 데이터로 사용

- Field.Index.ANALYZED : 분석기에 의해 토큰화 된 데이터 

- Field.Index.NOT_ANALYZED : 검색에는 사용되나 분석기를 사용 하지 않고 사용될 데이터


- Field.Store.NO : 원본 데이터를 저장 안함 (Field.Index.ANALYZED 필드에 대해서 공간 절약 할 용도)

- Field.Store.YES : 원본 데이터를 저장 함 


(4) 기본으로 제공 되는 Analyzer(토큰화 하여 분리 시켜 주는 분석기)는 4개가 제공됨 

- WhitespaceAnalyzer : (화이트)스페이스  단위로 토큰 분리 

- SimpleAnalyzer : Letter를 구분으로 토큰 분리 

- StopAnalyzer : SimpleAnalyzer 와 같지만 Stop 단어를 제거함 

- StandardAnalyzer : 문법 기반으로 토큰 분리, 영어권 언어에 적합 



3. Lucene 한글 이슈


(1) 형태소 분석기 및 사전화 인덱싱 

- 한글을 제대로 검색 하기 위해서는 형태소 분석기 및 사전화가 되어 있어야 하나, 이 또한 적합하지 않은 경우가 있음

- 형태소 분석기 및 사전화 작업에 따른 인덱싱 속도 저하와 복잡도 및 관리 리소스가 많이 투입 될 경우가 많음

- 물론 유지보수 및 관리 잘 된다면 검색 품질을 높일 수는 있으나, 요구 사항에 따른 검색 튜닝을 해야함

 

(2) Lucene.Net.Analysis.CJK.CJKAnalyzer 사용 

- 일반적인 쇼핑몰 등의 상품명 및 정보를 검색할 경우 CJK 분석기를 이용하는 것이 유연하게 대처 가능 하다고 봄

- 인덱싱 파일이 커지고 의미상 의미 없는 단어가 검색 되긴 하지만, 기계적으로 인덱싱 속도가 빠름

- 외래어나 기타 사전화 되지 않는 단어에 대해서도 검색이 용이함 

- 3.0,3 버전 이후 부터 contrib 형태로 제공 됨 



4. 인덱싱 및 검색 전략 (Lucene.Net.Analysis.CJK.CJKAnalyzer 사용한다는 전제)


(1) 병합 전략을 위해 유니크한 id 필드를 공통으로 정의 함 (병합시 삭제 + 추가를 위해)


(2) 분석기에 의해 토큰(Field.Index.ANALYZED)화 되어지는 필드를 정하고, 이를 기반으로 보조적인 검색 필드로 일반검색

(Field.Index.NOT_ANALYZED)필드 및 그외 조회되는 데이터(Field.Index.NO)를 정의함 


(3) Field.Index.NOT_ANALYZED의 경우 분석 되지 않는 데이터이므로 대소문자 대응을 해야 함 

- http://www.gossamer-threads.com/lists/lucene/java-dev/74936


(4) 검색의 정렬의 특성상 아래의 정렬 순서로 성능이 좋음 

- http://blog.richeton.com/2009/05/12/lucene-sort-tips/ 


-  SortField.SCORE(적중율), SortField.DOC(인덱싱 문서 순서)으로 정렬, 검색 되어지게 하는 것이 성능상 좋음 

- 사용자 정의 정렬은 되도록이면 SortField.INT 를 사용하여 정렬을 할 수 있도록 인덱싱 처리  

- SortField.STRING의 경우 데이터가 많이 검색 될 경우 위의 검색 옵션 보다 월등히 성능이 좋지 않음 


(5) Lucene 검색 문법

- http://lucene.apache.org/core/3_6_1/queryparsersyntax.html


- SQL 문과 같은 문법으로 사용하기 위해 SQL 파서는 무겁다고 판단

- 아래의 정규식을 이용하여 SQL을 분리해서 Lucene 검색 문법으로 변경 처리 할 수 있음 

* 정규식 : "(?:'(?:[^']|'')*'|-?\d+(?:\.\d+)?(?:[eE]-?\d+)?|\w+|[<>=]{2}|\S)"

select(sel) [ * | field, ... ] 

from    directory 

where   field1='val' and field2='val' or ... 

order by [_score | _doc | field1 [order_expression], ...] 

group by field1, ...   


( *order_expression : [ asc | desc | doc_asc | doc_desc | score_asc | score_desc | int_asc | int_desc ] ) 

( *group result fields : group (group by value), count (group by count) )


(6) 검색 팁 

- Analyzer의 종류에 따라 토큰으로 분리가 되는 데이터가 다르므로 와일드카드 검색등을 이용할 때 고려해야 함 

  (와일드카드 검색은 성능에 영향을 미침)

- CJKAnalyzer는 중국어, 일본어, 한국어에 대해서 두 음절씩 잘라 내므로 와일드카드 검색이 의미 없을 것으로 판단


- 분야별 개수를 가져오는 Faceted Search 같은 기법은 아직 쓸모 있어 보이지 않음 (내가 잘 모를수도..)

- 그냥 직접 해쉬 맵 객체를 이용하여 구현 함 


- 토큰 필드 색인시 기본 문자열 + 공백을 제거한 문자열을 같이 포함 시켜서 넣도록 함 

- 한 글자에 대해서 검색이 용이하게 하려면 단어별로 앞글자를 공백을 넣고 같이 인덱싱 데이터에 넣으면 효과가 있을 것임

- ex) "오페라의 유령 - 25주년 기념 내한공연" 

       -> "오 유 기 내 오페라의유령25주년기념내한공연 오페라의 유령 - 25주년 기념 내한공연"  


- 사용자 검색어에 대해서 공백 단위로 AND 조건 검색 하도록 쿼리 변경 


기타 일반적인 전략 : http://wiki.apache.org/jakarta-lucene/ImproveSearchingSpeed



'Dev > .NET' 카테고리의 다른 글

SerializeToXML - C#  (0) 2009.08.10
C# Web Service -> REST  (0) 2009.08.09
Integrating WCF Services with COM+  (0) 2008.09.03
Interop 응용 프로그램 배포  (0) 2007.10.05
RCW, CCW  (0) 2007.10.04

- 기존에 TextWriter 객체에 Serialize 하니 XML Declaration 부분에 UTF-16으로 고정 되어서 MemoryStream 로 교체
- MemoryStream는 XML Declaration 부분을 표시 하지 않음


using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

// 구현
protected string SerializeToXML<T>(T t)
{
    string strXml = "";
    XmlSerializer xs = new XmlSerializer(typeof(T));

    try
    {
        using (MemoryStream ms = new MemoryStream())
        {
            xs.Serialize(ms, t);
            strXml = Encoding.UTF8.GetString(ms.ToArray());
        }

    }
    catch (Exception) { }

    return strXml;
}

'Dev > .NET' 카테고리의 다른 글

LUCENE.NET 검색엔진  (0) 2013.01.02
C# Web Service -> REST  (0) 2009.08.09
Integrating WCF Services with COM+  (0) 2008.09.03
Interop 응용 프로그램 배포  (0) 2007.10.05
RCW, CCW  (0) 2007.10.04
Web Service(WSDL), REST 어떤 방식으로 서비스를 만들면 좋을것인가 고민을 한적이있다.

그런데 왜 난 꼭 REST 방식을 웹페이지로 따로 만들어야 된다고 생각을 했을까?
그냥 아래와 같이 Web Service에 대해서 GET, POST 방식을 같이 노출하면 같이 사용할 수 있는데..


Web Service 의 인터페이스 메쏘드의 Input 파라미터에 대해서 Complex Type을 쓰지 않고 기본 타입을 쓰고,
web.config 에 아래와 같이 GET, POST 방식을 노출 해주면 끝.
<configuration>
<system.web>
<webServices>
<protocols>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
</system.web>
</configuration>
하지만 Basic Type 을 쓰면 Strong한 타입 체킹을 할 수 없다는 단점이 있긴하다..

'Dev > .NET' 카테고리의 다른 글

LUCENE.NET 검색엔진  (0) 2013.01.02
SerializeToXML - C#  (0) 2009.08.10
Integrating WCF Services with COM+  (0) 2008.09.03
Interop 응용 프로그램 배포  (0) 2007.10.05
RCW, CCW  (0) 2007.10.04

http://msdn.microsoft.com/en-us/library/bb978523.aspx
http://msdn.microsoft.com/en-us/library/bb735856.aspx


WCF의 COM+ 통합서비스

쉽게 얘기해서 COM+의 서비스를 종전의 DCOM이나 RDS처럼 WCF 여러 채널(바인딩)을 통해서 서비스 하는 방법이다

Binding

Interoperability

Transactions

Encoding

BasicHttpBinding

Basic Profile 1.1

(None)

Text, (MTOM)

WSHttpBinding

WS

(None), Yes

Text, (MTOM)

WSDualHttpBinding

WS

(None), Yes

Text, (MTOM)

WSFederationHttpBinding

WS-Federation

(None), Yes

Text, (MTOM)

NetTcpBinding

.NET

(None), Yes

Binary

NetNamedPipeBinding

.NET

(None), Yes

Binary

NetMsmqBinding

.NET

(None), Yes

 

NetPeerTcpBinding

Peer

(None)

 

MsmqIntegrationBinding

MSMQ

(None), Yes

 


여기서 WSHttpBinding과 BasicHttpBinding 과 같은 경우는 IIS를 통해서 지원을 하고,
NetTCPBinding 과 같은 경우는 COM+를 통해서 호스팅이 가능 하도록 설계되어 있다
물론 WAS(IIS 7.0)의 경우 모두 호스팅이 가능할 것으로..
(COM+ 통합의 경우 셀프 호스팅은 아직 안해봐서 모르겠다..)



WCF의 COM+ 통합서비스를 하려면 다음과 같은 도구가 필요하다

- .NET Framework 3.0 (or 3.5 ) (3.5를 설치하면 3.0, 2.0 등이 모두 설치된다.) - 3.5 권장..
- SvcConfigEditor.exe (Service Configuration Editor) 혹은 ComSvcConfig.exe

ComSvcConfig.exe는 .NET Framework 3.0와 함께 제공되며 커맨트 툴로 COM+의 WCF의 구성을 돕는다.
허나 .NET SDK로 제공되는  SvcConfigEditor.exe를 이용하면 Visual 한 환경으로 WCF 구성파일 편집, 생성및 COM+을 쉽게 구성할수 있다.

* ComSvcConfig.exe : http://msdn.microsoft.com/ko-kr/library/ms732009.aspx



구성 방법

1. 우선 만들어진 컴포넌트(VC++ ATL 간단하게 만들었다)를 COM+에 패키지를 생성하여 추가한다.

2. SvcConfigEditor.exe를 실행하여 "파일->통합->COM+응용프로그램" 메뉴를 선택
- COM+의 패키지의 컴포넌트 인터페이스를 선택
- 컴포넌트의 메서드 선택
- COM+ 호스팅 선택
- TCP를 선택
- 서비스 주소 세팅 (ex: net.tcp://localhost:9000/Simple)
- 완료

3. 2번과 같이 세팅이 완료된 경우 COM+의 해당 패키지의 등록정보-활성화 탭의 "응용 프로그램 루트 디렉토리"란에 WCF 설정파일이 생성된 디렉토리 경로가 세팅된다.
ex: C:\Program Files\ComPlus Applications\{593add39-f66f-442a-a901-01379dbac4eb}\

4. 그리고 netstat 로 확인을 해보면 hosting 이 되어 있는걸 확인할 수 있다.

C:\>netstat -an | findstr 9000
  TCP    0.0.0.0:9000           0.0.0.0:0              LISTENING


위의 방법 이외에 웹호스팅으로도 선택하여 IIS를 통하여 서비스 하는것도 가능하며,
웹호스팅의 경우 웹사이트및 가상디렉토리를 선택하면 해당 디렉토리에 WCF구성파일을 생성해준다
(.svc 과 web.config)



테스트

위와같이 세팅이 되어 있다면 Visual Studio 2005 (Extensions for WPF and WCF 설치)나 Visual Studio 2008을 이용하여 테스트 가능하다

SvcUtil.exe 과 같은 툴로 스켈레톤 코드를 생성하여 포함시켜 사용해도 되고 VS 2008의 경우 서비스 참조를 하면 자동으로 생성을 해준다.

그리고 참조된 객체를 생성할때 생성자의 파라미터로 app.config에 설정되어 있는 endpoint 중에 하나를 선택하여 그 이름을 세팅하면 해당 endpoint를 이용하여 Communicaiton(통신)를 하게 된다.

using (Simple.SimpleClient s = new Simple.SimpleClient("NetTcpBinding_ISimple"))
{
    int nResult = (int)s.Add(50, 58);
    Console.WriteLine("result = {0}", nResult);
}


그러나 처음 실행하면 다음과 같은 오류를 뱉어낸다.

통신 개체 System.ServiceModel.Channels.ServiceChannel은(는) Faulted 상태이기 때
문에 통신에 사용할 수 없습니다.

이유는 COM+의 해당 패키지를 호스팅하는 프로세스가 객체를 자동 활성화 시키지 못해서 그런것 같다.
그러나 이미 활성화가 되어 있는 경우는 문제 없다.
어떻게 활성화를 자동으로 시킬지는 알아봐야 할듯...

* 이벤트로그 내용

COM+: COM 인스턴스를 만드는 동안 오류가 발생했습니다.
 원본:
 App ID: 593add39-f66f-442a-a901-01379dbac4eb
 CLSID: 9833d2b6-d279-4bc3-b9be-7a4194c4a1b2
 들어오는 트랜잭션 ID: 00000000-0000-0000-0000-000000000000
 서로게이트: CDECL-DESK\cdecl
 요청 ID: System.Runtime.InteropServices.COMException (0x80070542): 적어도 클라이언트 토큰은 Out of process Webhost 활성화를 위한 최소 가장의 SecurityImpersonationLevel을 갖고 있어야 합니다.
   위치: System.ServiceModel.ComIntegration.ComPlusInstanceProvider.GetInstance(InstanceContext instanceContext, Message message)
 예외: aspnet_wp
 프로세스 이름: 22228
 프로세스 ID: %9



위의 이유에서인지 MS에서는 WCF 서비스 형태로도 제공되는 COM+의 경우 해당 패키지를 서비스 형태로 실행하는 방법에 대해서 제시를 한다.

     서비스 COM+ 패키지 -> 등록정보 -> 활성화 탭 ->  "NT서비스로 응용프로그램 실행"

서비스 형태로 등록을 한 후 해당 패키지를 중지->시작 을 하면 서비스 형태로 실행이 되어 시스템 재시작 후에도 문제가 없다고 하는데...


기타

WCF의 COM+ 통합서비스를 하기 위해서는 XP및 Windows 2003 서버에 아래의 업데이트를 설치를 해줘야 한다.

Windows Communication Foundation용 업데이트(KB912817)

간략한 설명 :
MSDTC의 WS-AtomicTransaction 프로토콜에 대한 지원과 WCF(Windows Communication Foundation) COM+ 통합 기능에 대한 지원을 추가합니다.


'Dev > .NET' 카테고리의 다른 글

SerializeToXML - C#  (0) 2009.08.10
C# Web Service -> REST  (0) 2009.08.09
Interop 응용 프로그램 배포  (0) 2007.10.05
RCW, CCW  (0) 2007.10.04
&quot;The underlying connection was closed&quot; - WebServices  (0) 2005.10.13
출처 : http://msdn2.microsoft.com/ko-kr/library/tc0204w0(VS.80).aspx


Interop 응용 프로그램 배포

Interop 응용 프로그램에는 일반적으로 .NET 클라이언트 어셈블리, COM 형식 라이브러리를 나타내는 하나 이상의 interop 어셈블리, 하나 이상의 등록된 COM 구성 요소 등이 포함됩니다. .NET Framework SDK에서는 형식 라이브러리를 interop 어셈블리로 가져오고 변환하는 도구를 제공합니다. interop 어셈블리는 응용 프로그램을 구성하는 다른 어셈블리와 함께 제공되어야 합니다.

어셈블리에는 강력한 이름을 지정할 수 있는데 강력한 이름의 어셈블리에는 고유하게 식별되는 게시자의 공개 키가 포함됩니다. 게시자는 /keyfile 옵션을 사용하여 형식 라이브러리 가져오기(Tlbimp.exe)로 생성된 어셈블리에 서명할 수 있습니다. 서명된 어셈블리는 전역 어셈블리 캐시에 설치될 수 있지만 서명되지 않은 어셈블리는 사용자의 컴퓨터에 전용 어셈블리로 설치되어야 합니다.

전용 어셈블리

전용으로 사용될 어셈블리를 설치하려면 응용 프로그램 EXE 및 가져온 COM 형식이 포함된 interop 어셈블리를 동일한 디렉터리 구조에 설치해야 합니다. 다음 예제에서는 서로 다른 응용 프로그램 디렉터리에 위치하는Client1.exe와 Client2.exe에서 전용으로 사용될 서명되지 않은 interop 어셈블리를 보여 줍니다. 이 예제에서 LOANLib.dll이라는 interop 어셈블리는 두 번 설치됩니다.

전용 배포 시 디렉터리 구조 및 레지스트리 항목


디렉터리 구조 및 Windows 레지스트리

응용 프로그램과 관련된 모든 COM 구성 요소는 Windows 레지스트리에 설치되어야 합니다. 예제의 Client1.exe와 Client2.exe는 서로 다른 컴퓨터에 설치되어 있기 때문에 COM 구성 요소를 두 컴퓨터에 모두 등록해야 합니다.

공유 어셈블리

여러 응용 프로그램에서 공유하는 어셈블리는 전역 어셈블리 캐시라고 하는 중앙 리포지토리에 설치되어야 합니다. 이렇게 하면, .NET 클라이언트에서는 서명되고 전역 어셈블리 캐시에 설치된 interop 어셈블리의 동일한 복사본에 액세스할 수 있습니다.

'Dev > .NET' 카테고리의 다른 글

SerializeToXML - C#  (0) 2009.08.10
C# Web Service -> REST  (0) 2009.08.09
Integrating WCF Services with COM+  (0) 2008.09.03
RCW, CCW  (0) 2007.10.04
&quot;The underlying connection was closed&quot; - WebServices  (0) 2005.10.13
CCW, RCW

출처 : http://www.microsoft.com/Korea/MSDN/MSDNMAG/ISSUES/2001/Interop/default.aspx


.NET에서 COM 개체 사용

새로눈 .NET 코드는 기존 COM 코드와 상호 운용되어야 하기 때문에 이 경우에 대해 먼저 설명하겠습니다. .NET 클라이언트는 그림?1과 같이 런터임 호출 가능 래퍼(RCW)를 통해 COM 서버에 액세스합니다. RCW는 COM 개체를 래핑하고 이 개체와 .NET 공통 언어 런타임(CLR) 환경을 서로 연결해 주는 역할을 합니다. 이로 인해 .NET 클라이언트 측면에서는 COM 개체를 기본 .NET 개체인 것처럼 인식하며, COM 개체 측면에서는 .NET 클라이언트를 표준 COM 클라이언트인 것처럼 인식합니다.

그림 1 RCW를 통한 클라이언트 액세스

그림 1 RCW를 통한 클라이언트 액세스

.NET 클라이언트 개발자는 두 가지 방법 중 하나를 사용하여 RCW를 만듭니다. Visual Studio .NET을 사용하는 경우에는 프로젝트의 참조 섹션을 마우스 오른쪽 단추로 클릭한 다음 바로 가기 메뉴에서 참조 추가를 선택하면 됩니다. 그러면 시스템에 등록된 모든 COM 형식 라이브러리를 선택할 수 있는 대화 상자가 나타나며, 등록하지 않은 형식 라이브러리를 찾아서 선택할 수도 있습니다(그림?2 참조). 그런 다음 RCW를 만들려는 COM 개체를 선택하면 Visual Studio .NET에서 RCW를 만들어 프로젝트에 추가해 줍니다.


그림 2 RCW 생성

그림 2 RCW 생성

Visual Studio .NET을 사용하지 않는 경우 .NET SDK에는 명령줄 도구(동일한 작업을 수행하는 도구로 TlbImp.exe라고도 함)가 포함됩니다. 형식 라이브러리를 읽고 RCW 코드를 만드는 논리는 System.Runtime.InteropServices.TypeLibConverter라고 불리는 .NET 런타임 클래스 내에 존재합니다. Visual Studio .NET과 TlbImp.exe는 내부적으로 이 클래스를 사용합니다.


그림 3 .COM을 사용하는 NET 클라이언트

그림 3 COM을 사용하는 .NET

그림?3에서는 COM 개체 서버를 사용하는 .NET 클라이언트 프로그램을 예로 보여주고 있습니다. 본 기사 맨 위에 있는 링크를 따라 예제를 다운로드 하십시오. 이 예제에는 COM 서버, COM 클라이언트 및 .NET 클라이언트가 포함되어 있기 때문에 이 두 개를 비교해 볼 수 있습니다. 소스 코드는 그림?4에 표시되어 있습니다.
일단 RCW를 생성한 다음에는 짧은 이름을 사용하여 개체를 참조할 수 있도록 하기 위해, Imports 문을 사용해 RCW의 네임스페이스를 클라이언트 프로그램으로 가져오고 싶어할 것입니다. 이경우 새로운 연산자를 사용하면 RCW 개체를 쉽게 만들 수 있습니다. 일단 RCW 개체가 생성되면 RCW는 기본 COM 함수인 CoCreateInstance를 내부적으로 호출하고, 이 결과 RCW 개체에 의해 래핑되는 COM 개체가 만들어집니다. 그러면 .NET 클라이언트 프로그램은 RCW가 기본 .NET 개체인 것처럼 RCW에서 메서드를 호출하며, RCW는 각 호출을 COM 호출 규칙으로 변환합니다. 예를 들어 .NET 문자열은 COM이 필요로 하는 BSTR 문자열로 변환하여 해당 개체로 보냅니다. RCW는 COM 개체로부터 반환된 결과를 클라이언트로 반환하기 전에 기본 .NET 형식으로 변환합니다.
예제 COM 클라이언트 프로그램을 실행한 다음 단추를 클릭하면 제가 코드에 삽입해 놓은 대화 상자에 개체가 만들어지고 즉시 없어지는 것을 알 수 있을 것입니다. 예제로 제공한? .NET 클라이언트 프로그램을 실행한 다음 Get Time 단추를 클릭하면 개체가 만들어지지만 즉시 없어지지는 않습니다. 래퍼 개체가 시야에서 사라질 때 당연한 것으로 생각할 수 있지만 그렇지 않습니다. 개체 레퍼런스를 'nothing'으로 명시적으로 설정해 놓아도 마찬가지입니다. RCW가 범위를 벗어나서 프로그램에서 더 이상 액세스할 수가 없지만, 나중에 RCW의 가비지가 수집되거나 사라지기 전까지는 RCW가 래핑하는 COM 개체를 실제로 해제하지는 않습니다. 이것은 문제가 될 수 있습니다. 대부분의 COM 개체는 이러한 주기를 고려하지 않고 작성되기 때문에 클라이언트가 종료될 때 바로 해제되어야 하는 값비싼 리소스를 계속 사용하는 경우가 있을 수 있습니다.
이러한 문제는 두 가지 방법 중 하나로 해결할 수 있습니다. 첫 번째 방법은 System.GC.Collect 함수를 통해 고속 가비지 수집을 강제로 수행하는 것입니다. 이 함수를 호출하면 사용하고 있지 않은 모든 시스템 리소스와 범위 내에 존재하지 않는 RCW를 수집하여 다시 사용하게 됩니다. 이 방법의 단점은 전체 가비지 수집의 오버헤드가 높다는 것입니다. 마치 Julia Child가 전체 부엌을 청소하지 않고도 자신이 좋아하는 과도만을 닦아 낼 수 있는 것처럼, 하나의 개체를 버리기 위해 즉시 비용을 들일 필요는 없습니다. 다른 개체에 영향을 주지 않으면서 하나의 특정 COM 개체를 없애려면 System.Runtime.InteropServices.Marshal.ReleaseComObject 함수를 사용할 수 있습니다.
앞 단락에서 설명한 RCW 메커니즘을 위해서는 초기 바운드 개체가 필요합니다. 이것은 개발자가 개발 시점에서 래퍼 클래스를 구성하기 위해 형식 라이브러리가 제공하는 개체에 대해 근본적으로 이해하고 있어야 함을 의미합니다. 하지만 이것이 모든 디자인 시나리오에서 가능한 일은 아닙니다. 예를 들어, 클라이언트가 개체 및 메서드의 ProgID를 읽고 런타임 도중 이를 스크립트 코드로부터 읽는 경우, 스크립트 상황에서는 후기 바인딩이 필요합니다. 대부분의 COM 개체는 특별히 IDispatch 인터페이스를 지원하므로 이러한 형식의 후기 바운드 액세스를 사용할 수 있습니다. 이런 경우에는 RCW를 미리 만들 수 없습니다. 그렇다면 .NET은 이러한 시나리오를 어떻게 처리할 수 있을까요?


Figure 5 Late Binding

그림 5 후기 바인딩

.NET Framework는 대부분의 COM 개체가 지원하는 IDispatch 인터페이스에 대해 후기 바인딩을 지원합니다. 그림?5는 후기 바인딩 프로그램에 대한 예제이며 그림?6은 코드입니다. 정적 메서드인 Type.GetTypeFromProgID를 통해 개체의 ProgID를 기반으로 하는 .NET 시스템 형식을 만드십시오. ProgID 대신 이를 사용할 경우 정적 메서드인 Type.GetTypeFromCLSID(그림?6에는 없음)는 CLSID를 기반으로 하여 같은 역할을 합니다. 그런 다음 Activator.CreateInstance 메서드를 사용하여 COM 개체를 만들고 Type.InvokeMember 함수를 통해 메서드를 호출하여, 메서드의 매개 변수를 배열 형태로 전달합니다. 후기 바인딩의 경우 이 작업은 더 복잡하지만 어렵지 않게 수행할 수 있을 것입니다.


맨 위로


COM에서 .NET 개체 사용

반대로, 이미 COM과 통신하고 있는 클라이언트에서 .NET 개체를 사용하려는 경우를 가정해 봅시다. 이런 경우는 그 반대 상황보다 흔하지 않은 시나리오입니다. 왜냐하면 이 상황에서는 .NET 환경에서의 새로운 COM 개발을 미리 전제로 하기 때문입니다. 하지만 10개의 COM 개체를 사용하는 기존 COM 클라이언트에 .NET 개체로만 존재하는 기능 집합을 추가하려는 경우에 이러한 상황이 발생할 수 있습니다. 그림?7은 COM 호출 가능 래퍼(CCW)를 통해 이러한 상황을 지원하는 .NET Framework를 나타냅니다.
그림 7 COM 호출 가능 래퍼

그림 7 COM 호출 가능 래퍼

CCW는 .NET 개체를 래핑하고 이 개체와 CLR 환경을 연결해 주며 .NET 개체가 기본 .NET 개체인 것처럼 COM 클라이언트에 나타나도록 만듭니다. COM 호출이 가능한 래퍼와 함께 작동하도록 하려면 .NET 구성 요소의 어셈블리에 유효한 이름이 지정되어야 합니다. 그렇지 않으면 CLR 런타임에서는 이름을 명확하게 식별할 수 없게 됩니다. 이 이름은 표준 .NET 구성 요소의 위치 규칙을 따라야 하는데, 이는 이름이 글로벌 어셈블리 캐시(GAC)에 위치해야 하거나 또는 경우에 따라 클라이언트 응용 프로그램의 디렉터리 트리 내에 존재해야 함을 의미합니다. COM에서 만들려는 모든 .NET 클래스는 기본 생성자를 제공해야 하는데, 기본 생성자란 매개 변수가 필요하지 않은 생성자를 말합니다. COM 개체 생성 함수는 함수에서 만들어진 개체한테 매개 변수를 전달하는 방식을 알지 못하므로, 클래스에서 이러한 상황이 필요하지 않도록 만들어야 합니다. COM 클라이언트에 사용할 필요가 없는 생성자를 가지고 있는 한, .NET 클래스에서는 .NET 클라이언트에 사용하기 위해 매개 변수화된 생성자를 제한 없이 사용할 수 있습니다.
COM 클라이언트가 .NET 개체를 찾기 위해서는, 개체를 만들 때 클라이언트가 서버를 찾기 위해 COM에서 필요로 하는 레지스트리 항목을 만들어야 합니다. 이때 .NET SDK에 있는 RegAsm.exe라는 유틸리티 프로그램을 사용하시면 됩니다. 이 프로그램에서는 .NET 클래스에 있는 메타 데이터를 읽은 후 COM 클라이언트를 메타 데이터로 지정하는 레지스트리 항목을 만듭니다. 그림?8은 이 프로그램에서 만들어지는 레지스트리 항목입니다. 여기에 사용하는 COM 서버는 중개 DLL인 Mscoree.dll입니다. InProcServer32 키의 Class 값은 만들고 래핑하려는 .NET 클래스를 이 DLL에게 알려주며, Assembly 항목은 이 클래스를 찾을 .NET 어셈블리를 DLL에게 알려 줍니다.

GAC의 복잡한 디렉터리 구조 때문에 GAC에 있는 .NET 구성 요소를 등록하는 것은 어렵습니다. Explorer에는 사용자 지정 플러그인이 있어 GAC의 복잡성을 단순화할 수 있기 때문에, Explore에서는 복잡해 보이지 않지만 명령줄에서는 손대기 어려울 정도로 디렉터리가 중첩되어 있습니다. .NET 구성 요소를 표준 디렉터리에 같다 좋으면 아주 편리합니다. 여기서 regasm.exe를 실행한 다음 gacutil.exe를 사용하여 구성 요소를 GAC로 이동시킵니다. 등록한 다음 COM 구성 요소를 이동시키면 사용할 수 없게 될 수 있습니다. 그러나 .NET에서는 이런 경우가 없습니다. 사실, DLL Hell은 .NET 아키텍처가 해결하기 위해 특수하게 디자인되었던 문제 중의 하나입니다. 공유된 모든 구성 요소는 유효한 이름을 사용하여 이름이 충돌되지 않도록 지정된 상태에서 GAC에 저장됩니다. .NET 로더는 요청자의 디렉터리 트리에서 요청된 구성 요소를 검사한 다음 원하는 어셈블리를 찾을 수 없는 경우 GAC를 검사합니다.
COM 클라이언트는 .NET 개체가 기본 COM 개체인 것처럼 .NET 개체를 액세스합니다. 클라이언트가 CoCreateInstance를 호출하여 개체를 만들면 레지스트리는 등록된 서버인 Mscoree.dll로 요청을 보냅니다. 이 DLL은 요청된 CLSID를 검사하고 레지스트리를 읽은 다음, 만들려는 .NET 클래스와 해당 클래스가 포함된 어셈블리를 찾으며 해당 .NET 클래스를 기반으로 즉시 CCW을 수행합니다. 모든 DLL COM 서버는 이러한 목적으로 사용할 DllGetClassObject라는 글로벌 함수를 제공합니다. CCW는 기본 COM 형식을 자신의 .NET 형식으로 변환(예: BSTR을 .NET 문자열로 변환)한 다음 .NET 개체로 보냅니다. 또한 .NET의 결과와 오류를 COM으로 변환하기도 합니다. 이 기사에 대한 예제 코드에는 현재 시간을 제공하는 .NET 구성 요소와 현재 시간에 액세스하는 COM 클라이언트가 포함되어 있습니다.

.NET 개발자는 COM 클라이언트 상에서 일부 메서드, 인터페이스 및 클래스는 사용하고 다른 메서드, 인터페이스 및 클래스는 사용하지 않도록 얼마든지 원할 수 있습니다. 따라서 .NET에서는 System.Runtime.InteropServices.ComVisible라는 메타 데이터 속성을 제공합니다. 이 속성은 어셈블리, 클래스, 인터페이스, 개별 메서드 등에 사용될 수 있습니다. CCW는 런타임 도중 이 메타 데이터 특성을 읽은 다음 이 특성이 False로 설정된 요청에 대해 COM으로부터 오는 모든 액세스를 거부할 수 있습니다. 단 하위 계층에서 만들어진 설정은 상위 계층에서는 무시됩니다.

예를 들어, 인터페이스에서 ComVisible을 False로 설정하고 해당 인터페이스의 메서드 중 하나를 True를 설정하면, 클라이언트는 해당 인터페이스에서 해당 메서드를 호출할 수 있지만 다른 메서드에는 액세스할 수 없습니다. 예제 프로그램에서는 다음 코드에 나타난 것처럼 클래스에 대해 이 속성을 True로 설정했습니다. 따라서 COM에서는 이 항목을 볼 수 있습니다.

<System.Runtime.InteropServices.ComVisible(True)>
Public Class Class1
현재 CLR 기본 설정이 True이므로 이 특성이 False로 특별히 설정되어 있지 않으면 이 항목이 COM에 표시됩니다. 그러나, 어셈블리에 대한 Visual Studio .NET의 현재 기본 동작은 프로젝트의 AssemblyInfo.vb 파일에서 이 특성을 False로 설정하게 되어 있습니다. 이것은 사용자가 특별히 표시하도록 지정하지 않는 한 어셈블리 내에서 COM에 아무 것도 표시되지 않는다는 것을 의미합니다.?

개인적으로는 시스템 기본값을 완전히 반대되는 값으로 재정의하는 개발 도구의 관행에 전혀 동의하지는 않습니다. 사용자를 깜짝 놀라게 하는 것은 일반적으로 좋지 않은 것이므로 가능한 한 피해야 할 일입니다. 시스템 설명서를 참조하는 모든 프로그래머는 ComVisible의 기본값이 True임을 알고 있을 것입니다. 그러나 Visual Studio를 사용하여 간단한 구성 요소를 작성할 때 COM은 그 구성 요소를 찾을 수 없으며 프로그래머는 그 이유를 알 수 없을 것입니다. 기본값이 특별히 옳다거나 잘못되었다고 말하는 것은 아닙니다. 각각의 기본값에는 장점과 단점이 있으며 두 기본값이 모두 옳을 수 있다고 생각합니다. 그러나, 운영 체제와 기본적인 개발 환경에서 서로 다른 값을 사용하는 경우 제품이 출시되기 전에 둘 중의 한 기본값을 변경(둘 다 변경하면 결과가 동일)하지 않으면 Microsoft에 수많은 전화 문의가 쇄도할 것입니다.

 

'Dev > .NET' 카테고리의 다른 글

SerializeToXML - C#  (0) 2009.08.10
C# Web Service -> REST  (0) 2009.08.09
Integrating WCF Services with COM+  (0) 2008.09.03
Interop 응용 프로그램 배포  (0) 2007.10.05
&quot;The underlying connection was closed&quot; - WebServices  (0) 2005.10.13

Solving "The underlying connection was closed: An unexpected error occurred on a send." (Webservices)

 

Sometimes when you invoke a webservice the call fails with the following exception:

 

System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send.
at System.Web.Services.Protocols.WebClientProtocol.GetWebResponse(WebRequest request)
at System.Web.Services.Protocols.HttpWebClientProtocol.GetWebResponse(WebRequest request)
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
at ...

 

In some cases the first call to the webservice works just fine, but if in the following few minutes no new call to the webservice is made, the next call would throw the exception shown above. This problem could be solved by altering the generated proxy class; in the GetWebRequest function the KeepAlive property must be set to false. This can be accomplished by following these steps:

  • Add a Web Reference using the normal way (if you haven't already added one ofcourse).
  • Make sure Show All Files menu item is enable in the Project menu.
  • In the Solution Explorer window, navigate to:
    • Web References
      • <Name of your webservice>
        • Reference.map
          • Reference.cs (or .vb)
  • Open the Reference.cs file and add following code in the webservice proxy class:
    protected override System.Net.WebRequest GetWebRequest(Uri uri)
    {
     System.Net.HttpWebRequest webRequest =
      (System.Net.HttpWebRequest) base.GetWebRequest(uri);
     webRequest.KeepAlive = false;
     return webRequest;
    }

'Dev > .NET' 카테고리의 다른 글

SerializeToXML - C#  (0) 2009.08.10
C# Web Service -> REST  (0) 2009.08.09
Integrating WCF Services with COM+  (0) 2008.09.03
Interop 응용 프로그램 배포  (0) 2007.10.05
RCW, CCW  (0) 2007.10.04

+ Recent posts