1 /***********************************************************************/
2 /* Open Visualization Data Explorer */
3 /* (C) Copyright IBM Corp. 1989,1999 */
4 /* ALL RIGHTS RESERVED */
5 /* This code licensed under the */
6 /* "IBM PUBLIC LICENSE - Open Visualization Data Explorer" */
7 /***********************************************************************/
8
9 #include <dxconfig.h>
10
11
12 #include <stdio.h>
13 #include <string.h>
14
15 #include <dx/dx.h>
16
17 #include "config.h"
18 #include "crc.h"
19 #include "d.h"
20 #include "graph.h"
21 #include "utils.h"
22 #include "sysvars.h"
23 #include "distp.h"
24
25 #define DICT_LOCK(l,d) if (l) DXlock (&(d)->lock, exJID)
26 #define DICT_UNLOCK(l,d) if (l) DXunlock (&(d)->lock, exJID)
27
28 typedef struct _EXDictElement *EXDictElement;
29 typedef struct _EXDictElementSorted *EXDictElementSorted;
30
31 typedef struct _EXDictElement
32 {
33 EXDictElement next; /* the element chain */
34 EXDictElement prev;
35 char *key; /* symbolic key */
36 Pointer hash; /* hash of key */
37 EXObj obj; /* data with the key */
38 } _EXDictElement;
39
40 typedef struct _EXDictElementSorted
41 {
42 struct _EXDictElement de;
43 EXDictElementSorted newer; /* the sort chain */
44 EXDictElementSorted older;
45 } _EXDictElementSorted;
46
47 typedef struct _EXDictHead
48 {
49 EXDictElement e; /* element pointer */
50 } _EXDictHead, *EXDictHead;
51
52 typedef struct _EXDictionary
53 {
54 int items; /* number of in table */
55 int iterIndex; /* current iterator index */
56 EXDictElement iterItem; /* current iterator item */
57 int mask; /* bit mask for size */
58 int locking;/* is locking required */
59 int size; /* size of table */
60 int local; /* is this a local dictionary */
61 EXDictElement free; /* element free list */
62 lock_type lock; /* lock if necessary */
63 int sorted; /* is a sort list kept? */
64 EXDictElementSorted newest; /* sort list head */
65 EXDictElementSorted oldest; /* sort list tail */
66 int reverse;
67 _EXDictHead h[1]; /* the elements in the table */
68 } _EXDictionary;
69
70 static void _dxf_ExRemoveFromSortList(EXDictionary d, EXDictElement de);
71 static void _dxf_ExMakeNewest(EXDictionary d, EXDictElement de);
72
__ExDictionaryCreate(int size,int local,int locking,int sorted)73 static EXDictionary __ExDictionaryCreate (int size, int local,
74 int locking, int sorted)
75 {
76 int n;
77 EXDictionary d;
78 Error l;
79
80 #ifdef DICT_TIMING
81 DXMarkTimeLocal("D create");
82 #endif
83 /*
84 * Adjust the inputs so that they match our notion of things. Round
85 * up to a power of 2 so that the masking will work. If a dictionary
86 * is in local memory then it doesn't need locking.
87 */
88
89 size = (size <= 0) ? 1 : _dxf_ExNextPowerOf2 (size);
90 locking = local ? FALSE : locking;
91
92 /*
93 * OK, now let's construct the dictionary and fill in the blanks.
94 */
95
96 n = sizeof (_EXDictionary);
97 n += sizeof (_EXDictHead) * size;
98
99 d = (EXDictionary) (local ? DXAllocateLocal (n) : DXAllocate (n));
100 if (! d)
101 _dxf_ExDie ("_dxf_ExDictionaryCreate: DXAllocate failed");
102
103 ExZero (d, n);
104
105 d->mask = size - 1;
106 d->locking = locking;
107 d->size = size;
108 d->local = local;
109
110 d->sorted = sorted;
111 d->newest = NULL;
112 d->oldest = NULL;
113
114 if (locking)
115 {
116 l = DXcreate_lock (&d->lock, "Dictionary");
117 if (l != OK)
118 _dxf_ExDie ("_dxf_ExDictionaryCreate: DXcreate_lock failed");
119 }
120
121 #ifdef DICT_TIMING
122 DXMarkTimeLocal("-D create");
123 #endif
124 return (d);
125 }
126
127 /*
128 * Constructs a new dictionary based upon the following characteristics:
129 *
130 * size Number of buckets to create
131 * local Whether dictionary should reside in local memory
132 * locking Whether dictionary requires locking
133 */
_dxf_ExDictionaryCreate(int size,int local,int locking)134 EXDictionary _dxf_ExDictionaryCreate (int size, int local, int locking)
135 {
136 return __ExDictionaryCreate(size, local, locking, 0);
137 }
138
_dxf_ExDictionaryCreateSorted(int size,int local,int locking)139 EXDictionary _dxf_ExDictionaryCreateSorted (int size, int local, int locking)
140 {
141 return __ExDictionaryCreate(size, local, locking, 1);
142 }
143
144
145 /*
146 * Destroys a dictionary. We need not bother with locking here since we
147 * assume that a dictionary will only be destroyed when no-one else is
148 * going to use it.
149 */
150
_dxf_ExDictionaryDestroy(EXDictionary d)151 Error _dxf_ExDictionaryDestroy (EXDictionary d)
152 {
153 int i;
154 EXDictHead h;
155 EXDictElement e, ne;
156
157 #ifdef DICT_TIMING
158 DXMarkTimeLocal("D destroy");
159 #endif
160 if (! d)
161 return (OK);
162
163 /*
164 * Get rid of the free element list.
165 */
166
167 for (e = d->free; e; e = ne)
168 {
169 ne = e->next;
170 DXFree ((Pointer) e);
171 }
172
173 /*
174 * Destroy all of the records in the table.
175 */
176
177 for (i = d->size, h = d->h; i--; h++)
178 {
179 for (e = h->e; e; e = ne)
180 {
181 ne = e->next;
182 ExDelete (e->obj); /* delete its value */
183 DXFree ((Pointer) e->key); /* destroy the key */
184 DXFree ((Pointer) e); /* get rid of the block */
185 }
186 }
187
188 if (d->locking)
189 DXdestroy_lock (&d->lock);
190
191 DXFree ((Pointer) d);
192
193 #ifdef DICT_TIMING
194 DXMarkTimeLocal("-D destroy");
195 #endif
196 return (OK);
197 }
198
199 /* Releases all unneeded memory, returned TRUE if any memory is freed */
_dxf_ExDictionaryCompact(EXDictionary d)200 int _dxf_ExDictionaryCompact (EXDictionary d)
201 {
202 EXDictElement e, ne;
203 int result;
204
205 #ifdef DICT_TIMING
206 DXMarkTimeLocal("D compact");
207 #endif
208 if (! d)
209 return (FALSE);
210
211 DICT_LOCK (d->locking, d);
212
213 result = d->free != NULL;
214 /*
215 * Get rid of the free element list.
216 */
217 for (e = d->free; e; e = ne)
218 {
219 ne = e->next;
220 DXFree ((Pointer) e);
221 }
222 d->free = NULL;
223
224 DICT_UNLOCK (d->locking, d);
225
226 #ifdef DICT_TIMING
227 DXMarkTimeLocal("-D compact");
228 #endif
229 return (result);
230 }
231
232 /*
233 * Empties a dictionary. Returns true if any memory has been freed.
234 */
235
_dxf_ExDictionaryPurge(EXDictionary d)236 int _dxf_ExDictionaryPurge (EXDictionary d)
237 {
238 int i;
239 EXDictHead h;
240 EXDictElement e, ne;
241 int result;
242
243 #ifdef DICT_TIMING
244 DXMarkTimeLocal("D purge");
245 #endif
246 if (! d)
247 return (FALSE);
248
249 if(!_dxd_exRemoteSlave)
250 _dxf_ExDistributeMsg(DM_FLUSHDICT, (Pointer)d, 0, TOSLAVES);
251
252 DICT_LOCK (d->locking, d);
253
254 /*
255 * Destroy all of the records in the table.
256 */
257 for (result = FALSE, i = d->size, h = d->h; i--; h++)
258 {
259 for (e = h->e, h->e = NULL; e; e = ne)
260 {
261 ne = e->next;
262
263 /* add node to free list */
264 e->next = d->free;
265 d->free = e;
266
267 if (d->sorted)
268 _dxf_ExRemoveFromSortList(d, e);
269
270 ExDelete (e->obj);
271 DXFree ((Pointer) e->key);
272
273 result = TRUE;
274 }
275 }
276
277 DICT_UNLOCK (d->locking, d);
278
279 #ifdef DICT_TIMING
280 DXMarkTimeLocal("-D purge");
281 #endif
282 return (result);
283 }
284
285
286 /*
287 * Searches for the dictionary element associated with the given key.
288 * If it is found then it is returned, otherwise NULL is returned.
289 *
290 * NOTE: If the dictionary requires locking then the locking should be
291 * performed outside of this routine.
292 */
293
294 static EXDictElement
ExDictionarySearchE(EXDictionary d,int b,char * key,Pointer hash)295 ExDictionarySearchE (EXDictionary d, int b, char *key, Pointer hash)
296 {
297 EXDictElement e;
298
299 for (e = d->h[b].e; e; e = e->next)
300 if ((hash == e->hash) && ! strcmp (key, e->key))
301 {
302 if (d->sorted)
303 _dxf_ExMakeNewest(d, e);
304 return (e);
305 }
306
307 return (NULL);
308 }
309
310
311 /*
312 * Searches the given dictionary for an object associated with the given key.
313 * If an entry is found then its value is returned, otherwise NULL is returned.
314 */
315
_dxf_ExDictionarySearch(EXDictionary d,char * key)316 EXObj _dxf_ExDictionarySearch (EXDictionary d, char *key)
317 {
318 Pointer h; /* hash value */
319 int b; /* bucket id */
320 int l; /* locking? */
321 EXDictElement e; /* the found element */
322 EXObj o; /* the found object */
323
324 #ifdef DICT_TIMING
325 DXMarkTimeLocal("D search");
326 #endif
327 if (d->items == 0)
328 return (NULL);
329
330 h = (Pointer) _dxf_ExCRCString (EX_INITIAL_CRC, key);
331 b = (long) h & d->mask;
332 l = d->locking;
333
334 if (! d->h[b].e) /* a quick out */
335 return (NULL);
336
337 DICT_LOCK (l, d);
338 e = ExDictionarySearchE (d, b, key, h);
339 if (e)
340 {
341 o = e->obj;
342 ExReference (o);
343 }
344 else
345 o = NULL;
346
347 DICT_UNLOCK (l, d);
348
349 #ifdef DICT_TIMING
350 DXMarkTimeLocal("-D search");
351 #endif
352
353 return (o);
354 }
355
356
357 static EXDictElement
_dxf_ExAllocateNewDictionaryElement(EXDictionary d)358 _dxf_ExAllocateNewDictionaryElement(EXDictionary d)
359 {
360 int size;
361
362 if (d->sorted)
363 size = sizeof(struct _EXDictElementSorted);
364 else
365 size = sizeof(struct _EXDictElement);
366
367 return d->local ?
368 (EXDictElement)DXAllocateLocalZero(size) :
369 (EXDictElement)DXAllocateZero(size);
370 }
371
372 /*
373 * Inserts a new element into a dictionary.
374 */
375
_dxf_ExDictionaryInsert(EXDictionary d,char * key,EXObj obj)376 Error _dxf_ExDictionaryInsert (EXDictionary d, char *key, EXObj obj)
377 {
378 Pointer h;
379 int b;
380 int l;
381 Pointer k;
382 EXDictElement e, ne;
383 EXObj o;
384
385 #ifdef DICT_TIMING
386 DXMarkTimeLocal("D insert");
387 #endif
388
389 ExReference (obj);
390
391 h = (Pointer) _dxf_ExCRCString (EX_INITIAL_CRC, key);
392 b = (long) h & d->mask;
393 l = d->locking;
394
395 DICT_LOCK (l, d);
396
397 e = d->h[b].e ? ExDictionarySearchE (d, b, key, h) : NULL;
398
399 /*
400 * If we find something with the given key then just replace the object.
401 * If not then we need to construct a new dictionary element, fill out
402 * its fields and link it into the structure. Just to be sure, however,
403 * we redo the search to make sure that no-one snuck something with the
404 * same key in on us.
405 */
406 if (e)
407 {
408 o = e->obj; /* remember for delete */
409 e->obj = obj; /* replace object */
410 DICT_UNLOCK (l, d);
411 ExDelete (o);
412
413 if (_dxf_ExIsDictionarySorted(d))
414 _dxf_ExMakeNewest(d, e);
415
416 }
417 else
418 {
419 /*
420 * Get an element block. If we can find one from the free list the
421 * make use of it, otherwise go ahead and allocate one. Once we
422 * have the block then go ahead and fill in its fields.
423 */
424
425 if (d->free)
426 {
427 ne = d->free;
428 d->free = ne->next;
429 DICT_UNLOCK (l, d);
430 }
431 else
432 {
433 DICT_UNLOCK (l, d);
434
435 ne = _dxf_ExAllocateNewDictionaryElement(d);
436 if (! ne)
437 _dxf_ExDie ("_dxf_ExDictionaryInsert: can't DXAllocate");
438 }
439
440 ne->prev = NULL;
441 ne->key = d->local ? _dxf_ExCopyStringLocal (key) : _dxf_ExCopyString (key);
442 ne->obj = obj;
443 ne->hash = h;
444
445 /*
446 * Redo the search.
447 */
448
449 if (l)
450 {
451 DICT_LOCK (l, d);
452 e = d->h[b].e ? ExDictionarySearchE (d, b, key, h) : NULL;
453
454 /*
455 * If someone snuck one in on us then just replace the data and
456 * get rid of the one we created.
457 */
458
459 if (e)
460 {
461 o = e->obj; /* remember for delete */
462 e->obj = obj; /* replace object */
463 k = ne->key; /* remember for delete */
464 ne->next = d->free; /* free element block */
465 d->free = ne;
466
467 if (_dxf_ExIsDictionarySorted(d))
468 _dxf_ExMakeNewest(d, e);
469
470 DICT_UNLOCK (l, d);
471
472 ExDelete (o); /* delete old object */
473 DXFree ((Pointer) k); /* delete new key */
474 }
475 else
476 {
477 if (d->h[b].e) /* fill in back pointer */
478 d->h[b].e->prev = ne;
479 ne->next = d->h[b].e; /* link in new element */
480 d->h[b].e = ne;
481 d->items++; /* bump item count */
482
483 if (_dxf_ExIsDictionarySorted(d))
484 _dxf_ExMakeNewest(d, ne);
485
486 DICT_UNLOCK (l, d);
487 }
488 }
489 else
490 {
491 if (d->h[b].e) /* fill in back pointer */
492 d->h[b].e->prev = ne;
493 ne->next = d->h[b].e; /* link in new element */
494 d->h[b].e = ne;
495 d->items++; /* bump item count */
496 }
497 }
498
499 #ifdef DICT_TIMING
500 DXMarkTimeLocal("-D insert");
501 #endif
502 return (OK);
503 }
504
505
506 /*
507 * Deletes an element from a dictionary
508 */
509
_dxf_ExDictionaryDelete(EXDictionary d,Pointer key)510 Error _dxf_ExDictionaryDelete (EXDictionary d, Pointer key)
511 {
512 Pointer h;
513 int b;
514 int l;
515 Pointer k;
516 EXDictElement e;
517 EXObj o;
518
519 #ifdef DICT_TIMING
520 DXMarkTimeLocal("D delete");
521 #endif
522
523 if (d->items == 0)
524 return (ERROR);
525
526 h = (Pointer) _dxf_ExCRCString (EX_INITIAL_CRC, key);
527 b = (long) h & d->mask;
528 l = d->locking;
529
530 DICT_LOCK (l, d);
531 e = ExDictionarySearchE (d, b, key, h);
532
533 if (! e) /* a quick out */
534 {
535 DICT_UNLOCK (l, d);
536 return (ERROR);
537 }
538
539 o = e->obj; /* remember for delete */
540 k = e->key; /* remember for delete */
541
542 if (e->next) /* unlink */
543 e->next->prev = e->prev;
544
545 if (e->prev)
546 e->prev->next = e->next;
547 else
548 d->h[b].e = e->next;
549
550 e->next = d->free; /* add to free list */
551 d->free = e;
552 d->items--;
553
554 if (d->sorted)
555 _dxf_ExRemoveFromSortList(d, e);
556
557 DICT_UNLOCK (l, d);
558
559 ExDelete (o);
560 DXFree ((Pointer) k);
561
562 #ifdef DICT_TIMING
563 DXMarkTimeLocal("-D delete");
564 #endif
565
566 return (OK);
567 }
568
569
570 /*
571 * Deletes an element from a dictionary. This ignores locking because
572 * it is intended to be called while iterating through the dictionary
573 * using the ExDictionary*Iterate functions.
574 */
575
_dxf_ExDictionaryDeleteNoLock(EXDictionary d,Pointer key)576 Error _dxf_ExDictionaryDeleteNoLock (EXDictionary d, Pointer key)
577 {
578 Pointer h;
579 int b;
580 Pointer k;
581 EXDictElement e;
582 EXObj o;
583
584 #ifdef DICT_TIMING
585 DXMarkTimeLocal("D del NL");
586 #endif
587
588 if (d->items == 0)
589 return (ERROR);
590
591 h = (Pointer) _dxf_ExCRCString (EX_INITIAL_CRC, key);
592 b = (long) h & d->mask;
593
594 e = ExDictionarySearchE (d, b, key, h);
595
596 if (! e) /* a quick out */
597 {
598 return (ERROR);
599 }
600
601 o = e->obj; /* remember for delete */
602 k = e->key; /* remember for delete */
603
604 if (e->next) /* unlink */
605 e->next->prev = e->prev;
606
607 if (e->prev)
608 e->prev->next = e->next;
609 else
610 d->h[b].e = e->next;
611
612 if (d->iterItem == e)
613 d->iterItem = e->next;
614
615 e->next = d->free; /* add to free list */
616 d->free = e;
617 d->items--;
618
619 if (d->sorted)
620 _dxf_ExRemoveFromSortList(d, e);
621
622 ExDelete (o);
623 DXFree ((Pointer) k);
624
625 #ifdef DICT_TIMING
626 DXMarkTimeLocal("-D del NL");
627 #endif
628
629 return (OK);
630 }
631
632 /* A set of routines to iterate the dictionary */
_dxf_ExDictionaryBeginIterate(EXDictionary d)633 Error _dxf_ExDictionaryBeginIterate (EXDictionary d)
634 {
635 if (!d)
636 return (OK);
637
638 DICT_LOCK (d->locking, d);
639
640 d->iterIndex = -1;
641 d->iterItem = NULL;
642
643 return (OK);
644 }
645
_dxf_ExDictionaryIterate(EXDictionary d,char ** key)646 EXObj _dxf_ExDictionaryIterate (EXDictionary d, char **key)
647 {
648 EXObj result;
649 EXDictElement e = d->iterItem;
650 int i = d->iterIndex;
651
652 while (e == NULL)
653 {
654 ++i;
655 if (i == d->size)
656 {
657 *key = NULL;
658 return NULL;
659 }
660 e = d->h[i].e;
661 }
662
663 result = e->obj;
664 *key = e->key;
665
666 d->iterItem = e->next;
667 d->iterIndex = i;
668
669 return (result);
670 }
671
672
_dxf_ExDictionaryIterateMany(EXDictionary d,char ** key,EXObj * obj,int n)673 int _dxf_ExDictionaryIterateMany (EXDictionary d, char **key, EXObj *obj, int n)
674 {
675 EXDictElement e = d->iterItem;
676 int i = d->iterIndex;
677 int cnt = 0;
678
679 if (i >= d->size)
680 return (0);
681
682 for (cnt = 0; cnt < n; )
683 {
684 while (e == NULL)
685 {
686 ++i;
687 if (i == d->size)
688 {
689 d->iterItem = NULL;
690 d->iterIndex = i;
691 return (cnt);
692 }
693 e = d->h[i].e;
694 }
695
696 *key++ = e->key;
697 *obj++ = e->obj;
698 cnt++;
699 e = e->next;
700 }
701
702 d->iterItem = e;
703 d->iterIndex = i;
704 return (cnt);
705 }
706
_dxf_ExDictionaryEndIterate(EXDictionary d)707 Error _dxf_ExDictionaryEndIterate (EXDictionary d)
708 {
709 DICT_UNLOCK (d->locking, d);
710 return (OK);
711 }
712
_dxf_ExDictPrint(EXDictionary d)713 Error _dxf_ExDictPrint (EXDictionary d)
714 {
715 int l, i, colision;
716 EXDictElement e;
717
718
719 if (!d)
720 return (OK);
721 l = d->locking;
722
723 DICT_LOCK (l, d);
724
725 for (i = 0, e = d->free; e; ++i, e = e->next)
726 ;
727
728 DXMessage ("Dictionary 0x%08x, size %d, %slocking, freelist size %d",
729 d, d->size, l? "": "non-", i);
730 DXMessage("%25s | %4s | %2s | %10s", "name", "bin", "cl", "object");
731 for (i = 0; i < d->size; ++i)
732 {
733 for (colision = 0, e = d->h[i].e; e != NULL; e = e->next, ++colision)
734 {
735 DXMessage ("%25s | %4d | %2d | 0x%08x", e->key, i, colision, e->obj);
736 }
737 }
738
739 DICT_UNLOCK (l, d);
740 return (OK);
741 }
742
743
744 void
_dxf_ExPrintSortList(EXDictionary d)745 _dxf_ExPrintSortList(EXDictionary d)
746 {
747 if (! d->sorted)
748 DXMessage("dictionary is not sorted");
749 else
750 {
751 EXDictElementSorted a = d->oldest, b = d->newest;
752
753
754 DXMessage("o->n n->o");
755 while (a || b)
756 {
757 DXMessage("0x%08lx 0x%08lx", a, b);
758 a = a->newer; b = b->older;
759 }
760 }
761 }
762
763 int
_dxf_ExIsDictionarySorted(EXDictionary d)764 _dxf_ExIsDictionarySorted(EXDictionary d)
765 {
766 return d->sorted;
767 }
768
769 static void
_dxf_ExRemoveFromSortList(EXDictionary d,EXDictElement de)770 _dxf_ExRemoveFromSortList(EXDictionary d, EXDictElement de)
771 {
772 if (d->sorted)
773 {
774 EXDictElementSorted sde = (EXDictElementSorted)de;
775
776 if (sde->older)
777 sde->older->newer = sde->newer;
778 if (sde->newer)
779 sde->newer->older = sde->older;
780
781 if (d->newest == sde)
782 d->newest = sde->older;
783
784 if (d->oldest == sde)
785 d->oldest = sde->newer;
786
787 sde->newer = sde->older = NULL;
788 }
789 }
790
791 static void
_dxf_ExMakeNewest(EXDictionary d,EXDictElement de)792 _dxf_ExMakeNewest(EXDictionary d, EXDictElement de)
793 {
794 if (d->sorted)
795 {
796 EXDictElementSorted sde = (EXDictElementSorted)de;
797
798 if (d->newest == sde)
799 return;
800
801 if (sde->newer || sde->older)
802 _dxf_ExRemoveFromSortList(d, (EXDictElement)sde);
803
804 if (NULL != (sde->older = d->newest))
805 sde->older->newer = sde;
806
807 sde->newer = NULL;
808
809 d->newest = sde;
810
811 if (d->oldest == NULL)
812 d->oldest = sde;
813 }
814 }
815
816
817 Error
_dxf_ExDictionaryBeginIterateSorted(EXDictionary d,int reverse)818 _dxf_ExDictionaryBeginIterateSorted(EXDictionary d, int reverse)
819 {
820 if (! d->sorted)
821 {
822 DXSetError(ERROR_INTERNAL,
823 "attempting to use _dxf_ExDictionaryBeginIterateSorted"
824 "on an unsorted dictionary");
825 return ERROR;
826 }
827
828 if (reverse)
829 {
830 d->reverse = 1;
831 d->iterItem = (EXDictElement)d->newest;
832 }
833 else
834 {
835 d->reverse = 0;
836 d->iterItem = (EXDictElement)d->oldest;
837 }
838
839 return OK;
840 }
841
842 EXObj
_dxf_ExDictionaryIterateSorted(EXDictionary d,char ** key)843 _dxf_ExDictionaryIterateSorted(EXDictionary d, char **key)
844 {
845 EXDictElementSorted e = (EXDictElementSorted)d->iterItem;
846
847 if (! d->sorted)
848 {
849 DXSetError(ERROR_INTERNAL,
850 "attempting to use _dxf_ExDictionaryIterateSorted"
851 "on an unsorted dictionary");
852 return ERROR;
853 }
854
855 if (e != NULL)
856 {
857 if (d->reverse)
858 d->iterItem = (EXDictElement)(e->older);
859 else
860 d->iterItem = (EXDictElement)(e->newer);
861
862 *key = e->de.key;
863 return e->de.obj;
864 }
865 else
866 {
867 *key = 0;
868 return NULL;
869 }
870 }
871
872 int
_dxf_ExDictionaryIterateSortedMany(EXDictionary d,char ** key,EXObj * obj,int n)873 _dxf_ExDictionaryIterateSortedMany (EXDictionary d, char **key, EXObj *obj, int n)
874 {
875 int nn;
876
877 for (nn = 0; nn < n; nn++)
878 if (NULL == (*obj++ = _dxf_ExDictionaryIterateSorted(d, key++)))
879 break;
880
881 return nn;
882 }
883
884