OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright (C) 2004, 2006 Apple Computer, Inc. All rights reserved. | 2 * Copyright (C) 2004, 2006 Apple Computer, Inc. All rights reserved. |
3 * Copyright (C) 2007-2009 Google, Inc. All rights reserved. | 3 * Copyright (C) 2007-2009 Google, Inc. All rights reserved. |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions | 6 * modification, are permitted provided that the following conditions |
7 * are met: | 7 * are met: |
8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
11 * notice, this list of conditions and the following disclaimer in the | 11 * notice, this list of conditions and the following disclaimer in the |
12 * documentation and/or other materials provided with the distribution. | 12 * documentation and/or other materials provided with the distribution. |
13 * | 13 * |
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY | 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 */ | 25 */ |
26 | 26 |
27 #include "config.h" | 27 #include "config.h" |
28 | 28 |
29 #include <map> | |
30 #include <set> | |
31 #include <string> | |
32 #include <v8.h> | |
33 | |
34 #include "bindings/npruntime.h" | |
35 #include "NPV8Object.h" | 29 #include "NPV8Object.h" |
36 #include "npruntime_priv.h" | 30 #include "npruntime_priv.h" |
37 #include "V8NPObject.h" | 31 #include "V8NPObject.h" |
38 | 32 |
33 #include <wtf/HashMap.h> | |
34 #include <wtf/HashSet.h> | |
39 #include <wtf/Assertions.h> | 35 #include <wtf/Assertions.h> |
40 | 36 |
41 using namespace v8; | |
42 | |
43 | |
44 // FIXME: Consider removing locks if we're singlethreaded already. | 37 // FIXME: Consider removing locks if we're singlethreaded already. |
45 // The static initializer here should work okay, but we want to avoid | 38 // The static initializer here should work okay, but we want to avoid |
46 // static initialization in general. | 39 // static initialization in general. |
47 // | 40 // |
48 // Commenting out the locks to avoid dependencies on chrome for now. | 41 // Commenting out the locks to avoid dependencies on chrome for now. |
49 // Need a platform abstraction which we can use. | 42 // Need a platform abstraction which we can use. |
50 // static Lock StringIdentifierMapLock; | 43 // static Lock StringIdentifierMapLock; |
51 | 44 |
52 namespace { | 45 namespace { |
53 | 46 |
54 // We use StringKey here as the key-type to avoid a string copy to | 47 // We use StringKey here as the key-type to avoid a string copy to |
55 // construct the map key and for faster comparisons than strcmp. | 48 // construct the map key and for faster comparisons than strcmp. |
56 struct StringKey { | 49 class StringKey { |
57 StringKey(const char* str) : string(str), length(strlen(str)) {} | 50 public: |
58 const char* string; | 51 StringKey(const char* str) : _string(str), _length(strlen(str)) {} |
Mike Belshe
2009/04/30 21:37:52
style wise, these should be m_string and m_length?
| |
59 const size_t length; | 52 StringKey() : _string(0), _length(0) {} |
53 StringKey(WTF::HashTableDeletedValueType) | |
54 : _string(hashTableDeletedValue()), _length(0) { } | |
55 | |
56 StringKey& operator=(const StringKey& other) { | |
57 this->_string = other._string; | |
58 this->_length = other._length; | |
59 return *this; | |
60 } | |
61 | |
62 bool isHashTableDeletedValue() const { | |
63 return _string == hashTableDeletedValue(); | |
64 } | |
65 | |
66 const char* _string; | |
67 size_t _length; | |
68 private: | |
69 const char* hashTableDeletedValue() const { | |
70 return reinterpret_cast<const char*>(-1); | |
71 } | |
60 }; | 72 }; |
61 | 73 |
62 inline bool operator<(const StringKey& x, const StringKey& y) { | 74 inline bool operator==(const StringKey& x, const StringKey& y) { |
63 // Shorter strings are less than longer strings, memcmp breaks ties. | 75 if (x._length != y._length) { |
64 if (x.length < y.length) | 76 return false; |
77 } else if (x._string == y._string) { | |
65 return true; | 78 return true; |
66 else if (x.length > y.length) | 79 } else { |
67 return false; | 80 ASSERT(!x.isHashTableDeletedValue() && !y.isHashTableDeletedValue()); |
68 else | 81 return memcmp(x._string, y._string, y._length) == 0; |
69 return memcmp(x.string, y.string, y.length) < 0; | 82 } |
70 } | 83 } |
71 | 84 |
85 // Implement WTF::DefaultHash<StringKey>::Hash interface. | |
86 struct StringKeyHash { | |
87 static unsigned hash(const StringKey& key) { | |
88 // Use the same string hash function as in V8. | |
Mike Belshe
2009/04/30 21:37:52
This comment might make the reader think this need
| |
89 unsigned hash = 0; | |
90 size_t len = key._length; | |
91 const char* str = key._string; | |
92 for (size_t i = 0; i < len; i++) { | |
93 char c = str[i]; | |
94 hash += c; | |
95 hash += (hash << 10); | |
96 hash ^= (hash >> 6); | |
97 } | |
98 hash += (hash << 3); | |
99 hash ^= (hash >> 11); | |
100 hash += (hash << 15); | |
101 if (hash == 0) { | |
102 hash = 27; | |
103 } | |
104 return hash; | |
105 } | |
106 | |
107 static bool equal(const StringKey& x, const StringKey& y) { | |
108 return x == y; | |
109 } | |
110 | |
111 static const bool safeToCompareToEmptyOrDeleted = true; | |
112 }; | |
113 | |
72 } // namespace | 114 } // namespace |
73 | 115 |
74 typedef std::map<const StringKey, PrivateIdentifier*> StringIdentifierMap; | 116 // Implement HashTraits<StringKey> |
117 struct StringKeyHashTraits : WTF::GenericHashTraits<StringKey> { | |
118 static void constructDeletedValue(StringKey& slot) { | |
119 new (&slot) StringKey(WTF::HashTableDeletedValue); | |
120 } | |
121 static bool isDeletedValue(const StringKey& value) { | |
122 return value.isHashTableDeletedValue(); | |
123 } | |
124 }; | |
125 | |
126 typedef WTF::HashMap<StringKey, PrivateIdentifier*, \ | |
127 StringKeyHash, StringKeyHashTraits> StringIdentifierMap; | |
75 | 128 |
76 static StringIdentifierMap* getStringIdentifierMap() { | 129 static StringIdentifierMap* getStringIdentifierMap() { |
77 static StringIdentifierMap* stringIdentifierMap = 0; | 130 static StringIdentifierMap* stringIdentifierMap = 0; |
78 if (!stringIdentifierMap) | 131 if (!stringIdentifierMap) |
79 stringIdentifierMap = new StringIdentifierMap(); | 132 stringIdentifierMap = new StringIdentifierMap(); |
80 return stringIdentifierMap; | 133 return stringIdentifierMap; |
81 } | 134 } |
82 | 135 |
83 // FIXME: Consider removing locks if we're singlethreaded already. | 136 // FIXME: Consider removing locks if we're singlethreaded already. |
84 // static Lock IntIdentifierMapLock; | 137 // static Lock IntIdentifierMapLock; |
85 | 138 |
86 typedef std::map<int, PrivateIdentifier*> IntIdentifierMap; | 139 typedef WTF::HashMap<int, PrivateIdentifier*> IntIdentifierMap; |
87 | 140 |
88 static IntIdentifierMap* getIntIdentifierMap() { | 141 static IntIdentifierMap* getIntIdentifierMap() { |
89 static IntIdentifierMap* intIdentifierMap = 0; | 142 static IntIdentifierMap* intIdentifierMap = 0; |
90 if (!intIdentifierMap) | 143 if (!intIdentifierMap) |
91 intIdentifierMap = new IntIdentifierMap(); | 144 intIdentifierMap = new IntIdentifierMap(); |
92 return intIdentifierMap; | 145 return intIdentifierMap; |
93 } | 146 } |
94 | 147 |
95 extern "C" { | 148 extern "C" { |
96 | 149 |
97 NPIdentifier NPN_GetStringIdentifier(const NPUTF8* name) { | 150 NPIdentifier NPN_GetStringIdentifier(const NPUTF8* name) { |
98 ASSERT(name); | 151 ASSERT(name); |
99 | 152 |
100 if (name) { | 153 if (name) { |
101 // AutoLock safeLock(StringIdentifierMapLock); | 154 // AutoLock safeLock(StringIdentifierMapLock); |
102 | 155 |
103 StringKey key(name); | 156 StringKey key(name); |
104 StringIdentifierMap* identMap = getStringIdentifierMap(); | 157 StringIdentifierMap* identMap = getStringIdentifierMap(); |
105 StringIdentifierMap::iterator iter = identMap->find(key); | 158 StringIdentifierMap::iterator iter = identMap->find(key); |
106 if (iter != identMap->end()) | 159 if (iter != identMap->end()) |
107 return static_cast<NPIdentifier>(iter->second); | 160 return static_cast<NPIdentifier>(iter->second); |
108 | 161 |
109 size_t nameLen = key.length; | 162 size_t nameLen = key._length; |
110 | 163 |
111 // We never release identifiers, so this dictionary will grow. | 164 // We never release identifiers, so this dictionary will grow. |
112 PrivateIdentifier* identifier = static_cast<PrivateIdentifier*>( | 165 PrivateIdentifier* identifier = static_cast<PrivateIdentifier*>( |
113 malloc(sizeof(PrivateIdentifier) + nameLen + 1)); | 166 malloc(sizeof(PrivateIdentifier) + nameLen + 1)); |
114 char* nameStorage = reinterpret_cast<char*>(identifier + 1); | 167 char* nameStorage = reinterpret_cast<char*>(identifier + 1); |
115 memcpy(nameStorage, name, nameLen + 1); | 168 memcpy(nameStorage, name, nameLen + 1); |
116 identifier->isString = true; | 169 identifier->isString = true; |
117 identifier->value.string = reinterpret_cast<NPUTF8*>(nameStorage); | 170 identifier->value.string = reinterpret_cast<NPUTF8*>(nameStorage); |
118 key.string = nameStorage; | 171 key._string = nameStorage; |
119 (*identMap)[key] = identifier; | 172 identMap->set(key, identifier); |
120 return (NPIdentifier)identifier; | 173 return (NPIdentifier)identifier; |
121 } | 174 } |
122 | 175 |
123 return 0; | 176 return 0; |
124 } | 177 } |
125 | 178 |
126 void NPN_GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, | 179 void NPN_GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, |
127 NPIdentifier* identifiers) { | 180 NPIdentifier* identifiers) { |
128 ASSERT(names); | 181 ASSERT(names); |
129 ASSERT(identifiers); | 182 ASSERT(identifiers); |
130 | 183 |
131 if (names && identifiers) | 184 if (names && identifiers) |
132 for (int i = 0; i < nameCount; i++) | 185 for (int i = 0; i < nameCount; i++) |
133 identifiers[i] = NPN_GetStringIdentifier(names[i]); | 186 identifiers[i] = NPN_GetStringIdentifier(names[i]); |
134 } | 187 } |
135 | 188 |
136 NPIdentifier NPN_GetIntIdentifier(int32_t intid) { | 189 NPIdentifier NPN_GetIntIdentifier(int32_t intid) { |
137 // AutoLock safeLock(IntIdentifierMapLock); | 190 // AutoLock safeLock(IntIdentifierMapLock); |
138 | 191 |
139 IntIdentifierMap* identMap = getIntIdentifierMap(); | 192 IntIdentifierMap* identMap = getIntIdentifierMap(); |
140 IntIdentifierMap::iterator iter = identMap->find(intid); | 193 IntIdentifierMap::iterator iter = identMap->find(intid); |
141 if (iter != identMap->end()) | 194 if (iter != identMap->end()) |
142 return static_cast<NPIdentifier>(iter->second); | 195 return static_cast<NPIdentifier>(iter->second); |
143 | 196 |
144 // We never release identifiers, so this dictionary will grow. | 197 // We never release identifiers, so this dictionary will grow. |
145 PrivateIdentifier* identifier = reinterpret_cast<PrivateIdentifier*>( | 198 PrivateIdentifier* identifier = reinterpret_cast<PrivateIdentifier*>( |
146 malloc(sizeof(PrivateIdentifier))); | 199 malloc(sizeof(PrivateIdentifier))); |
147 identifier->isString = false; | 200 identifier->isString = false; |
148 identifier->value.number = intid; | 201 identifier->value.number = intid; |
149 (*identMap)[intid] = identifier; | 202 identMap->set(intid, identifier); |
150 return (NPIdentifier)identifier; | 203 return (NPIdentifier)identifier; |
151 } | 204 } |
152 | 205 |
153 bool NPN_IdentifierIsString(NPIdentifier identifier) { | 206 bool NPN_IdentifierIsString(NPIdentifier identifier) { |
154 PrivateIdentifier* i = reinterpret_cast<PrivateIdentifier*>(identifier); | 207 PrivateIdentifier* i = reinterpret_cast<PrivateIdentifier*>(identifier); |
155 return i->isString; | 208 return i->isString; |
156 } | 209 } |
157 | 210 |
158 NPUTF8 *NPN_UTF8FromIdentifier(NPIdentifier identifier) { | 211 NPUTF8 *NPN_UTF8FromIdentifier(NPIdentifier identifier) { |
159 PrivateIdentifier* i = reinterpret_cast<PrivateIdentifier*>(identifier); | 212 PrivateIdentifier* i = reinterpret_cast<PrivateIdentifier*>(identifier); |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
269 // The g_live_objects is a hash table of all live objects to their owner | 322 // The g_live_objects is a hash table of all live objects to their owner |
270 // objects. Presence in this table is used primarily to determine if | 323 // objects. Presence in this table is used primarily to determine if |
271 // objects are live or not. | 324 // objects are live or not. |
272 // | 325 // |
273 // The g_root_objects is a hash table of root objects to a set of | 326 // The g_root_objects is a hash table of root objects to a set of |
274 // objects that should be deactivated in sync with the root. A | 327 // objects that should be deactivated in sync with the root. A |
275 // root is defined as a top-level owner object. This is used on | 328 // root is defined as a top-level owner object. This is used on |
276 // Frame teardown to deactivate all objects associated | 329 // Frame teardown to deactivate all objects associated |
277 // with a particular plugin. | 330 // with a particular plugin. |
278 | 331 |
279 typedef std::set<NPObject*> NPObjectSet; | 332 typedef WTF::HashSet<NPObject*> NPObjectSet; |
280 typedef std::map<NPObject*, NPObject*> NPObjectMap; | 333 typedef WTF::HashMap<NPObject*, NPObject*> NPObjectMap; |
281 typedef std::map<NPObject*, NPObjectSet*> NPRootObjectMap; | 334 typedef WTF::HashMap<NPObject*, NPObjectSet*> NPRootObjectMap; |
282 | 335 |
283 // A map of live NPObjects with pointers to their Roots. | 336 // A map of live NPObjects with pointers to their Roots. |
284 NPObjectMap g_live_objects; | 337 NPObjectMap g_live_objects; |
285 | 338 |
286 // A map of the root objects and the list of NPObjects | 339 // A map of the root objects and the list of NPObjects |
287 // associated with that object. | 340 // associated with that object. |
288 NPRootObjectMap g_root_objects; | 341 NPRootObjectMap g_root_objects; |
289 | 342 |
290 void _NPN_RegisterObject(NPObject* obj, NPObject* owner) { | 343 void _NPN_RegisterObject(NPObject* obj, NPObject* owner) { |
291 ASSERT(obj); | 344 ASSERT(obj); |
292 | 345 |
293 // Check if already registered. | 346 // Check if already registered. |
294 if (g_live_objects.find(obj) != g_live_objects.end()) { | 347 if (g_live_objects.find(obj) != g_live_objects.end()) { |
295 return; | 348 return; |
296 } | 349 } |
297 | 350 |
298 if (!owner) { | 351 if (!owner) { |
299 // Registering a new owner object. | 352 // Registering a new owner object. |
300 ASSERT(g_root_objects.find(obj) == g_root_objects.end()); | 353 ASSERT(g_root_objects.find(obj) == g_root_objects.end()); |
301 g_root_objects[obj] = new NPObjectSet(); | 354 g_root_objects.set(obj, new NPObjectSet()); |
302 } else { | 355 } else { |
303 // Always associate this object with it's top-most parent. | 356 // Always associate this object with it's top-most parent. |
304 // Since we always flatten, we only have to look up one level. | 357 // Since we always flatten, we only have to look up one level. |
305 NPObjectMap::iterator owner_entry = g_live_objects.find(owner); | 358 NPObjectMap::iterator owner_entry = g_live_objects.find(owner); |
306 NPObject* parent = NULL; | 359 NPObject* parent = NULL; |
307 if (g_live_objects.end() != owner_entry) | 360 if (g_live_objects.end() != owner_entry) |
308 parent = owner_entry->second; | 361 parent = owner_entry->second; |
309 | 362 |
310 if (parent) { | 363 if (parent) { |
311 owner = parent; | 364 owner = parent; |
312 } | 365 } |
313 ASSERT(g_root_objects.find(obj) == g_root_objects.end()); | 366 ASSERT(g_root_objects.find(obj) == g_root_objects.end()); |
314 if (g_root_objects.find(owner) != g_root_objects.end()) | 367 if (g_root_objects.find(owner) != g_root_objects.end()) |
315 (g_root_objects[owner])->insert(obj); | 368 g_root_objects.get(owner)->add(obj); |
316 } | 369 } |
317 | 370 |
318 ASSERT(g_live_objects.find(obj) == g_live_objects.end()); | 371 ASSERT(g_live_objects.find(obj) == g_live_objects.end()); |
319 g_live_objects[obj] = owner; | 372 g_live_objects.set(obj, owner); |
320 } | 373 } |
321 | 374 |
322 void _NPN_UnregisterObject(NPObject* obj) { | 375 void _NPN_UnregisterObject(NPObject* obj) { |
323 ASSERT(obj); | 376 ASSERT(obj); |
324 ASSERT(g_live_objects.find(obj) != g_live_objects.end()); | 377 ASSERT(g_live_objects.find(obj) != g_live_objects.end()); |
325 | 378 |
326 NPObject* owner = NULL; | 379 NPObject* owner = NULL; |
327 if (g_live_objects.find(obj) != g_live_objects.end()) | 380 if (g_live_objects.find(obj) != g_live_objects.end()) |
328 owner = g_live_objects.find(obj)->second; | 381 owner = g_live_objects.find(obj)->second; |
329 | 382 |
330 if (owner == NULL) { | 383 if (owner == NULL) { |
331 // Unregistering a owner object; also unregister it's descendants. | 384 // Unregistering a owner object; also unregister it's descendants. |
332 ASSERT(g_root_objects.find(obj) != g_root_objects.end()); | 385 ASSERT(g_root_objects.find(obj) != g_root_objects.end()); |
333 NPObjectSet* set = g_root_objects[obj]; | 386 NPObjectSet* set = g_root_objects.get(obj); |
334 while (set->size() > 0) { | 387 while (set->size() > 0) { |
335 #ifndef NDEBUG | 388 #ifndef NDEBUG |
336 size_t size = set->size(); | 389 int size = set->size(); |
337 #endif | 390 #endif |
338 NPObject* sub_object = *(set->begin()); | 391 NPObject* sub_object = *(set->begin()); |
339 // The sub-object should not be a owner! | 392 // The sub-object should not be a owner! |
340 ASSERT(g_root_objects.find(sub_object) == g_root_objects.end()); | 393 ASSERT(g_root_objects.find(sub_object) == g_root_objects.end()); |
341 | 394 |
342 // First, unregister the object. | 395 // First, unregister the object. |
343 set->erase(sub_object); | 396 set->remove(sub_object); |
344 g_live_objects.erase(sub_object); | 397 g_live_objects.remove(sub_object); |
345 | 398 |
346 // Remove the JS references to the object. | 399 // Remove the JS references to the object. |
347 ForgetV8ObjectForNPObject(sub_object); | 400 ForgetV8ObjectForNPObject(sub_object); |
348 | 401 |
349 ASSERT(set->size() < size); | 402 ASSERT(set->size() < size); |
350 } | 403 } |
351 delete set; | 404 delete set; |
352 g_root_objects.erase(obj); | 405 g_root_objects.remove(obj); |
353 } else { | 406 } else { |
354 NPRootObjectMap::iterator owner_entry = g_root_objects.find(owner); | 407 NPRootObjectMap::iterator owner_entry = g_root_objects.find(owner); |
355 if (owner_entry != g_root_objects.end()) { | 408 if (owner_entry != g_root_objects.end()) { |
356 NPObjectSet* list = owner_entry->second; | 409 NPObjectSet* list = owner_entry->second; |
357 ASSERT(list->find(obj) != list->end()); | 410 ASSERT(list->find(obj) != list->end()); |
358 list->erase(obj); | 411 list->remove(obj); |
359 } | 412 } |
360 } | 413 } |
361 ForgetV8ObjectForNPObject(obj); | 414 ForgetV8ObjectForNPObject(obj); |
362 | 415 |
363 g_live_objects.erase(obj); | 416 g_live_objects.remove(obj); |
364 } | 417 } |
365 | 418 |
366 bool _NPN_IsAlive(NPObject* obj) { | 419 bool _NPN_IsAlive(NPObject* obj) { |
367 return g_live_objects.find(obj) != g_live_objects.end(); | 420 return g_live_objects.find(obj) != g_live_objects.end(); |
368 } | 421 } |
369 | 422 |
370 } // extern "C" | 423 } // extern "C" |
OLD | NEW |