January 27th, 2026
0 reactions

A digression on the design and implementation of Safe­Array­Add­Ref and extending APIs in general

Last time, we learned about the difference between Safe­Array­Access­Data and Safe­Array­Add­Ref. I noted that Safe­Array­Add­Ref was a latecomer that was added to an existing API and that one of the design concerns was to minimize the impact upon existing code. When extending an existing API, a major concern is what the new feature means for people who were using the earlier version of the API.

One design principle for extending an API is “pay for play”: Programs can call the new API to get access to new features, but programs that choose not to do so are unaffected, and the old code continues to work as it did before. It is acceptable to add additional requirements for people who want to use the new feature, such as, “If you intend to reverse the polarity of a widget, you must pass the Allow­Polarity­Reversal flag when creating the widget.” Pre-existing code won’t pass that flag, but they also won’t be trying to reverse the polarity.

For SAFEARRAY, the story is a little trickier because the code that created the SAFEARRAY is not the code that is calling Safe­Array­Add­Ref. Therefore, you cannot impose new requirements on the caller of Safe­Array­Create because you don’t control that code. The whole point of Safe­Array­Add­Ref is to allow a function to defend itself from malicious behavior in the code that created the SAFEARRAY.

Another design issue is that when you add a new feature to an API, you want to make it easy for people who are already using that API to use the feature. If somebody asks, “How do I solve this problem?”, “Make these major changes to the underlying architecture of your program” will not be received well.

In the case of SAFEARRAY, the problem is compounded by the fact that the SAFEARRAY structure is itself public, so we have to assume that people are accessing the members in it without going through the wrapper functions like Safe­Array­Get­Dim or Safe­Array­Get­Elemsize. There is nowhere to put the reference count without breaking those people.

So how do you record a reference count when there is nowhere to record a reference count?

You have to maintain the reference counts externally.

The system maintains two process-wide tables table to track reference counts, one for tracking data block reference counts and another for tracking array descriptor reference counts. The table is indexed by the pointer to the data block or array descriptor, and the value is the reference count, if not zero. If a reference count drops to zero, then it is erased from the table. That way, the table contains reference counts only for actively-referenced items.

Okay, that ends our digression. Next time, we’ll try to answer a customer’s question about Safe­Array­Add­Ref.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

1 comment

Sort by :
  • Koro Unhallowed 1 hour ago

    Couldn’t that information be stored at a negative offset of the pointer, the same way the BSTR length is?