Aggregating the In-Memory Datasource

  • 리비전 슬러그: Aggregating_the_In-Memory_Datasource
  • 리비전 제목: Aggregating the In-Memory Datasource
  • 리비전 아이디: 196765
  • 제작일시:
  • 만든이: Nam-Hyung Kim
  • 현재 리비전인가요? 아니오
  • 댓글 /* 개요 */

리비전 내용

개요

당신은 메모리 상의 (in-memory) 데이터 소스에 XPCOM 모음(aggregation)1을 사용할 수 있습니다. 왜 이것이 필요할까요? 만약 당신이 데이터 소스2를 작성했다고 하면, 이를 구현하기 위한 한가지 방법은 메모리 상의 데이터 소스를 래핑(wrapping) 하는 것 입니다. 즉,

MyClass : public nsIMyInterface, public nsIRDFDataSource {
private:
    nsCOMPtr<nsIRDFDataSource> mInner;

public:
    // nsIRDFDataSource methods
    NS_IMETHOD Init(const char* aURI) {
        return mInner->Init(aURI);
    }

    NS_IMETHOD GetURI(char* *aURI) {
        return mInner->GetURI(aURI);
    }

    // etc., for each method in nsIRDFDataSource!
};

이것은 매우 힘들고, 에러를 발생시키기 쉬우며 인터페이스들이 (조금이라도) 변경된다면 역시 변경될 것입니다. 모음(aggregation)이 해결책이 될 수 있다! 다음은 이에 대한 자세한 사항들(gory details)입니다.

언제 동작하지 않을까?

비록 이 기법(magic)을 사용하는 것은 매우 편리하지만, 메모리 상의 데이터 소스의 메소드를 "오버라이드"(override)하고 싶은 경우에는 동작하지 않을 것 입니다. 예를 들어, 필자는 북마크 데이터 소스를 작성하고 있는 동안, Assert() 메소드가 오직 "북마크에 관련된" 어서션(assertion)만을 허용(accept)하도록 만들고 싶었습니다. 만약 단지 메모리 상의 데이터 소스에 위임하도록 했었다면, Assert() 메소드는 오래된 임의의 쓰레기 값들을 허용했을 것입니다. 마찬가지로, Flush() 메소드를 오버라이드하여 bookmarks.html 파일을 다시 디스크에 기록하도록 할 수 있었습니다.

다시 말해서, "읽기 전용 리플렉션"을 얻기 위해 데이터 소스를 구현할 때에만 이 기법이 유용하게 사용될 수 있습니다. 즉, 어떤 정보의 내용을 RDF 그래프로 반영(reflect)하고 싶은 경우를 말합니다. (아마도 RDF 그래프는 다른 정보들과 통합될 수 있으며 스타일을 적용하여 디스플레이될 수 있을 것입니다)

기술적인 세부 사항

앞의 코드처럼 nsCOMPtr를 위임자를 사용하겠지만, 이번의 경우는 nsIRDFDataSource 객체를 이용하지 않을 것이다. nsCOMPtr<nsIRDFDataSource> 를 사용하는 대신 nsCOMPtr<nsISupports> 를 사용하길 원하게 될 것이다.

class MyClass : public nsIMyInterface {
    ...
private:
    nsCOMPtr<nsISupports> mInner;
};

객체가 생성될 때 (혹은, 최악의 경우, 누군가 QI(?)를 한 경우에) 데이터 소스 위임자를 구성한다.

rv = nsComponentManager::CreateInstance(
        kRDFInMemoryDataSourceCID,
        this, /* the "outer" */
        nsCOMTypeInfo<nsISupports>::GetIID(),
        getter_AddRefs(mInner));

this를 "outer" 인수로 넘긴 것에 주의하자. 이제, 메모리 상의 데이터 소스의 QueryInterface() 메소드의 구현이 실패한다면 그것은 요청한 인터페이스를 구현하지 않았기 때문일 것이다. 이 경우 QueryInterface() 는 "outer"(우리가 구현한 객체)에게 전달될 것이다. 이것은 QueryInterface() 의 대칭적 특성을 보존한다.

