Getting Started Guide 编辑

If you have never used nsCOMPtrs before, this section is for you. If you're already familiar with nsCOMPtrs, then you might want to skip ahead to the Reference Manual or the FAQ. Don't worry; the Getting Started Guide is short.

Introduction

What is nsCOMPtr?

nsCOMPtr is a tool to help prevent leaks.

nsCOMPtr is a "smart pointer". It is a template class that acts, syntactically, just like an ordinary pointer in C or C++, i.e., you can apply * or -> to it to "get to" what it points at. nsCOMPtr is smart in that, unlike a raw C++ pointer to an XPCOM object, nsCOMPtr manages AddRef, Release, and QueryInterface for you. nsCOMPtr is defined in the source files:

...though you probably don't want to look in there, just yet.

With nsCOMPtr, you can write code that is shorter, cleaner, clearer, and safer, than you can with raw XPCOM interface pointers.

XPCOM Basics: Ownership and Reference Counting

This is a quick refresher on some fundamental issues of XPCOM. You should already know this, and should be able to just skim this short section. If this is unfamiliar material, you're not ready for nsCOMPtrs yet. A good place to learn about the basic rules and reasoning behind COM is in Essential COM by Don Box. Don Box gets into more of the details, traps, and pitfalls of COM in Effective COM. You should also have a reasonable knowledge of C++. Probably the three most helpful books on this topic are The C++ Programming Language by Bjarne Stroustrup, Effective C++, and More Effective C++ by Scott Meyers.

All XPCOM objects are allocated on the heap. Clients don't get to know much about the implementation of any such object. They reference it only through a pointer to an `interface', i.e., the static type of the pointer is a pointer to an abstract base class, the actual object pointed to is a class derived from that abstract base class. The XPCOM object is said to `implement that interface'. The client's reference to the object is typically called `an interface pointer'.

An object may implement many interfaces. Each interface is (at least conceptually) separately `reference counted'. That is, the interface keeps a count of the number of clients holding references to it. When that count goes to zero, the interface may delete itself. Clients are expected to keep this reference count accurate by incrementing it when they acquire a reference to the interface, and decrementing it before they let go. To facilitate this, all interfaces inherit from an abstract base class that provides the member functions AddRef, and Release.

A rule of XPCOM is that any function that creates or returns an interface pointer will have already AddRefed it. The caller can then hold onto the reference indefinitely, calling Release when it no longer needs it. When the last pointer to an interface is Released, the interface (and consequently, typically the underlying object) will delete itself. As long as there is an outstanding AddRef against the interface, it continues to exist. If you forget to call Release, the object will leak, i.e., the storage for that object will never be reclaimed. Leaks are bad :-).

A reference through which you will call AddRef and Release is called an owning reference. It holds a stake in the underlying object. That object cannot go away until the owning reference has relinquished its claim. Not all references need to be owning references. In fact, if two objects somehow end up owning each other (even transitively) it becomes difficult for either of those object to be reclaimed without adding some `out-of-band' mechanism for breaking the ownership cycle. The document Some COM Ownership Guidelines provides some hints on when ownership is needed. The following lists are good starting point, but by no means complete.

You use an owning reference when

  • you created the object;
  • you got the object from a function that might have created it, e.g., any `getter' function, such as QueryInterface, or CreateInstance. All good getters AddRef the interface pointers they produce, thus providing you with an owning reference;
  • you will hold onto the reference longer than the scope of the function in which you acquired it, e.g., you got it as a parameter, but you're hanging onto it in a member variable (see, for example, Comparison 1, below).

You don't need an owning reference when

  • the object is passed in as a parameter, and you don't need to keep it any longer than the scope of this function;
  • the object's lifetime is known to contain yours in some well defined way, e.g., in the nodes of a tree, parent nodes keep owning references to their children, children need not keep owning references to their parents.

It turns out that reference counting by hand is hard for programmers to get right. It may sound simple, but in practice it's very easy to forget to Release at the appropriate moment. Or to AddRef too many or too few times.

How does nsCOMPtr help?

nsCOMPtr manages AddRef, Release, and other red-tape for you. An nsCOMPtr looks and acts as much like a raw XPCOM interface pointer as C allows, but it knows it owns the object it points to. This takes a little getting used to on your part, but ends up with less typing, clearer, safer code, and less leaks.

For instance, here is a typical snippet of code (at its most compact) where you assign a XPCOM interface pointer into a member variable, i.e., the body of a `setter' function, side-by-side using raw XPCOM interface pointers and nsCOMPtrs.

