Changeset 291 for trunk/src/ole32/ole32.cpp
- Timestamp:
- Jul 12, 1999, 2:21:37 AM (26 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/ole32/ole32.cpp
r200 r291 1 /* 2 * 3 * Project Odin Software License can be found in LICENSE.TXT 4 * 5 */ 6 /* 7 * COM/OLE misc. functions. 8 * 9 * 1/7/99 10 * 11 * Copyright 1999 David J. Raison 12 * 13 * Some portions from Wine Implementation 14 * Copyright 1995 Martin von Loewis 15 * Copyright 1998 Justin Bradford 16 * Copyright 1999 Francis Beaudet 17 * Copyright 1999 Sylvain St-Germain 18 */ 19 20 #include "ole32.h" 21 22 #include "oString.h" 23 #include "moniker.h" // RunningObjectTableImpl_*** 24 25 // ====================================================================== 26 // Local Data 27 // ====================================================================== 28 typedef HRESULT (CALLBACK *DllGetClassObjectFunc)(REFCLSID clsid, REFIID iid, LPVOID *ppv); 29 1 30 /* 31 * This linked list contains the list of registered class objects. These 32 * are mostly used to register the factories for out-of-proc servers of OLE 33 * objects. 34 */ 35 typedef struct tagRegisteredClass 36 { 37 CLSID classIdentifier; 38 LPUNKNOWN classObject; 39 DWORD runContext; 40 DWORD connectFlags; 41 DWORD dwCookie; 42 struct tagRegisteredClass* nextClass; 43 } RegisteredClass; 44 45 static RegisteredClass* firstRegisteredClass = NULL; 46 47 /* 48 * COM External Lock structures and methods declaration 2 49 * 3 * Project Odin Software License can be found in LICENSE.TXT 50 * This api provides a linked list to managed external references to 51 * COM objects. 4 52 * 5 53 */ 54 55 #define EL_END_OF_LIST 0 56 #define EL_NOT_FOUND 0 57 6 58 /* 7 * Win32 OLE stubs for OS/2 59 * Declaration of the static structure that manage the 60 * external lock to COM objects. 61 */ 62 typedef struct COM_ExternalLock COM_ExternalLock; 63 typedef struct COM_ExternalLockList COM_ExternalLockList; 64 65 struct COM_ExternalLock 66 { 67 IUnknown *pUnk; /* IUnknown referenced */ 68 ULONG uRefCount; /* external lock counter to IUnknown object*/ 69 COM_ExternalLock *next; /* Pointer to next element in list */ 70 }; 71 72 struct COM_ExternalLockList 73 { 74 COM_ExternalLock *head; /* head of list */ 75 }; 76 77 /* 78 * Declaration and initialization of the static structure that manages 79 * the external lock to COM objects. 80 */ 81 static COM_ExternalLockList elList = { EL_END_OF_LIST }; 82 83 /* 84 * This open DLL table belongs in a per process table, but my guess is that 85 * it shouldn't live in the kernel, so I'll put them out here in DLL 86 * space assuming that there is one OLE32 per process. 87 */ 88 typedef struct tagOpenDll 89 { 90 char * DllName; /* really only needed for debugging */ 91 HINSTANCE hLibrary; 92 struct tagOpenDll * next; 93 } OpenDll; 94 95 static OpenDll * openDllList = NULL; /* linked list of open dlls */ 96 97 /* 98 * Com Library reference count... 8 99 * 9 * 1998/06/12 10 * 11 * Copyright 1998 Sander van Leeuwen 100 * Used to control loading / unloading of Library resources, 101 * Runnng object table, DLL's etc... 12 102 */ 13 14 #include "ole32.h" 103 static LONG COM_ref = 0; 104 105 // ====================================================================== 106 // Prototypes. 107 // ====================================================================== 108 static HRESULT COM_GetRegisteredClassObject( 109 REFCLSID rclsid, 110 DWORD dwClsContext, 111 LPUNKNOWN * ppUnk); 112 113 static void COM_RevokeAllClasses(); 114 115 static void COM_ExternalLockFreeList(); 116 117 static void COM_ExternalLockAddRef( 118 IUnknown * pUnk); 119 120 static void COM_ExternalLockRelease( 121 IUnknown * pUnk, 122 BOOL bRelAll); 123 124 static BOOL COM_ExternalLockInsert( 125 IUnknown * pUnk); 126 127 static void COM_ExternalLockDelete( 128 COM_ExternalLock * element); 129 130 static COM_ExternalLock * COM_ExternalLockFind( 131 IUnknown * pUnk); 132 133 static COM_ExternalLock * COM_ExternalLockLocate( 134 COM_ExternalLock * element, 135 IUnknown * pUnk); 136 137 // ====================================================================== 138 // Public API's 139 // ====================================================================== 15 140 16 141 // ---------------------------------------------------------------------- … … 20 145 { 21 146 dprintf(("OLE32.CoBuildVersion\n")); 22 return (rmm<<16)+rup; 23 } 24 147 return (rmm << 16) + rup; 148 } 149 150 // ---------------------------------------------------------------------- 151 // CoDosDateTimeToFileTime 152 // ---------------------------------------------------------------------- 153 BOOL WIN32API CoDosDateTimeToFileTime(WORD nDosDate, WORD nDosTime, 154 FILETIME *lpFileTime) 155 { 156 dprintf(("OLE32: CoDosDateTimeToFileTime")); 157 158 return DosDateTimeToFileTime(nDosDate, nDosTime, lpFileTime); 159 } 160 161 // ---------------------------------------------------------------------- 162 // CoDosDateTimeToFileTime 163 // ---------------------------------------------------------------------- 164 HRESULT WIN32API CoFileTimeNow(FILETIME *lpFileTime) 165 { 166 SYSTEMTIME systime; 167 168 dprintf(("OLE32: CoFileTimeNow")); 169 170 GetSystemTime(&systime); 171 return SystemTimeToFileTime(&systime, lpFileTime); 172 } 173 174 // ---------------------------------------------------------------------- 175 // CoDosDateTimeToFileTime 176 // ---------------------------------------------------------------------- 177 BOOL WIN32API CoFileTimeToDosDateTime(FILETIME *lpFileTime, LPWORD lpDosDate, 178 LPWORD lpDosTime) 179 { 180 dprintf(("OLE32: CoFileTimeToDosDateTime")); 181 182 return FileTimeToDosDateTime(lpFileTime, lpDosDate, lpDosTime); 183 } 184 185 // ---------------------------------------------------------------------- 186 // CoInitialize() 187 // ---------------------------------------------------------------------- 188 HRESULT WIN32API CoInitialize(LPVOID lpReserved) 189 { 190 dprintf(("OLE32: CoInitialize\n")); 191 192 return CoInitializeEx(lpReserved, COINIT_APARTMENTTHREADED); 193 } 194 195 // ---------------------------------------------------------------------- 196 // CoInitializeEx() 197 // ---------------------------------------------------------------------- 198 HRESULT WIN32API CoInitializeEx( 199 LPVOID lpReserved, // [in] pointer to win32 malloc interface 200 DWORD dwCoInit) // [in] A value from COINIT specifies the thread 201 { 202 HRESULT hr; 203 204 dprintf(("OLE32: CoInitializeEx(%p, %lx)\n", lpReserved, dwCoInit)); 205 206 if (lpReserved != NULL) 207 { 208 dprintf(("Warning: Bad parameter %p, must be an old Windows Application", lpReserved)); 209 } 210 211 /* 212 * Check for unsupported features. 213 */ 214 if (dwCoInit != COINIT_APARTMENTTHREADED) 215 { 216 dprintf(("Warning: Unsupported flag %lx", dwCoInit)); 217 /* Hope for the best and continue anyway */ 218 } 219 220 /* 221 * Initialise the Running Object Table 222 */ 223 hr = S_FALSE; 224 if (++COM_ref == 1) 225 { 226 hr = RunningObjectTableImpl_Initialize(); 227 if (hr != S_OK) 228 --COM_ref; 229 } 230 231 return hr; 232 } 233 234 // ---------------------------------------------------------------------- 235 // CoUninitialize() 236 // ---------------------------------------------------------------------- 237 void WIN32API CoUninitialize(void) 238 { 239 dprintf(("OLE32: CoUninitialize")); 240 241 if (--COM_ref == 0) 242 { 243 dprintf(("OLE32: Releasing COM libraries")); 244 245 RunningObjectTableImpl_UnInitialize(); 246 247 COM_RevokeAllClasses(); 248 249 CoFreeAllLibraries(); 250 251 COM_ExternalLockFreeList(); 252 } 253 } 254 255 // ---------------------------------------------------------------------- 256 // CoCreateInstance 257 // ---------------------------------------------------------------------- 258 HRESULT WIN32API CoCreateInstance 259 (REFCLSID rclsid, 260 LPUNKNOWN pUnkOuter, 261 DWORD dwClsContext, 262 REFIID iid, 263 LPVOID * ppv) 264 { 265 HRESULT hres; 266 LPCLASSFACTORY lpclf = 0; 267 268 oStringA tCLSID(rclsid); 269 oStringA tIId(iid); 270 271 dprintf(("OLE32: CoCreateInstance")); 272 dprintf((" CLSID:%s", (char *)tCLSID)); 273 dprintf((" IID :%s", (char *)tIId)); 274 275 // Sanity check 276 if (ppv == 0) 277 return E_POINTER; 278 279 *ppv = 0; 280 281 // Get a class factory to construct the object we want. 282 hres = CoGetClassObject(rclsid, dwClsContext, NULL, &IID_IClassFactory, (LPVOID *)&lpclf); 283 284 if (FAILED(hres)) 285 return hres; 286 287 // Create the object and don't forget to release the factory 288 hres = IClassFactory_CreateInstance(lpclf, pUnkOuter, iid, ppv); 289 IClassFactory_Release(lpclf); 290 291 return hres; 292 } 293 294 // ---------------------------------------------------------------------- 295 // CoCreateInstanceEx 296 // ---------------------------------------------------------------------- 297 HRESULT WIN32API CoCreateInstanceEx 298 (REFCLSID rclsid, 299 LPUNKNOWN pUnkOuter, 300 DWORD dwClsContext, 301 COSERVERINFO * pServerInfo, 302 ULONG cmq, 303 MULTI_QI * pResults) 304 { 305 IUnknown * pUnk = NULL; 306 HRESULT hr; 307 ULONG index; 308 int successCount = 0; 309 310 oStringA tCLSID(rclsid); 311 312 dprintf(("OLE32: CoCreateInstanceEx")); 313 dprintf((" CLSID:%s", (char *)tCLSID)); 314 315 // Sanity check 316 if ( (cmq == 0) || (pResults == NULL)) 317 return E_INVALIDARG; 318 319 if (pServerInfo != NULL) 320 dprintf(("OLE32: CoCreateInstanceEx - pServerInfo not supported!")); 321 322 // Initialize all the "out" parameters. 323 for (index = 0; index < cmq; index++) 324 { 325 pResults[index].pItf = NULL; 326 pResults[index].hr = E_NOINTERFACE; 327 } 328 329 /* 330 * Get the object and get it's IUnknown pointer. 331 */ 332 hr = CoCreateInstance(rclsid, pUnkOuter, dwClsContext, &IID_IUnknown, (VOID**)&pUnk); 333 334 if (hr) 335 return hr; 336 337 /* 338 * Then, query for all the interfaces requested. 339 */ 340 for (index = 0; index < cmq; index++) 341 { 342 pResults[index].hr = IUnknown_QueryInterface(pUnk, pResults[index].pIID, (VOID**)&(pResults[index].pItf)); 343 344 if (pResults[index].hr == S_OK) 345 successCount++; 346 } 347 348 /* 349 * Release our temporary unknown pointer. 350 */ 351 IUnknown_Release(pUnk); 352 353 if (successCount == 0) 354 return E_NOINTERFACE; 355 356 if (successCount != cmq) 357 return CO_S_NOTALLINTERFACES; 358 359 return S_OK; 360 } 361 362 // ---------------------------------------------------------------------- 363 // CoGetClassObject 364 // ---------------------------------------------------------------------- 365 HRESULT WIN32API CoGetClassObject 366 (REFCLSID rclsid, 367 DWORD dwClsContext, 368 LPVOID pvReserved, 369 REFIID iid, 370 LPVOID * ppv) 371 { 372 LPUNKNOWN regClassObject; 373 HRESULT hres = E_UNEXPECTED; 374 375 char dllName[MAX_PATH+1]; 376 LONG dllNameLen = sizeof(dllName); 377 HINSTANCE hLibrary; 378 379 DllGetClassObjectFunc DllGetClassObject; 380 oStringA tCLSID(rclsid); 381 382 #ifdef DEBUG 383 oStringA tIId(iid); 384 dprintf(("OLE32: CoGetClassObject")); 385 dprintf((" CLSID:%s", (char *)tCLSID)); 386 dprintf((" IID :%s", (char *)tIId)); 387 #endif 388 389 // First, try and see if we can't match the class ID with one of the 390 // registered classes. 391 if (S_OK == COM_GetRegisteredClassObject(rclsid, dwClsContext, ®ClassObject)) 392 { 393 // Get the required interface from the retrieved pointer. 394 hres = IUnknown_QueryInterface(regClassObject, iid, ppv); 395 396 // Since QI got another reference on the pointer, we want to release the 397 // one we already have. If QI was unsuccessful, this will release the object. This 398 // is good since we are not returning it in the "out" parameter. 399 IUnknown_Release(regClassObject); 400 401 return hres; 402 } 403 404 // out of process and remote servers not supported yet... 405 if (dwClsContext & CLSCTX_LOCAL_SERVER) 406 { 407 dprintf(("OLE32: CoGetClassObject - CLSCTX_LOCAL_SERVER not supported!\n")); 408 return E_ACCESSDENIED; 409 } 410 411 if (dwClsContext & CLSCTX_REMOTE_SERVER) 412 { 413 dprintf(("OLE32: CoGetClassObject - CLSCTX_REMOTE_SERVER not supported!\n")); 414 return E_ACCESSDENIED; 415 } 416 417 // Get down to the biz.. 418 if (dwClsContext & (CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER)) 419 { 420 HKEY CLSIDkey; 421 HKEY key; 422 423 /* lookup CLSID in registry key HKCR/CLSID */ 424 hres = RegOpenKeyExA(HKEY_CLASSES_ROOT, "CLSID", 0, KEY_READ, &CLSIDkey); 425 if (hres != ERROR_SUCCESS) 426 return REGDB_E_READREGDB; 427 428 hres = RegOpenKeyExA(CLSIDkey, tCLSID, 0, KEY_QUERY_VALUE, &key); 429 if (hres != ERROR_SUCCESS) 430 { 431 RegCloseKey(CLSIDkey); 432 return REGDB_E_CLASSNOTREG; 433 } 434 435 hres = RegQueryValueA(key, "InprocServer32", dllName, &dllNameLen); 436 RegCloseKey(key); 437 RegCloseKey(CLSIDkey); 438 if (hres != ERROR_SUCCESS) 439 { 440 dprintf(("OLE32: CoGetClassObject - InprocServer32 not found in registry")); 441 return REGDB_E_READREGDB; 442 } 443 444 dprintf(("OLE32: CoGetClassObject - Registry reports InprocServer32 dll as %s\n", dllName)); 445 446 /* open dll, call DllGetClassFactory */ 447 hLibrary = CoLoadLibrary(dllName, TRUE); 448 if (hLibrary == 0) 449 { 450 dprintf(("OLE32: CoGetClassObject - couldn't load %s\n", dllName)); 451 return E_ACCESSDENIED; /* or should this be CO_E_DLLNOTFOUND? */ 452 } 453 454 DllGetClassObject = (DllGetClassObjectFunc)GetProcAddress(hLibrary, "DllGetClassObject"); 455 if (!DllGetClassObject) 456 { 457 dprintf(("OLE32: CoGetClassObject - couldn't function DllGetClassObject in %s\n", dllName)); 458 /* not sure if this should be called here CoFreeLibrary(hLibrary);*/ 459 return E_ACCESSDENIED; 460 } 461 462 // Ask the DLL for it's class object. (there was a note here about class 463 // factories but this is good. 464 return DllGetClassObject(rclsid, iid, ppv); 465 } 466 return hres; 467 } 468 469 // ====================================================================== 470 // Private functions. 471 // ====================================================================== 472 473 // ---------------------------------------------------------------------- 474 // COM_GetRegisteredClassObject 475 // ---------------------------------------------------------------------- 476 // This internal method is used to scan the registered class list to 477 // find a class object. 478 // 479 // Params: 480 // rclsid Class ID of the class to find. 481 // dwClsContext Class context to match. 482 // ppv [out] returns a pointer to the class object. Complying 483 // to normal COM usage, this method will increase the 484 // reference count on this object. 485 static HRESULT COM_GetRegisteredClassObject 486 (REFCLSID rclsid, 487 DWORD dwClsContext, 488 LPUNKNOWN * ppUnk) 489 { 490 RegisteredClass* curClass; 491 492 oStringA tCLSID(rclsid); 493 494 dprintf(("OLE32: COM_GetRegisteredClassObject")); 495 dprintf((" CLSID:%s", (char *)tCLSID)); 496 497 // Sanity check 498 if (ppUnk == 0) 499 return E_POINTER; 500 501 // Iterate through the whole list and try to match the class ID. 502 curClass = firstRegisteredClass; 503 504 while (curClass != 0) 505 { 506 // Check if we have a match on the class ID. 507 if (IsEqualGUID(curClass->classIdentifier, rclsid)) 508 { 509 // Since we don't do out-of process or DCOM just right away, 510 // let's ignore the class context. 511 512 // We have a match, return the pointer to the class object. 513 *ppUnk = curClass->classObject; 514 515 IUnknown_AddRef(curClass->classObject); 516 517 return S_OK; 518 } 519 520 // Step to the next class in the list. 521 curClass = curClass->nextClass; 522 } 523 524 // If we get to here, we haven't found our class. 525 return S_FALSE; 526 } 527 528 // ---------------------------------------------------------------------- 529 // COM_RevokeAllClasses 530 // ---------------------------------------------------------------------- 531 // This method is called when the COM libraries are uninitialized to 532 // release all the references to the class objects registered with 533 // the library 534 static void COM_RevokeAllClasses() 535 { 536 dprintf(("OLE32: COM_RevokeAllClasses")); 537 538 while (firstRegisteredClass != 0) 539 { 540 CoRevokeClassObject(firstRegisteredClass->dwCookie); 541 } 542 } 543 544 // ---------------------------------------------------------------------- 545 // COM_ExternalLockAddRef 546 // ---------------------------------------------------------------------- 547 // Method that increments the count for a IUnknown* in the linked 548 // list. The item is inserted if not already in the list. 549 static void COM_ExternalLockAddRef(IUnknown * pUnk) 550 { 551 dprintf(("OLE32: COM_ExternalLockAddRef")); 552 553 COM_ExternalLock *externalLock = COM_ExternalLockFind(pUnk); 554 555 /* 556 * Add an external lock to the object. If it was already externally 557 * locked, just increase the reference count. If it was not. 558 * add the item to the list. 559 */ 560 if ( externalLock == EL_NOT_FOUND ) 561 COM_ExternalLockInsert(pUnk); 562 else 563 externalLock->uRefCount++; 564 565 /* 566 * Add an internal lock to the object 567 */ 568 IUnknown_AddRef(pUnk); 569 } 570 571 // ---------------------------------------------------------------------- 572 // COM_ExternalLockRelease 573 // ---------------------------------------------------------------------- 574 // Method that decrements the count for a IUnknown* in the linked 575 // list. The item is removed from the list if its count end up at zero or if 576 // bRelAll is TRUE. 577 static void COM_ExternalLockRelease(IUnknown * pUnk, BOOL bRelAll) 578 { 579 dprintf(("OLE32: COM_ExternalLockRelease")); 580 581 COM_ExternalLock * externalLock = COM_ExternalLockFind(pUnk); 582 583 if ( externalLock != EL_NOT_FOUND ) 584 { 585 do 586 { 587 externalLock->uRefCount--; /* release external locks */ 588 IUnknown_Release(pUnk); /* release local locks as well */ 589 590 if ( bRelAll == FALSE ) 591 break; /* perform single release */ 592 593 } while ( externalLock->uRefCount > 0 ); 594 595 if ( externalLock->uRefCount == 0 ) /* get rid of the list entry */ 596 COM_ExternalLockDelete(externalLock); 597 } 598 } 599 600 // ---------------------------------------------------------------------- 601 // COM_ExternalLockFreeList 602 // ---------------------------------------------------------------------- 603 // Method that frees the content of the list. 604 static void COM_ExternalLockFreeList() 605 { 606 dprintf(("OLE32: COM_ExternalLockFreeList")); 607 608 COM_ExternalLock *head; 609 610 head = elList.head; /* grab it by the head */ 611 while (head != EL_END_OF_LIST) 612 { 613 COM_ExternalLockDelete(head); /* get rid of the head stuff */ 614 615 head = elList.head; /* get the new head... */ 616 } 617 } 618 619 // ---------------------------------------------------------------------- 620 // COM_ExternalLockDump 621 // ---------------------------------------------------------------------- 622 // Method that dump the content of the list. 623 void COM_ExternalLockDump() 624 { 625 dprintf(("OLE32: COM_ExternalLockDump")); 626 627 COM_ExternalLock *current = elList.head; 628 629 printf("External lock list:"); 630 631 while ( current != EL_END_OF_LIST ) 632 { 633 dprintf((" %p with %lu references count.\n", current->pUnk, current->uRefCount)); 634 635 /* Skip to the next item */ 636 current = current->next; 637 } 638 } 639 640 // ---------------------------------------------------------------------- 641 // COM_ExternalLockFind 642 // ---------------------------------------------------------------------- 643 // Find a IUnknown* in the linked list 644 static COM_ExternalLock * COM_ExternalLockFind(IUnknown *pUnk) 645 { 646 dprintf(("OLE32: COM_ExternalLockFind")); 647 648 return COM_ExternalLockLocate(elList.head, pUnk); 649 } 650 651 // ---------------------------------------------------------------------- 652 // COM_ExternalLockLocate 653 // ---------------------------------------------------------------------- 654 // Recursivity agent for IUnknownExternalLockList_Find 655 static COM_ExternalLock * COM_ExternalLockLocate( COM_ExternalLock * element, IUnknown * pUnk) 656 { 657 if ( element == EL_END_OF_LIST ) 658 return EL_NOT_FOUND; 659 660 else if ( element->pUnk == pUnk ) /* We found it */ 661 return element; 662 663 else /* Not the right guy, keep on looking */ 664 return COM_ExternalLockLocate( element->next, pUnk); 665 } 666 667 // ---------------------------------------------------------------------- 668 // COM_ExternalLockInsert 669 // ---------------------------------------------------------------------- 670 // Insert a new IUnknown* to the linked list 671 static BOOL COM_ExternalLockInsert(IUnknown * pUnk) 672 { 673 dprintf(("OLE32: COM_ExternalLockInsert")); 674 675 COM_ExternalLock * newLock = NULL; 676 COM_ExternalLock * previousHead = NULL; 677 678 // Allocate space for the new storage object 679 newLock = (COM_ExternalLock *)HeapAlloc(GetProcessHeap(), 0, sizeof(COM_ExternalLock)); 680 681 if (newLock != NULL) 682 { 683 if ( elList.head == EL_END_OF_LIST ) 684 elList.head = newLock; /* The list is empty */ 685 else 686 { 687 // insert does it at the head 688 previousHead = elList.head; 689 elList.head = newLock; 690 } 691 692 /* 693 * Set new list item data member 694 */ 695 newLock->pUnk = pUnk; 696 newLock->uRefCount = 1; 697 newLock->next = previousHead; 698 699 return TRUE; 700 } 701 702 return FALSE; 703 } 704 705 // ---------------------------------------------------------------------- 706 // ExternalLockDelete 707 // ---------------------------------------------------------------------- 708 // Method that removes an item from the linked list. 709 static void COM_ExternalLockDelete(COM_ExternalLock * itemList) 710 { 711 dprintf(("OLE32: ExternalLockDelete")); 712 713 COM_ExternalLock *current = elList.head; 714 715 if ( current == itemList ) 716 { 717 // this section handles the deletion of the first node 718 elList.head = itemList->next; 719 HeapFree( GetProcessHeap(), 0, itemList); 720 } 721 else 722 { 723 do 724 { 725 if ( current->next == itemList ) /* We found the item to free */ 726 { 727 current->next = itemList->next; /* readjust the list pointers */ 728 729 HeapFree( GetProcessHeap(), 0, itemList); 730 break; 731 } 732 733 /* Skip to the next item */ 734 current = current->next; 735 736 } while ( current != EL_END_OF_LIST ); 737 } 738 }
Note:
See TracChangeset
for help on using the changeset viewer.