1 // LoadCodecs.cpp 2 3 #include "StdAfx.h" 4 5 #include "LoadCodecs.h" 6 7 #include "../../../Common/MyCom.h" 8 #ifdef NEW_FOLDER_INTERFACE 9 #include "../../../Common/StringToInt.h" 10 #endif 11 #include "../../../Windows/PropVariant.h" 12 13 #include "../../ICoder.h" 14 #include "../../Common/RegisterArc.h" 15 16 #ifdef EXTERNAL_CODECS 17 #include "../../../Windows/FileFind.h" 18 #include "../../../Windows/DLL.h" 19 #ifdef NEW_FOLDER_INTERFACE 20 #include "../../../Windows/ResourceString.h" 21 static const UINT kIconTypesResId = 100; 22 #endif 23 24 #ifdef _WIN32 25 #include "Windows/Registry.h" 26 #endif 27 28 using namespace NWindows; 29 using namespace NFile; 30 31 #ifdef _WIN32 32 extern HINSTANCE g_hInstance; 33 #endif 34 35 static CSysString GetLibraryFolderPrefix() 36 { 37 #ifdef _WIN32 38 TCHAR fullPath[MAX_PATH + 1]; 39 ::GetModuleFileName(g_hInstance, fullPath, MAX_PATH); 40 CSysString path = fullPath; 41 int pos = path.ReverseFind(TEXT(CHAR_PATH_SEPARATOR)); 42 return path.Left(pos + 1); 43 #else 44 return CSysString(); // FIX IT 45 #endif 46 } 47 48 #define kCodecsFolderName TEXT("Codecs") 49 #define kFormatsFolderName TEXT("Formats") 50 static const TCHAR *kMainDll = TEXT("7z.dll"); 51 52 #ifdef _WIN32 53 static LPCTSTR kRegistryPath = TEXT("Software") TEXT(STRING_PATH_SEPARATOR) TEXT("7-zip"); 54 static LPCTSTR kProgramPathValue = TEXT("Path"); 55 static bool ReadPathFromRegistry(HKEY baseKey, CSysString &path) 56 { 57 NRegistry::CKey key; 58 if(key.Open(baseKey, kRegistryPath, KEY_READ) == ERROR_SUCCESS) 59 if (key.QueryValue(kProgramPathValue, path) == ERROR_SUCCESS) 60 { 61 NName::NormalizeDirPathPrefix(path); 62 return true; 63 } 64 return false; 65 } 66 67 #endif 68 69 CSysString GetBaseFolderPrefixFromRegistry() 70 { 71 CSysString moduleFolderPrefix = GetLibraryFolderPrefix(); 72 #ifdef _WIN32 73 if (!NFind::DoesFileExist(moduleFolderPrefix + kMainDll) && 74 !NFind::DoesDirExist(moduleFolderPrefix + kCodecsFolderName) && 75 !NFind::DoesDirExist(moduleFolderPrefix + kFormatsFolderName)) 76 { 77 CSysString path; 78 if (ReadPathFromRegistry(HKEY_CURRENT_USER, path)) 79 return path; 80 if (ReadPathFromRegistry(HKEY_LOCAL_MACHINE, path)) 81 return path; 82 } 83 #endif 84 return moduleFolderPrefix; 85 } 86 87 typedef UInt32 (WINAPI *GetNumberOfMethodsFunc)(UInt32 *numMethods); 88 typedef UInt32 (WINAPI *GetNumberOfFormatsFunc)(UInt32 *numFormats); 89 typedef UInt32 (WINAPI *GetHandlerPropertyFunc)(PROPID propID, PROPVARIANT *value); 90 typedef UInt32 (WINAPI *GetHandlerPropertyFunc2)(UInt32 index, PROPID propID, PROPVARIANT *value); 91 typedef UInt32 (WINAPI *CreateObjectFunc)(const GUID *clsID, const GUID *iid, void **outObject); 92 typedef UInt32 (WINAPI *SetLargePageModeFunc)(); 93 94 95 static HRESULT GetCoderClass(GetMethodPropertyFunc getMethodProperty, UInt32 index, 96 PROPID propId, CLSID &clsId, bool &isAssigned) 97 { 98 NWindows::NCOM::CPropVariant prop; 99 isAssigned = false; 100 RINOK(getMethodProperty(index, propId, &prop)); 101 if (prop.vt == VT_BSTR) 102 { 103 isAssigned = true; 104 clsId = *(const GUID *)prop.bstrVal; 105 } 106 else if (prop.vt != VT_EMPTY) 107 return E_FAIL; 108 return S_OK; 109 } 110 111 HRESULT CCodecs::LoadCodecs() 112 { 113 CCodecLib &lib = Libs.Back(); 114 lib.GetMethodProperty = (GetMethodPropertyFunc)lib.Lib.GetProc("GetMethodProperty"); 115 if (lib.GetMethodProperty == NULL) 116 return S_OK; 117 118 UInt32 numMethods = 1; 119 GetNumberOfMethodsFunc getNumberOfMethodsFunc = (GetNumberOfMethodsFunc)lib.Lib.GetProc("GetNumberOfMethods"); 120 if (getNumberOfMethodsFunc != NULL) 121 { 122 RINOK(getNumberOfMethodsFunc(&numMethods)); 123 } 124 125 for(UInt32 i = 0; i < numMethods; i++) 126 { 127 CDllCodecInfo info; 128 info.LibIndex = Libs.Size() - 1; 129 info.CodecIndex = i; 130 131 RINOK(GetCoderClass(lib.GetMethodProperty, i, NMethodPropID::kEncoder, info.Encoder, info.EncoderIsAssigned)); 132 RINOK(GetCoderClass(lib.GetMethodProperty, i, NMethodPropID::kDecoder, info.Decoder, info.DecoderIsAssigned)); 133 134 Codecs.Add(info); 135 } 136 return S_OK; 137 } 138 139 static HRESULT ReadProp( 140 GetHandlerPropertyFunc getProp, 141 GetHandlerPropertyFunc2 getProp2, 142 UInt32 index, PROPID propID, NCOM::CPropVariant &prop) 143 { 144 if (getProp2) 145 return getProp2(index, propID, &prop);; 146 return getProp(propID, &prop); 147 } 148 149 static HRESULT ReadBoolProp( 150 GetHandlerPropertyFunc getProp, 151 GetHandlerPropertyFunc2 getProp2, 152 UInt32 index, PROPID propID, bool &res) 153 { 154 NCOM::CPropVariant prop; 155 RINOK(ReadProp(getProp, getProp2, index, propID, prop)); 156 if (prop.vt == VT_BOOL) 157 res = VARIANT_BOOLToBool(prop.boolVal); 158 else if (prop.vt != VT_EMPTY) 159 return E_FAIL; 160 return S_OK; 161 } 162 163 static HRESULT ReadStringProp( 164 GetHandlerPropertyFunc getProp, 165 GetHandlerPropertyFunc2 getProp2, 166 UInt32 index, PROPID propID, UString &res) 167 { 168 NCOM::CPropVariant prop; 169 RINOK(ReadProp(getProp, getProp2, index, propID, prop)); 170 if (prop.vt == VT_BSTR) 171 res = prop.bstrVal; 172 else if (prop.vt != VT_EMPTY) 173 return E_FAIL; 174 return S_OK; 175 } 176 177 #endif 178 179 static const unsigned int kNumArcsMax = 48; 180 static unsigned int g_NumArcs = 0; 181 static const CArcInfo *g_Arcs[kNumArcsMax]; 182 void RegisterArc(const CArcInfo *arcInfo) 183 { 184 if (g_NumArcs < kNumArcsMax) 185 g_Arcs[g_NumArcs++] = arcInfo; 186 } 187 188 static void SplitString(const UString &srcString, UStringVector &destStrings) 189 { 190 destStrings.Clear(); 191 UString s; 192 int len = srcString.Length(); 193 if (len == 0) 194 return; 195 for (int i = 0; i < len; i++) 196 { 197 wchar_t c = srcString[i]; 198 if (c == L' ') 199 { 200 if (!s.IsEmpty()) 201 { 202 destStrings.Add(s); 203 s.Empty(); 204 } 205 } 206 else 207 s += c; 208 } 209 if (!s.IsEmpty()) 210 destStrings.Add(s); 211 } 212 213 void CArcInfoEx::AddExts(const wchar_t *ext, const wchar_t *addExt) 214 { 215 UStringVector exts, addExts; 216 if (ext != 0) 217 SplitString(ext, exts); 218 if (addExt != 0) 219 SplitString(addExt, addExts); 220 for (int i = 0; i < exts.Size(); i++) 221 { 222 CArcExtInfo extInfo; 223 extInfo.Ext = exts[i]; 224 if (i < addExts.Size()) 225 { 226 extInfo.AddExt = addExts[i]; 227 if (extInfo.AddExt == L"*") 228 extInfo.AddExt.Empty(); 229 } 230 Exts.Add(extInfo); 231 } 232 } 233 234 #ifdef EXTERNAL_CODECS 235 236 HRESULT CCodecs::LoadFormats() 237 { 238 const NDLL::CLibrary &lib = Libs.Back().Lib; 239 GetHandlerPropertyFunc getProp = 0; 240 GetHandlerPropertyFunc2 getProp2 = (GetHandlerPropertyFunc2)lib.GetProc("GetHandlerProperty2"); 241 if (getProp2 == NULL) 242 { 243 getProp = (GetHandlerPropertyFunc)lib.GetProc("GetHandlerProperty"); 244 if (getProp == NULL) 245 return S_OK; 246 } 247 248 UInt32 numFormats = 1; 249 GetNumberOfFormatsFunc getNumberOfFormats = (GetNumberOfFormatsFunc)lib.GetProc("GetNumberOfFormats"); 250 if (getNumberOfFormats != NULL) 251 { 252 RINOK(getNumberOfFormats(&numFormats)); 253 } 254 if (getProp2 == NULL) 255 numFormats = 1; 256 257 for(UInt32 i = 0; i < numFormats; i++) 258 { 259 CArcInfoEx item; 260 item.LibIndex = Libs.Size() - 1; 261 item.FormatIndex = i; 262 263 RINOK(ReadStringProp(getProp, getProp2, i, NArchive::kName, item.Name)); 264 265 NCOM::CPropVariant prop; 266 if (ReadProp(getProp, getProp2, i, NArchive::kClassID, prop) != S_OK) 267 continue; 268 if (prop.vt != VT_BSTR) 269 continue; 270 item.ClassID = *(const GUID *)prop.bstrVal; 271 prop.Clear(); 272 273 UString ext, addExt; 274 RINOK(ReadStringProp(getProp, getProp2, i, NArchive::kExtension, ext)); 275 RINOK(ReadStringProp(getProp, getProp2, i, NArchive::kAddExtension, addExt)); 276 item.AddExts(ext, addExt); 277 278 ReadBoolProp(getProp, getProp2, i, NArchive::kUpdate, item.UpdateEnabled); 279 if (item.UpdateEnabled) 280 ReadBoolProp(getProp, getProp2, i, NArchive::kKeepName, item.KeepName); 281 282 if (ReadProp(getProp, getProp2, i, NArchive::kStartSignature, prop) == S_OK) 283 if (prop.vt == VT_BSTR) 284 { 285 UINT len = ::SysStringByteLen(prop.bstrVal); 286 item.StartSignature.SetCapacity(len); 287 memmove(item.StartSignature, prop.bstrVal, len); 288 } 289 Formats.Add(item); 290 } 291 return S_OK; 292 } 293 294 #ifdef NEW_FOLDER_INTERFACE 295 void CCodecIcons::LoadIcons(HMODULE m) 296 { 297 UString iconTypes = MyLoadStringW(m, kIconTypesResId); 298 UStringVector pairs; 299 SplitString(iconTypes, pairs); 300 for (int i = 0; i < pairs.Size(); i++) 301 { 302 const UString &s = pairs[i]; 303 int pos = s.Find(L':'); 304 CIconPair iconPair; 305 iconPair.IconIndex = -1; 306 if (pos < 0) 307 pos = s.Length(); 308 else 309 { 310 UString num = s.Mid(pos + 1); 311 if (!num.IsEmpty()) 312 { 313 const wchar_t *end; 314 iconPair.IconIndex = (UInt32)ConvertStringToUInt64(num, &end); 315 if (*end != L'\0') 316 continue; 317 } 318 } 319 iconPair.Ext = s.Left(pos); 320 IconPairs.Add(iconPair); 321 } 322 } 323 324 bool CCodecIcons::FindIconIndex(const UString &ext, int &iconIndex) const 325 { 326 iconIndex = -1; 327 for (int i = 0; i < IconPairs.Size(); i++) 328 { 329 const CIconPair &pair = IconPairs[i]; 330 if (ext.CompareNoCase(pair.Ext) == 0) 331 { 332 iconIndex = pair.IconIndex; 333 return true; 334 } 335 } 336 return false; 337 } 338 #endif 339 340 #ifdef _7ZIP_LARGE_PAGES 341 extern "C" 342 { 343 extern SIZE_T g_LargePageSize; 344 } 345 #endif 346 347 HRESULT CCodecs::LoadDll(const CSysString &dllPath, bool needCheckDll) 348 { 349 if (needCheckDll) 350 { 351 NDLL::CLibrary library; 352 if (!library.LoadEx(dllPath, LOAD_LIBRARY_AS_DATAFILE)) 353 return S_OK; 354 } 355 Libs.Add(CCodecLib()); 356 CCodecLib &lib = Libs.Back(); 357 #ifdef NEW_FOLDER_INTERFACE 358 lib.Path = dllPath; 359 #endif 360 bool used = false; 361 HRESULT res = S_OK; 362 if (lib.Lib.Load(dllPath)) 363 { 364 #ifdef NEW_FOLDER_INTERFACE 365 lib.LoadIcons(); 366 #endif 367 368 #ifdef _7ZIP_LARGE_PAGES 369 if (g_LargePageSize != 0) 370 { 371 SetLargePageModeFunc setLargePageMode = (SetLargePageModeFunc)lib.Lib.GetProc("SetLargePageMode"); 372 if (setLargePageMode != 0) 373 setLargePageMode(); 374 } 375 #endif 376 377 lib.CreateObject = (CreateObjectFunc)lib.Lib.GetProc("CreateObject"); 378 if (lib.CreateObject != 0) 379 { 380 int startSize = Codecs.Size(); 381 res = LoadCodecs(); 382 used = (Codecs.Size() != startSize); 383 if (res == S_OK) 384 { 385 startSize = Formats.Size(); 386 res = LoadFormats(); 387 used = used || (Formats.Size() != startSize); 388 } 389 } 390 } 391 if (!used) 392 Libs.DeleteBack(); 393 return res; 394 } 395 396 HRESULT CCodecs::LoadDllsFromFolder(const CSysString &folderPrefix) 397 { 398 NFile::NFind::CEnumerator enumerator(folderPrefix + CSysString(TEXT("*"))); 399 NFile::NFind::CFileInfo fi; 400 while (enumerator.Next(fi)) 401 { 402 if (fi.IsDir()) 403 continue; 404 RINOK(LoadDll(folderPrefix + fi.Name, true)); 405 } 406 return S_OK; 407 } 408 409 #endif 410 411 #ifndef _SFX 412 static inline void SetBuffer(CByteBuffer &bb, const Byte *data, int size) 413 { 414 bb.SetCapacity(size); 415 memmove((Byte *)bb, data, size); 416 } 417 #endif 418 419 HRESULT CCodecs::Load() 420 { 421 #ifdef NEW_FOLDER_INTERFACE 422 InternalIcons.LoadIcons(g_hInstance); 423 #endif 424 425 Formats.Clear(); 426 #ifdef EXTERNAL_CODECS 427 Codecs.Clear(); 428 #endif 429 for (UInt32 i = 0; i < g_NumArcs; i++) 430 { 431 const CArcInfo &arc = *g_Arcs[i]; 432 CArcInfoEx item; 433 item.Name = arc.Name; 434 item.CreateInArchive = arc.CreateInArchive; 435 item.CreateOutArchive = arc.CreateOutArchive; 436 item.AddExts(arc.Ext, arc.AddExt); 437 item.UpdateEnabled = (arc.CreateOutArchive != 0); 438 item.KeepName = arc.KeepName; 439 440 #ifndef _SFX 441 SetBuffer(item.StartSignature, arc.Signature, arc.SignatureSize); 442 #endif 443 Formats.Add(item); 444 } 445 #ifdef EXTERNAL_CODECS 446 const CSysString baseFolder = GetBaseFolderPrefixFromRegistry(); 447 RINOK(LoadDll(baseFolder + kMainDll, false)); 448 RINOK(LoadDllsFromFolder(baseFolder + kCodecsFolderName TEXT(STRING_PATH_SEPARATOR))); 449 RINOK(LoadDllsFromFolder(baseFolder + kFormatsFolderName TEXT(STRING_PATH_SEPARATOR))); 450 #endif 451 return S_OK; 452 } 453 454 #ifndef _SFX 455 456 int CCodecs::FindFormatForArchiveName(const UString &arcPath) const 457 { 458 int slashPos1 = arcPath.ReverseFind(WCHAR_PATH_SEPARATOR); 459 int slashPos2 = arcPath.ReverseFind(L'.'); 460 int dotPos = arcPath.ReverseFind(L'.'); 461 if (dotPos < 0 || dotPos < slashPos1 || dotPos < slashPos2) 462 return -1; 463 UString ext = arcPath.Mid(dotPos + 1); 464 for (int i = 0; i < Formats.Size(); i++) 465 { 466 const CArcInfoEx &arc = Formats[i]; 467 if (!arc.UpdateEnabled) 468 continue; 469 if (arc.FindExtension(ext) >= 0) 470 return i; 471 } 472 return -1; 473 } 474 475 int CCodecs::FindFormatForExtension(const UString &ext) const 476 { 477 if (ext.IsEmpty()) 478 return -1; 479 for (int i = 0; i < Formats.Size(); i++) 480 if (Formats[i].FindExtension(ext) >= 0) 481 return i; 482 return -1; 483 } 484 485 int CCodecs::FindFormatForArchiveType(const UString &arcType) const 486 { 487 for (int i = 0; i < Formats.Size(); i++) 488 if (Formats[i].Name.CompareNoCase(arcType) == 0) 489 return i; 490 return -1; 491 } 492 493 bool CCodecs::FindFormatForArchiveType(const UString &arcType, CIntVector &formatIndices) const 494 { 495 formatIndices.Clear(); 496 for (int pos = 0; pos < arcType.Length();) 497 { 498 int pos2 = arcType.Find('.', pos); 499 if (pos2 < 0) 500 pos2 = arcType.Length(); 501 const UString name = arcType.Mid(pos, pos2 - pos); 502 int index = FindFormatForArchiveType(name); 503 if (index < 0 && name != L"*") 504 { 505 formatIndices.Clear(); 506 return false; 507 } 508 formatIndices.Add(index); 509 pos = pos2 + 1; 510 } 511 return true; 512 } 513 514 #endif 515 516 #ifdef EXTERNAL_CODECS 517 518 #ifdef EXPORT_CODECS 519 extern unsigned int g_NumCodecs; 520 STDAPI CreateCoder2(bool encode, UInt32 index, const GUID *iid, void **outObject); 521 STDAPI GetMethodProperty(UInt32 codecIndex, PROPID propID, PROPVARIANT *value); 522 // STDAPI GetNumberOfMethods(UInt32 *numCodecs); 523 #endif 524 525 STDMETHODIMP CCodecs::GetNumberOfMethods(UInt32 *numMethods) 526 { 527 *numMethods = 528 #ifdef EXPORT_CODECS 529 g_NumCodecs + 530 #endif 531 Codecs.Size(); 532 return S_OK; 533 } 534 535 STDMETHODIMP CCodecs::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) 536 { 537 #ifdef EXPORT_CODECS 538 if (index < g_NumCodecs) 539 return GetMethodProperty(index, propID, value); 540 #endif 541 542 const CDllCodecInfo &ci = Codecs[index 543 #ifdef EXPORT_CODECS 544 - g_NumCodecs 545 #endif 546 ]; 547 548 if (propID == NMethodPropID::kDecoderIsAssigned) 549 { 550 NWindows::NCOM::CPropVariant propVariant; 551 propVariant = ci.DecoderIsAssigned; 552 propVariant.Detach(value); 553 return S_OK; 554 } 555 if (propID == NMethodPropID::kEncoderIsAssigned) 556 { 557 NWindows::NCOM::CPropVariant propVariant; 558 propVariant = ci.EncoderIsAssigned; 559 propVariant.Detach(value); 560 return S_OK; 561 } 562 return Libs[ci.LibIndex].GetMethodProperty(ci.CodecIndex, propID, value); 563 } 564 565 STDMETHODIMP CCodecs::CreateDecoder(UInt32 index, const GUID *iid, void **coder) 566 { 567 #ifdef EXPORT_CODECS 568 if (index < g_NumCodecs) 569 return CreateCoder2(false, index, iid, coder); 570 #endif 571 const CDllCodecInfo &ci = Codecs[index 572 #ifdef EXPORT_CODECS 573 - g_NumCodecs 574 #endif 575 ]; 576 if (ci.DecoderIsAssigned) 577 return Libs[ci.LibIndex].CreateObject(&ci.Decoder, iid, (void **)coder); 578 return S_OK; 579 } 580 581 STDMETHODIMP CCodecs::CreateEncoder(UInt32 index, const GUID *iid, void **coder) 582 { 583 #ifdef EXPORT_CODECS 584 if (index < g_NumCodecs) 585 return CreateCoder2(true, index, iid, coder); 586 #endif 587 const CDllCodecInfo &ci = Codecs[index 588 #ifdef EXPORT_CODECS 589 - g_NumCodecs 590 #endif 591 ]; 592 if (ci.EncoderIsAssigned) 593 return Libs[ci.LibIndex].CreateObject(&ci.Encoder, iid, (void **)coder); 594 return S_OK; 595 } 596 597 HRESULT CCodecs::CreateCoder(const UString &name, bool encode, CMyComPtr<ICompressCoder> &coder) const 598 { 599 for (int i = 0; i < Codecs.Size(); i++) 600 { 601 const CDllCodecInfo &codec = Codecs[i]; 602 if (encode && !codec.EncoderIsAssigned || !encode && !codec.DecoderIsAssigned) 603 continue; 604 const CCodecLib &lib = Libs[codec.LibIndex]; 605 UString res; 606 NWindows::NCOM::CPropVariant prop; 607 RINOK(lib.GetMethodProperty(codec.CodecIndex, NMethodPropID::kName, &prop)); 608 if (prop.vt == VT_BSTR) 609 res = prop.bstrVal; 610 else if (prop.vt != VT_EMPTY) 611 continue; 612 if (name.CompareNoCase(res) == 0) 613 return lib.CreateObject(encode ? &codec.Encoder : &codec.Decoder, &IID_ICompressCoder, (void **)&coder); 614 } 615 return CLASS_E_CLASSNOTAVAILABLE; 616 } 617 618 int CCodecs::GetCodecLibIndex(UInt32 index) 619 { 620 #ifdef EXPORT_CODECS 621 if (index < g_NumCodecs) 622 return -1; 623 #endif 624 #ifdef EXTERNAL_CODECS 625 const CDllCodecInfo &ci = Codecs[index 626 #ifdef EXPORT_CODECS 627 - g_NumCodecs 628 #endif 629 ]; 630 return ci.LibIndex; 631 #else 632 return -1; 633 #endif 634 } 635 636 bool CCodecs::GetCodecEncoderIsAssigned(UInt32 index) 637 { 638 #ifdef EXPORT_CODECS 639 if (index < g_NumCodecs) 640 { 641 NWindows::NCOM::CPropVariant prop; 642 if (GetProperty(index, NMethodPropID::kEncoder, &prop) == S_OK) 643 if (prop.vt != VT_EMPTY) 644 return true; 645 return false; 646 } 647 #endif 648 #ifdef EXTERNAL_CODECS 649 const CDllCodecInfo &ci = Codecs[index 650 #ifdef EXPORT_CODECS 651 - g_NumCodecs 652 #endif 653 ]; 654 return ci.EncoderIsAssigned; 655 #else 656 return false; 657 #endif 658 } 659 660 HRESULT CCodecs::GetCodecId(UInt32 index, UInt64 &id) 661 { 662 UString s; 663 NWindows::NCOM::CPropVariant prop; 664 RINOK(GetProperty(index, NMethodPropID::kID, &prop)); 665 if (prop.vt != VT_UI8) 666 return E_INVALIDARG; 667 id = prop.uhVal.QuadPart; 668 return S_OK; 669 } 670 671 UString CCodecs::GetCodecName(UInt32 index) 672 { 673 UString s; 674 NWindows::NCOM::CPropVariant prop; 675 if (GetProperty(index, NMethodPropID::kName, &prop) == S_OK) 676 if (prop.vt == VT_BSTR) 677 s = prop.bstrVal; 678 return s; 679 } 680 681 #endif 682