대칭성을 만족하기 위해, 우리의 QueryInterface() 구현은 nsIRDFDataSource를 위임자에서 전달할 필요가 있다.3

NS_IMETHODIMP
MyClass::QueryInterface(REFNSIID aIID, void** aResult)
{
  NS_PRECONDITION(aResult != nsnull, "null ptr");
  if (! aResult)
    return NS_ERROR_NULL_POINTER;

  if (aIID.Equals(nsCOMTypeInfo<nsIMyInterface>::GetIID()) ||
      aIID.Equals(nsCOMTypeInfo<nsISupports>::GetIID())) {
    *aResult = NS_STATIC_CAST(nsIGlobalHistory*, this);
  }
  else if (aIID.Equals(nsCOMTypeInfo<nsIRDFDataSource>::GetIID())) {
    return mInner->QueryInterface(aIID, aResult);
  }
  else {
    *aResult = nsnull;
    return NS_NOINTERFACE;
  }

  NS_ADDREF(NS_STATIC_CAST(nsISupports*, aResult));
  return NS_OK;
}

여기서 주의해야 할 사항은 당신의 객체 내의 데이터 소스를 통해 무언가를 하기 전에 nsISupports에서 nsIRDFDataSourceQueryInterface()를 수행해야 한다는 것이다. 예를 들면:

NS_IMETHODIMP
MyClass::DoSomething()
{
  nsCOMPtr<nsIRDFDataSopurce> ds = do_QueryInterface(mInner);

  rv = ds->Assert(/* 필요한 작업을 수행한다 */);

  // etc...

  return NS_OK;
}

nsIRDFDataSource의 모음에 대한 포인터를 멤버 변수로 가지고 싶어할 수 있을 것이다. 하지만 이것은 불가능하다. 만약 그렇게 한다면 끝나지 않는 순환 참조가 발생할 것이다.

  1. 모든 종류의 XPCOM 모음(aggregation)에 대해서 설명하는 것은 이 문서의 범위를 넘어간다. QueryInterface() 메소드를 오버로드하는 기본적인 아이디어는, 인터페이스를 지원하는 위임자(delegate) 객체를 반환하도록 하는 것이다. 위임자의 참조 카운트를 안전하게 관리하고, QueryInterface()의 반사적(reflexive), 대칭적(symmetric), 추이적 (transitive) 특성을 만족시키기 위해 약간의 트릭이 사용된다. 이 부분에 대해 흥미를 느낀 사람이라면, COM 관련 서적의 해당 부분을 읽어보기 바란다.
  2. 데이터 소스를 작성하기 위한 정보를 더 얻고 싶다면, RDF 데이터 소스 How-To 문서를 보기 바란다.
  3. mInner 가 지원하는 것을 알고 있는 경우, 다른 인터페이스들도 mInner 에게 전달(forward)할 수 있다. 하지만 이는 매우 위험하다. 왜냐하면 동일한 객체의 다른 구현에서는 이러한 인터페이스를 지원하지 않을 수 있기 때문이다. 이 경우 QueryInterface()는 다시 당신의 객체?로 넘어오게 되며, 이는 무한히 반복될 것이다.

리비전 소스

