DPA_Merge

This function modifies one DPA according to the items in another DPA.

Declaration

BOOL
DPA_Merge (
    HDPA hdpaDest,
    HDPA hdpaSrc,
    DWORD dwFlags,
    PFNDPACOMPARE pfnCompare,
    PFNDPAMERGE pfnMerge,
    LPARAM lParam);

The comparison callback function has the prototype:

typedef int (*PFNDPACOMPARE) (PVOID p1, PVOID p2, LPARAM lParam);

The merge callback function has the prototype:

typedef PVOID (*PFNDPAMERGE) (UINT uMsg, PVOID p1, PVOID p2, LPARAM lParam);

Parameters

The hdpaDest argument provides a handle to the (target) DPA that may be modified.

The hdpaSrc argument provides a handle to the (source) DPA that provides new items.

The dwFlags argument provides bit flags to direct the behaviour. Recognised values are:

DPAM_SORTED (0x01) both input lists are already sorted
DPAM_UNION (0x04) form a generalised union of the input lists
DPAM_INTERSECT (0x08) form a generalised intersection of the input lists

The pfnCompare argument provides the address of a comparison callback function that decides the relative order of any pair of items from either DPA.

The pfnMerge argument provides the address of a merge callback function that notifies of changes to the target DPA.

The lParam argument provides a caller-specific context to be passed on each invocation of either callback function.

Return Value

The function returns TRUE for success, else FALSE.

Behaviour

The function fails if either DPA handle is NULL or if the address given for either callback function is NULL.

The function adds or removes items from the target list according to what items are found in the source list. Decisions depend on the relative order of items from each list, as defined by the combination of comparison function and context. If DPAM_SORTED is given in the dwFlags, the function assumes that both lists are already sorted in this order. Otherwise, the function sorts both lists (and merely assumes that both sort operations succeed).

The following are the rules for modifying the target list, as presently coded (see below for interpretation and comment):

  1. for each source item that has no matching target item but is higher in order than at least one target item, if DPAM_UNION is given among the dwFlags, then the source item (or a new item generated from it) is inserted into the (output) target;
  2. for each match of a source item and target item, one or the other (or a new item generated from them) takes the place of the target item in the (output) target, and neither the source item nor the target item can match any more items;
  3. for each target item that has no matching source item but is higher in order than at least one source item, if DPAM_INTERSECT is given among the dwFlags, then the target item is deleted from the target;
  4. for each source item that is lower in order than every (input) target item, if DPAM_UNION is given among the dwFlags,, then the source item (or a new item generated from it) is inserted into the (output) target.

Rules 1 to 3 are coded in one loop that works through the source and target from highest to lowest. Rule 4 mops up any source items that are left over from that loop. There is no rule 5 to mop up any target items that are left over.

Comparison Callback Function

Each time the DPA_Merge function wants to know the relative order of two items, it calls the comparison function. If DPAM_SORTED was not given among the dwFlags, the comparison function will first be called repeatedly to determine the relative order of items in the source list, and then to determine the relative order of items in the target list. Whatever the dwFlags, the comparison function may be called repeatedly to determine the relative order of a target item and a source item.

The comparison function is to return zero to indicate that the item at p1 matches the item at p2. A negative or positive result from the comparison function means respectively that the item at p1 is ordered before (lower than) or after (higher than) the item at p2.

Merge Callback Function

Each time the DPA_Merge function modifies the target list, it calls the merge function. The type of modification is described by the uMsg argument:

DPAMM_MERGE (1) found matching items;
choose what to insert in target
DPAMM_DELETE (2) removed unmatched item from target;
notification only, no response
DPAMM_INSERT (3) found unmatched item in source;
choose what to insert in target

For the DPAMM_MERGE modification, p1 points to an item in the target list and p2 to a matching item in the source list. The merge function returns the address of an item that is to take the place of the given target item. It may actually be the given target item, but obvious alternatives are the given source item (or a copy) or a new item generated from the two given items. If the merge function returns NULL, merging is aborted and DPA_Merge fails (leaving the target list in what is perhaps best regarded as an undefined state).

DPAMM_DELETE notifies that the item at p1 has been removed from the target list. This case arises because the given target item had no match in the source list and DPAM_INTERSECT was specified among the dwFlags. The p2 argument is NULL. The return value is ignored.

For the DPAMM_INSERT modification, the source item at p1 has no match in the target list and DPAM_UNION was specified among the dwFlags. The merge function returns the address of an item that is to be inserted into the target list. It may actually be the given source item, but an obvious alternative is an item generated from it (e.g., a copy). The p2 argument is NULL. If the merge function returns NULL, merging is aborted and DPA_Merge fails (leaving the target list in what is perhaps best regarded as an undefined state).

Interpretation and Coding Error

When both DPAM_UNION and DPAM_INTERSECT are clear, the number of items in the target does not change, but items that had a match in the source may have been replaced. Indeed, this function’s original (and perhaps still its best) use is when the DPAMM_MERGE case of the merge callback returns its p2 argument. The effect is then a simple merge, in which target items that have a match in the source are replaced by those matches.

That DPAM_UNION without DPAM_INTERSECT forms the target list as a generalised union of the two input lists is plain enough. The target ends with one item for every item that was in either list but not in both, and one item for every matching pair.

If DPAM_INTERSECT without DPAM_UNION is meant to produce a generalised intersection of the two input lists, i.e., such that the target ends with one item for every matching pair, then rule 3 ought apply to all target items that have no matching source item. Without a rule 5 to deal with target items that are lower in order than all source items, the intersection produced by this function is not reliable. A demonstration of this ridiculously long-standing bug is presented separately.

Though the behaviour from setting both DPAM_INTERSECT and DPAM_UNION is well-defined, in the sense of being reliably predictable from a description of the algorithm, useful interpretation eludes me and I incline to think that the combination has never been intended despite the handling of dwFlags as bits.

Variations

Historically, the defence against invalid arguments is stricter than described above. In versions before 6.10, the function fails if either DPA handle is implausible (meaning specifically that each must be an address at which the bytes of a DPA structure are writable) or if the address given for either callback function is not valid for reading at least one byte.

Rule 4 dates from 5.80. In earlier versions, the DPAM_UNION case misses source items that are lower in order than all target items. It is at least curious that this got fixed without noticing the corresponding incompleteness to the DPAM_INTERSECT case.

Availability

The DPA_Merge function is exported from COMCTL32.DLL as ordinal 11 in version 4.71 and higher. The implementation for version 6.10 and higher is built into a statically linked library and thence is also exported from the Internet Explorer module IERTUTIL.DLL as ordinal 87 in version 7.0 and higher.

Though this function dates from as long ago as 1997, it was still not documented by Microsoft in the MSDN Library at least as late as the CD edition dated January 2004.

This function has, however, got documented since then (in 2006, or perhaps some time during 2005), albeit as requiring “version 5.0 or later”. This article now uses Microsoft’s nomenclature.