Comparison 1. Setting a member variable.
// raw XPCOM interface pointers...
// given: |nsIFoo* mFooPtr;|

/*
   |AddRef| the new value if it's not
   |NULL|; assign it in; and |Release|
   the old value, if any (so we don't
   leak it).

   This order of assignment is special
   and must be used to avoid particular
   ownership bugs.
 */

NS_IF_ADDREF(aFooPtr);
nsIFoo* temp = mFooPtr;
mFooPtr = aFooPtr;
NS_IF_RELEASE(temp);
// |nsCOMPtr|...
// given: |nsCOMPtr<nsIFoo> mFooPtr;|

/*
   This assignment automatically
   |Release|s the old value in
   |mFooPtr|, if any, and |AddRef|s the
   new one, in the appropriate sequence
   to avoid the ownership bug mentioned
   earlier.
 */



mFooPtr = aFooPtr;





Additionally, the class using raw XPCOM interface pointers will need a destructor to Release mFooPtr; and a constructor to ensure that mFooPtr is initially set to NULL (or some other reasonable value).

nsCOMPtr helps you write code that is leak-proof, exception safe, and significantly less verbose than you would with raw XPCOM interface pointers. With nsCOMPtr, you may never have to call AddRef, Release, or QueryInterface by hand.

You still have to understand XPCOM. You still have to know which functions return interface pointers that have already been AddRefed and which don't. You still have to ensure your program logic doesn't produce circularly referencing garbage. nsCOMPtr is not a panacea. It is, however, helpful, easy to use, well-tested, and polite. It doesn't require that a function author cooperate with you, nor does your use force others to use it.

Using nsCOMPtr

The Basics

In most cases, you'll use an nsCOMPtr exactly as you would a raw XPCOM interface pointer. Note the slight difference in declaration.

Comparison 2. Similarities: nsCOMPtr is syntactically similar to raw XPCOM interface pointers.
// raw XPCOM interface pointers...

nsIFoo* fooPtr = 0;
 // ...
fooPtr->SomeFunction(x, y, z);
AnotherFunction(fooPtr);

if ( fooPtr )
  // ...

if ( fooPtr == foo2Ptr )
  // ...
// |nsCOMPtr|...

nsCOMPtr<nsIFoo> fooPtr;
// ...
fooPtr->SomeFunction(x, y, z);
AnotherFunction(fooPtr);

if ( fooPtr )
  // ...

if ( fooPtr == foo2Ptr )
  // ...

There are two main differences. First: you no longer need, nor are you allowed, to call AddRef or Release.

Comparison 3. Differences: AddRef and Release are illegal for nsCOMPtrs.
// raw XPCOM interface pointers...
// given: |nsIFoo* mFooPtr;|

  /*
    Note: this sequence is not the
    correct order to do assign
    raw pointers anyway (see
    Comparison 1) but I need it
    for this comparison.
  */

NS_IF_RELEASE(mFooPtr);

mFooPtr = aFooPtr;
NS_IF_ADDREF(mFooPtr);


// |nsCOMPtr|...
// given: |nsCOMPtr<nsIFoo> mFooPtr;|

  /*
    You no longer need, nor will the
    compiler let you, call |AddRef|,
    or |Release|.
  */



NS_IF_RELEASE(mFooPtr);
  // Error: |Release| is private
mFooPtr = aFooPtr;
NS_IF_ADDREF(mFooPtr);
  // Error: |AddRef| is private

Second: you can't just pass the address of an nsCOMPtr to a getter expecting to return a result through a raw XPCOM interface pointer parameter. You have to `annotate' the nsCOMPtr with the getter_AddRefs directive.