<h3 name=".EA.B0.9C.EC.9A.94"> 개요 </h3>
<p>당신은 메모리 상의 (in-memory) 데이터 소스에 XPCOM 모음(aggregation)<sup>1</sup>을 사용할 수 있습니다.
왜 이것이 필요할까요?
만약 당신이 데이터 소스<sup>2</sup>를 작성했다고 하면, 
이를 구현하기 위한 한가지 방법은 메모리 상의 데이터 소스를 래핑(wrapping) 하는 것 입니다. 즉,
</p>
<pre>MyClass : public nsIMyInterface, public nsIRDFDataSource {
private:
    nsCOMPtr&lt;nsIRDFDataSource&gt; mInner;

public:
    // nsIRDFDataSource methods
    NS_IMETHOD Init(const char* aURI) {
        return mInner-&gt;Init(aURI);
    }

    NS_IMETHOD GetURI(char* *aURI) {
        return mInner-&gt;GetURI(aURI);
    }

    // etc., for each method in nsIRDFDataSource!
};
</pre>
<p>이것은 매우 힘들고, 에러를 발생시키기 쉬우며 인터페이스들이 (조금이라도) 변경된다면 역시 변경될 것입니다.
모음(aggregation)이 해결책이 될 수 있다! 다음은 이에 대한 자세한 사항들(gory details)입니다.
</p>
<h3 name=".EC.96.B8.EC.A0.9C_.EB.8F.99.EC.9E.91.ED.95.98.EC.A7.80_.EC.95.8A.EC.9D.84.EA.B9.8C.3F"> 언제 동작하지 않을까? </h3>
<p>비록 이 기법(magic)을 사용하는 것은 매우 편리하지만,
메모리 상의 데이터 소스의 메소드를 "오버라이드"(override)하고 싶은 경우에는 동작하지 않을 것 입니다.
예를 들어, 필자는
<a class="external" href="http://lxr.mozilla.org/mozilla/source/browser/components/bookmarks/src/nsBookmarksService.cpp">북마크 데이터 소스</a>를
작성하고 있는 동안, <code>Assert()</code> 메소드가 오직 "북마크에 관련된" 어서션(assertion)만을
허용(accept)하도록 만들고 싶었습니다.
만약 단지 메모리 상의 데이터 소스에 위임하도록 했었다면,
<code>Assert()</code> 메소드는 오래된 임의의 쓰레기 값들을 허용했을 것입니다.
마찬가지로, <code>Flush()</code> 메소드를 오버라이드하여 
<code>bookmarks.html</code> 파일을 다시 디스크에 기록하도록 할 수 있었습니다.
</p><p>다시 말해서, "읽기 전용 리플렉션"을 얻기 위해 데이터 소스를 구현할 때에만
이 기법이 유용하게 사용될 수 있습니다.
즉, 어떤 정보의 내용을 RDF 그래프로 반영(reflect)하고 싶은 경우를 말합니다.
(아마도 RDF 그래프는 다른 정보들과 통합될 수 있으며 스타일을 적용하여 디스플레이될 수 있을 것입니다)
</p>
<h3 name=".EA.B8.B0.EC.88.A0.EC.A0.81.EC.9D.B8_.EC.84.B8.EB.B6.80_.EC.82.AC.ED.95.AD"> 기술적인 세부 사항 </h3>
<p>앞의 코드처럼 <code>nsCOMPtr</code>를 위임자를 사용하겠지만,
이번의 경우는 <code>nsIRDFDataSource</code> 객체를 이용하지 <i>않을</i> 것이다.
<code>nsCOMPtr&lt;nsIRDFDataSource&gt;</code> 를 사용하는 대신
<code>nsCOMPtr&lt;nsISupports&gt;</code> 를 사용하길 원하게 될 것이다.
</p>
<pre>class MyClass : public nsIMyInterface {
    ...
private:
    nsCOMPtr&lt;nsISupports&gt; mInner;
};
</pre>
<p>객체가 생성될 때 (혹은, 최악의 경우, 누군가 QI(?)를 한 경우에) 데이터 소스 위임자를 구성한다.
</p>
<pre>rv = nsComponentManager::CreateInstance(
        kRDFInMemoryDataSourceCID,
        this, /* the "outer" */
        nsCOMTypeInfo&lt;nsISupports&gt;::GetIID(),
        getter_AddRefs(mInner));
