Aggregating the In-Memory Datasource

  • 리비전 슬러그: Aggregating_the_In-Memory_Datasource
  • 리비전 제목: Aggregating the In-Memory Datasource
  • 리비전 아이디: 196762
  • 제작일시:
  • 만든이: 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 그래프는 다른 정보들과 통합될 수 있으며 스타일을 적용하여 디스플레이될 수 있을 것입니다)

Technical Details

As before, have an nsCOMPtr as your delegate, but this time around, don't derive from nsIRDFDataSource. Also, instead of keeping an nsCOMPtr<nsIRDFDataSource>, you'll just want an nsCOMPtr<nsISupports>:

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

Construct the datasource delegate when your object is constructed (or, at worst, when somebody QI's for it):

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

Note passing this as the "outer" parameter.

Now, if the in-memory datasource's implementation of QueryInterface() fails because it doesn't support the requested interface, it will forward the query interface to its "outer" (which is "us"). This preserves the symmetrical property of QueryInterface().

For us to preserve symmetry, our QueryInterface() implementation needs to forward nsIRDFDataSource to the delegate3:

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;
}

The only other thing that you'll need to be aware of is that you'll need to QueryInterface() from nsISupports to nsIRDFDataSource before you can actually do anything useful with the datasource from within your object. For example:

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

  rv = ds->Assert(/* something useful here */);

  // etc...

  return NS_OK;
}

It may be tempting to keep a pointer to the aggregate's nsIRDFDataSource in a member variable, but you can't do that. Why? Because if you did, you'd hold a circular reference that would never unwind.

Notes

  1. Describing all of the vagaries of XPCOM aggregation is beyond the scope of this document. The basic idea is to overload QueryInterface(), allowing it to return a delegate object that supports the interface. There is some trickery involved on the delegate's part to ensure that reference counting is done sanely, and that the reflexive, symmetric, and transitive properties of QueryInterface() are preserved. If you're really interested, I'd recommend reading about it in a COM book.
  2. For more information on writing a datasource, see the RDF Datasource How-To document.
  3. You could also forward other interfaces to the mInner that you know it can support; however, this is extremely risky. It's risky because another implementation of the same object might not support those interfaces. Then the QueryInterface() will be forwarded back to you, and we'll recurse off to infinity (and beyond!...)

리비전 소스

<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="Technical_Details"> Technical Details </h3>
<p>As before, have an <code>nsCOMPtr</code> as your delegate, but this time around, <i>don't</i> derive from <code>nsIRDFDataSource</code>. Also, instead of keeping an <code>nsCOMPtr&lt;nsIRDFDataSource&gt;</code>, you'll just want an <code>nsCOMPtr&lt;nsISupports&gt;</code>:
</p>
<pre>class MyClass : public nsIMyInterface {
    ...
private:
    nsCOMPtr&lt;nsISupports&gt; mInner;
};
</pre>
<p>Construct the datasource delegate when your object is constructed (or, at worst, when somebody QI's for it):
</p>
<pre>rv = nsComponentManager::CreateInstance(
        kRDFInMemoryDataSourceCID,
        this, /* the "outer" */
        nsCOMTypeInfo&lt;nsISupports&gt;::GetIID(),
        getter_AddRefs(mInner));
</pre>
<p>Note passing <code>this</code> as the "outer" parameter.
</p><p>Now, if the in-memory datasource's implementation of <code>QueryInterface()</code> fails because it doesn't support the requested interface, it will <i>forward</i> the query interface to its "outer" (which is "us"). This preserves the symmetrical property of <code>QueryInterface()</code>.
</p><p>For us to preserve symmetry, our <code>QueryInterface()</code> implementation needs to forward <code>nsIRDFDataSource</code> to the delegate<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>The only other thing that you'll need to be aware of is that you'll need to <code>QueryInterface()</code> from <code>nsISupports</code> to <code>nsIRDFDataSource</code> before you can actually do anything useful with the datasource from within your object. For example:
</p>
<pre>NS_IMETHODIMP
MyClass::DoSomething()
{
  nsCOMPtr&lt;nsIRDFDataSopurce&gt; ds = do_QueryInterface(mInner);

  rv = ds-&gt;Assert(/* something useful here */);

  // etc...

  return NS_OK;
}
</pre>
<p>It may be tempting to keep a pointer to the aggregate's <code>nsIRDFDataSource</code> in a member variable, but <i>you can't do that</i>. Why? Because if you did, you'd hold a circular reference that would never unwind.
</p>
<h3 name="Notes"> Notes </h3>
<ol><li>Describing all of the vagaries of <a href="ko/XPCOM">XPCOM</a> aggregation is beyond the scope of this document. The basic idea is to overload <code>QueryInterface()</code>, allowing it to return a <i>delegate</i> object that supports the interface. There is some trickery involved on the delegate's part to ensure that reference counting is done sanely, and that the reflexive, symmetric, and transitive properties of <code>QueryInterface()</code> are preserved. If you're really interested, I'd recommend reading about it in a COM book.
</li><li>For more information on writing a datasource, see the <a href="ko/RDF_Datasource_How-To">RDF Datasource How-To</a> document.
</li><li>You could also forward other interfaces to the <code>mInner</code> that you <i>know</i> it can support; however, this is <b>extremely risky</b>. It's risky because another implementation of the same object might <i>not</i> support those interfaces. Then the <code>QueryInterface()</code> will be forwarded back to you, and we'll recurse off to infinity (and beyond!...)
</li></ol>
Revert to this revision