Comparison 4. Differences: apply getter_AddRefs when using an nsCOMPtr as a `out parameter'.
// raw XPCOM interface pointers...

nsIFoo* foo;

GetFoo(&foo);
// |nsCOMPtr|s...

nsCOMPtr<nsIFoo> foo;

GetFoo(getter_AddRefs(foo));

That's it. You now know enough to start using nsCOMPtrs. There are a few other details you will want to know as you use nsCOMPtr in more complicated situations, but what you've just learned will cover 90% of your uses.

A Few Details

There are a couple more things that will help you get the most out of nsCOMPtr.

Very often, you first get an interface pointer by calling QueryInterface. QueryInterface is a getter like any other, and you already know one way to call it, applying the getter_AddRefs rule, as described above.

The hard way to QueryInterface into an nsCOMPtr.
// A way (though not the best way) to |QueryInterface| into an |nsCOMPtr|...

nsCOMPtr<nsIFoo> foo;

nsresult rv = bar->QueryInterface(NS_GET_IID(nsIFoo), getter_AddRefs(foo));

  // Or, if you're a savvy XPCOM programmer,
  //  you use the type-safe version...
nsresult rv = CallQueryInterface(bar, getter_AddRefs(foo));

QueryInterface is used so frequently, though, that nsCOMPtr has a special facility to call it. This facility is type-safe, and it enables an nsCOMPtr to be directly constructed from the result of QueryInterface. Construction from the correct value is more efficient that construction followed by assignment. This facility is the do_QueryInterface directive. Using do_QueryInterface, the sample above would look like this

How to QueryInterface into an nsCOMPtr.
// The best way to |QueryInterface| into an |nsCOMPtr|...

nsresult rv;
nsCOMPtr<nsIFoo> foo(do_QueryInterface(bar, &rv));

  // Or, if you don't care about the |nsresult|
nsCOMPtr<nsIFoo> foo(do_QueryInterface(bar));

nsCOMPtr happily calls AddRef and Release implicitly. This same favor is not extended to QueryInterface. nsCOMPtr does not QueryInterface on assignment without your explicit permission in the form of the do_QueryInterface directive. You need never worry about hidden queries. However, be aware that if you should have queried but didn't, e.g., when assigning in a raw pointer where C++ allows the assignment, but XPCOM wouldn't, nsCOMPtr will assert at runtime. Use do_QueryInterface whenever you assign in a pointer to a XPCOM interface of a different type, even if that type happens to derive from the base type of the nsCOMPtr

Comparison 6. do_QueryInterface prevents XPCOM type errors.
class nsIBar
  : public nsIFoo ... { ... };

nsIBar* p = ...;

  // C++ thinks every |nsIBar*| is an
  //  |nsIFoo*|, therefore, C++ allows
  //  this...
nsCOMPtr<nsIFoo> foo = p;
  //  ...even though it is an XPCOM
  //  type error
class nsIBar
  : public nsIFoo ... { ... };

nsIBar* p = ...;



  // No type error here...
nsCOMPtr<nsIFoo> foo(do_QueryInterface(p));




Remember, the C++ type system and the XPCOM type system are really two independent things. Because XPCOM interfaces are expressed as abstract C++ base classes, you may be tempted to let C++ handle the differences, or to use C++ casts to navigate between interface types. This is wrong. The only sanctioned way to get between XPCOM types is with QueryInterface. In the example above, there is no reason to assume that the nsIFoo* C++ pulls out of p would be the same one that p->QueryInterface() would return.

dont_AddRef is a similar directive that helps you when you assign in a pointer that has already been AddRefed, e.g., because you called a getter that returned the pointer as its function result.

Using dont_AddRef.
nsCOMPtr<nsIFoo> foo(dont_AddRef(CreateFoo()));
  // |CreateFoo| |AddRef|s its result, as all good getters do

Something nsCOMPtr Doesn't Do

An nsCOMPtr does all that is necessary to behave as an owning reference. A given nsCOMPtr does not, however, cooperate in making other owning pointers. After learning how nsCOMPtr automatically AddRefs a pointer as it is being assigned in, the natural assumption is that it does the same thing when assigning out. Here is a snippet of code that demonstrates this misconception.

// Incorrect assumptions about |nsCOMPtr|...

nsresult
nsCacheRecord::GetFileSpec( nsIFileSpec** aFileSpec )
    /*
      ...fills in the callers |nsFileSpec*| (which the caller supplied
      the address of) with a copy of my member variable |mFileSpec|,
      an |nsCOMPtr|.  I.e., this function is a `getter'.

      Remember: good XPCOM getters always |AddRef| their result.
    */
  {
    // ...
    *aFileSpec = mFileSpec;
      // the |nsCOMPtr| should take care of the refcount here, right?
    return NS_OK;
  }

Plainly, the author believed (though perhaps with some question) that the nsCOMPtr, mFileSpec, would AddRef automatically as it was assigned into *aFileSpec. This is not the case. An nsCOMPtr automatically calls AddRef and Release (only) on its own behalf. In all other situations, it is designed to be a drop in replacement for a raw XPCOM pointer. Where ever an nsCOMPtr is used in a situation where a raw pointer is needed, the nsCOMPtr automatically provides one.