</pre>
<p><code>this</code>를 "outer" 인수로 넘긴 것에 주의하자.
이제, 메모리 상의 데이터 소스의 <code>QueryInterface()</code> 메소드의 구현이 실패한다면
그것은 요청한 인터페이스를 구현하지 않았기 때문일 것이다.
이 경우 <code>QueryInterface()</code> 는 "outer"(우리가 구현한 객체)에게 <i>전달</i>될 것이다.
이것은 <code>QueryInterface()</code> 의 대칭적 특성을 보존한다.
</p><p>대칭성을 만족하기 위해, 우리의 <code>QueryInterface()</code> 구현은
<code>nsIRDFDataSource</code>를 위임자에서 전달할 필요가 있다.<sup>3</sup>
</p>
<pre>NS_IMETHODIMP
MyClass::QueryInterface(REFNSIID aIID, void** aResult)
{
  NS_PRECONDITION(aResult != nsnull, "null ptr");
  if (! aResult)
    return NS_ERROR_NULL_POINTER;

  if (aIID.Equals(nsCOMTypeInfo&lt;nsIMyInterface&gt;::GetIID()) ||
      aIID.Equals(nsCOMTypeInfo&lt;nsISupports&gt;::GetIID())) {
    *aResult = NS_STATIC_CAST(nsIGlobalHistory*, this);
  }
  else if (aIID.Equals(nsCOMTypeInfo&lt;nsIRDFDataSource&gt;::GetIID())) {
    return mInner-&gt;QueryInterface(aIID, aResult);
  }
  else {
    *aResult = nsnull;
    return NS_NOINTERFACE;
  }

  NS_ADDREF(NS_STATIC_CAST(nsISupports*, aResult));
  return NS_OK;
}
</pre>
<p>여기서 주의해야 할 사항은 당신의 객체 내의 데이터 소스를 통해 무언가를 하기 전에
<code>nsISupports</code>에서 <code>nsIRDFDataSource</code>로
<code>QueryInterface()</code>를 수행해야 한다는 것이다.
예를 들면:
</p>
<pre>NS_IMETHODIMP
MyClass::DoSomething()
{
  nsCOMPtr&lt;nsIRDFDataSopurce&gt; ds = do_QueryInterface(mInner);

  rv = ds-&gt;Assert(/* 필요한 작업을 수행한다 */);

  // etc...

  return NS_OK;
}
</pre>
<p><code>nsIRDFDataSource</code>의 모음에 대한 포인터를 멤버 변수로 가지고 싶어할 수 있을 것이다.
하지만 이것은 불가능하다. 만약 그렇게 한다면 끝나지 않는 순환 참조가 발생할 것이다.
</p>
<h3 name=".EC.A3.BC"> 주 </h3>
<ol><li>모든 종류의 <a href="ko/XPCOM">XPCOM</a> 모음(aggregation)에 대해서 설명하는 것은 이 문서의 범위를 넘어간다. <code>QueryInterface()</code> 메소드를 오버로드하는 기본적인 아이디어는, 인터페이스를 지원하는 <i>위임자(delegate)</i> 객체를 반환하도록 하는 것이다. 위임자의 참조 카운트를 안전하게 관리하고, <code>QueryInterface()</code>의 반사적(reflexive), 대칭적(symmetric), 추이적 (transitive) 특성을 만족시키기 위해 약간의 트릭이 사용된다. 이 부분에 대해 흥미를 느낀 사람이라면, COM 관련 서적의 해당 부분을 읽어보기 바란다.
</li><li>데이터 소스를 작성하기 위한 정보를 더 얻고 싶다면, <a href="ko/RDF_Datasource_How-To">RDF 데이터 소스 How-To</a> 문서를 보기 바란다.
</li><li><code>mInner</code> 가 지원하는 것을 <i>알고</i> 있는 경우, 다른 인터페이스들도 <code>mInner</code> 에게 전달(forward)할 수 있다. 하지만 이는 <b>매우 위험하다</b>. 왜냐하면 동일한 객체의 다른 구현에서는 이러한 인터페이스를 지원하지 <i>않을</i> 수 있기 때문이다. 이 경우 <code>QueryInterface()</code>는 다시 당신의 객체?로 넘어오게 되며, 이는 무한히 반복될 것이다.
</li></ol>
Revert to this revision