// |nsCOMPtr| produces a raw pointer when needed...

nsCOMPtr<nsIFoo> foo = ...;

  // 1.  Assigning into a raw pointer
nsIFoo* raw_foo = foo;

  // 2.  Assigning into another |nsCOMPtr|
nsCOMPtr<nsIFoo> foo2 = foo;

  // 3.  As a parameter
SetFoo(foo);

  // 4.  Testing the value in an |if| expression
  // 5.  Calling a member function
if ( foo )
  foo->DoSomething();

In all of these cases, pretty much the exact same code is executed (case 2 is slightly different, but the intent is the same). In each case, you are essentially extracting the raw pointer value for your own purpose. If the nsCOMPtr AddRefed the value each time you did that, cases 4 and 5 would obviously always generate leaks. SetFoo, from case 3, would have to be written two different ways when given an nsCOMPtr, it would know the value was already AddRefed, and when given a raw pointer it would assume the value was not AddRefed. Actually the contradictions run deeper than that. All these cases show that automatically AddRefing on `output' makes nsCOMPtrs and raw-pointers act differently from the point of view of the clients. The goal is to make them act the same so that nsCOMPtrs can be a drop in replacement (modulo managing its own `ownership').

Given what you now know, the rule is predictable. As described above, and unless you tell it otherwise, an nsCOMPtr AddRefs when you assign in to it. It does nothing when you assign out of it.

Where should I use nsCOMPtrs?

You should use an nsCOMPtr any place you use an interface pointer as an owning reference, i.e., where you call AddRef and Release on it. You should use nsCOMPtr as a member variable, where it will simplify setters, and eliminate constructors, destructors, and assignment operators. You should use nsCOMPtr on the stack, where it makes calling QueryInterface almost pleasant, and eliminates the complex logic that falls out of error handling.

Where shouldn't I use nsCOMPtrs?

Don't use nsCOMPtrs where you don't need an owning reference. See Some COM Ownership Guidelines. nsCOMPtr is designed to be used with XPCOM interfaces, so don't use it with non-interfaces with specific exceptions described below. Don't use nsCOMPtrs in XPCOM interfaces. Don't use them in plain old C code; nsCOMPtrs are, of course, a C++ only construct. Never cast an nsCOMPtr, it's almost guaranteed to leak.

nsCOMPtrs for non-interface classes

Appropriately formatted answer to come, in the meanwhile, the full details are available in this news posting (via Google Groups).

nsCOMPtrs in function signatures

In general, you won't want to use nsCOMPtr in the signature of XPCOM (i.e., `scriptable') functions. nsCOMPtr is not currently directly supported by IDL. However, you may sometime be tempted to use an nsCOMPtr in a non-scriptable function.

nsCOMPtr<T> f() don't return an nsCOMPtr

This practice is dangerous. Returning an AddRefed pointer in almost any form as a function result leads to several potential errors, some of which are leaks, some of which are dangling pointers. Returning an nsCOMPtr may seem like a good idea (since it tells clients you are giving them ownership), however it can be the cause of an dangling pointer. Consider:

// Don't return |nsCOMPtr|s...
nsCOMPtr<nsIFoo> CreateFoo();
// ...

nsIFoo* myFoo = CreateFoo(); // Oops: |myFoo| now dangles!
  // |CreateFoo| returns an |nsCOMPtr|, which
  //  automatically |Release|s right after this
  //  assignment.  Now |myFoo| refers to a
  //  deleted object.

You can tell callers you are giving them ownership in a way that doesn't pose this hazard by returning a already_AddRefed<T> (see bug #59212). An nsCOMPtr knows not to AddRef a value that is already_AddRefed.

// Preferred form: if you must return a pointer, use |already_AddRefed|...
already_AddRefed<nsIFoo> CreateFoo();
// ...

nsIFoo* myFoo1 = CreateFoo(); // doesn't dangle
nsCOMPtr<nsIFoo> myFoo2( CreateFoo() ); // doesn't leak
nsCOMPtr<nsIFoo> myFoo3( dont_AddRef(CreateFoo()) ); // redundant, but legal and correct

Compare this to the most frequent leaks caused by returning a raw pointer you have already AddRefed:

// Don't return raw pointers; that incites leaks...
nsIFoo* CreateFoo(); // returns an |AddRef|ed pointer
// ...

nsCOMPtr<nsIFoo> myFoo = CreateFoo(); // Oops: leak;
nsCOMPtr<nsIFoo> myFoo( dont_AddRef(CreateFoo()) );
  // Since |CreateFoo| already |AddRef|s its result, we must remind
  //  our |nsCOMPtr| not to.  It's easy to forget.  Prevent it in advance
  //  by not returning pointers as function results, or else by returning
  //  an |already_AddRefed<T>| as above.
void f( nsCOMPtr<T> ) don't pass an nsCOMPtr by value

This practice is wasteful, but not otherwise harmful. There is no need to AddRef parameters, as they are guaranteed to live as long as the function call. You only need to AddRef them as you store them in a structure that will live longer than the function call. Which means the appropriate member of that structure should be an nsCOMPtr, not the function parameter. Additionally, this signature may confuse callers into thinking they need an nsCOMPtr just to call the function.

void f( const nsCOMPtr<T>& ) don't pass an nsCOMPtr by const reference

Exactly as the signature above, this practice is wasteful, but not otherwise harmful, and has the same impact as passing an nsCOMPtr by value if the caller only supplied a raw pointer.

void f( nsCOMPtr<T>* ) avoid passing an nsCOMPtr by address, if possible

This practice requires callers to have an nsCOMPtr, and requires them to do a little extra work, as operator& for nsCOMPtrs is private (to help prevent leaks caused by casting; also see bug 59414). This is an acceptable way to declare `in/out' parameters, but prefer passing nsCOMPtrs by reference, as below.

// Passing an |nsCOMPtr| by pointer requires extra work...
void f( nsCOMPtr<nsIFoo>* );
// ...

nsCOMPtr<nsIFoo> myFoo = ...;

f( address_of(myFoo) );
void f( nsCOMPtr<T>& ) do pass an nsCOMPtr by reference for `in/out' parameters

This is the prefered scheme for providing `in/out' parameters. If you were to use a raw pointer instead, your function couldn't know what ownership relationship the caller had to the input value, and hence, couldn't know whether to Release it or not before assigning in the new value. By declaring the parameter as an nsCOMPtr&, the relationship is explicit.

Summary

An nsCOMPtr is an owning reference. Whatever it points to has been AddRefed, counting the nsCOMPtr as one of its `owners'. An nsCOMPtr always calls Release before letting go, whether the nsCOMPtr is letting go so that it can point to a different object, or because the nsCOMPtr is going out of scope. Any time a new value is assigned into an nsCOMPtr, the nsCOMPtr automatically always Releases its old referent, if any, and (unless you tell it you already have) AddRefs the new.

You use an nsCOMPtr exactly as you would a raw XPCOM interface pointer in almost all cases. You won't have to explictly call AddRef or Release through it, nor will the compiler allow it. The only place you can't use an nsCOMPtr without change is where a raw XPCOM interface pointer is an `out' parameter. In this case, you wrap the nsCOMPtr with getter_AddRefs (see Comparison 4).

When assigning into an nsCOMPtr, you will usually just supply another pointer (either a raw XPCOM interface pointer or an nsCOMPtr), with no additional directives (see, e.g., the nsCOMPtr side of Comparison 1). As stated above, with no directives, the nsCOMPtr will Release its old referent, if any, and AddRef the new. This is appropriate when the thing you're assigning in hasn't yet been AddRefed to account for the new reference. This is typically the case when you are assigning in a pointer that you didn't call a function to get, e.g., one that was passed in as a parameter, or that you pulled out of a structure.

You can tell nsCOMPtr it doesn't need to AddRef the new value on assignment by wrapping the new value in dont_AddRef. Do this, for example, when you got the new value from a function which, like all good XPCOM getters, already called AddRef on your behalf.

You may not assign in a pointer to a different interface type; you must first query it to the right type (see, e.g., Comparison 6 and the surrounding discussion). nsCOMPtr never calls QueryInterface implicitly, i.e., you must call it yourself, or explictly ask nsCOMPtr to call it with do_QueryInterface. The do_QueryInterface directive allows you to do the query as part of the assignment. This better facilitates constructing an nsCOMPtr directly from the right value, rather than constructing it and assigning in the correct value later. Construction alone is more efficient than construction followed by assignment. Prefer construction over assignment whereever reasonable. Be careful not to apply do_QueryInterface to a function returning an AddRefed pointer (see this short section for an explanation)

For more details, continue on to the Reference Manual.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

词条统计

浏览:45 次

字数:34044

最后编辑:6年前

编辑次数:0 次

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文