1 /** @file 2 Framework to UEFI 2.1 Setup Browser Thunk. The file consume EFI_FORM_BROWSER2_PROTOCOL 3 to produce a EFI_FORM_BROWSER_PROTOCOL. 4 5 Copyright (c) 2008 - 2011, Intel Corporation. All rights reserved.<BR> 6 This program and the accompanying materials 7 are licensed and made available under the terms and conditions of the BSD License 8 which accompanies this distribution. The full text of the license may be found at 9 http://opensource.org/licenses/bsd-license.php 10 11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, 12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 13 14 **/ 15 16 #include "HiiDatabase.h" 17 #include "SetupBrowser.h" 18 19 EFI_HII_HANDLE gStringPackHandle = NULL; 20 BOOLEAN mFrontPageDisplayed = FALSE; 21 // 22 // 106F3545-B788-4cb5-9D2A-CE0CDB208DF5 23 // 24 EFI_GUID gEfiHiiThunkProducerGuid = { 0x106f3545, 0xb788, 0x4cb5, { 0x9d, 0x2a, 0xce, 0xc, 0xdb, 0x20, 0x8d, 0xf5 } }; 25 26 27 /** 28 Get string by string id from HII Interface 29 30 31 @param Id String ID. 32 33 @retval CHAR16 * String from ID. 34 @retval NULL If error occurs. 35 36 **/ 37 CHAR16 * 38 GetStringById ( 39 IN EFI_STRING_ID Id 40 ) 41 { 42 return HiiGetString (gStringPackHandle, Id, NULL); 43 } 44 45 /** 46 47 Show progress bar with title above it. It only works in Graphics mode. 48 49 50 @param TitleForeground Foreground color for Title. 51 @param TitleBackground Background color for Title. 52 @param Title Title above progress bar. 53 @param ProgressColor Progress bar color. 54 @param Progress Progress (0-100) 55 @param PreviousValue The previous value of the progress. 56 57 @retval EFI_STATUS Success update the progress bar 58 59 **/ 60 EFI_STATUS 61 PlatformBdsShowProgress ( 62 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL TitleForeground, 63 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL TitleBackground, 64 IN CHAR16 *Title, 65 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL ProgressColor, 66 IN UINTN Progress, 67 IN UINTN PreviousValue 68 ) 69 { 70 EFI_STATUS Status; 71 EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; 72 EFI_UGA_DRAW_PROTOCOL *UgaDraw; 73 UINT32 SizeOfX; 74 UINT32 SizeOfY; 75 UINT32 ColorDepth; 76 UINT32 RefreshRate; 77 EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color; 78 UINTN BlockHeight; 79 UINTN BlockWidth; 80 UINTN BlockNum; 81 UINTN PosX; 82 UINTN PosY; 83 UINTN Index; 84 85 if (Progress > 100) { 86 return EFI_INVALID_PARAMETER; 87 } 88 89 UgaDraw = NULL; 90 Status = gBS->HandleProtocol ( 91 gST->ConsoleOutHandle, 92 &gEfiGraphicsOutputProtocolGuid, 93 (VOID **) &GraphicsOutput 94 ); 95 if (EFI_ERROR (Status)) { 96 GraphicsOutput = NULL; 97 98 Status = gBS->HandleProtocol ( 99 gST->ConsoleOutHandle, 100 &gEfiUgaDrawProtocolGuid, 101 (VOID **) &UgaDraw 102 ); 103 } 104 if (EFI_ERROR (Status) || (GraphicsOutput == NULL && UgaDraw == NULL)) { 105 return EFI_UNSUPPORTED; 106 } 107 108 SizeOfX = 0; 109 SizeOfY = 0; 110 if (GraphicsOutput != NULL) { 111 SizeOfX = GraphicsOutput->Mode->Info->HorizontalResolution; 112 SizeOfY = GraphicsOutput->Mode->Info->VerticalResolution; 113 } else { 114 Status = UgaDraw->GetMode ( 115 UgaDraw, 116 &SizeOfX, 117 &SizeOfY, 118 &ColorDepth, 119 &RefreshRate 120 ); 121 if (EFI_ERROR (Status)) { 122 return EFI_UNSUPPORTED; 123 } 124 } 125 126 BlockWidth = SizeOfX / 100; 127 BlockHeight = SizeOfY / 50; 128 129 BlockNum = Progress; 130 131 PosX = 0; 132 PosY = SizeOfY * 48 / 50; 133 134 if (BlockNum == 0) { 135 // 136 // Clear progress area 137 // 138 SetMem (&Color, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), 0x0); 139 140 if (GraphicsOutput != NULL) { 141 Status = GraphicsOutput->Blt ( 142 GraphicsOutput, 143 &Color, 144 EfiBltVideoFill, 145 0, 146 0, 147 0, 148 PosY - EFI_GLYPH_HEIGHT - 1, 149 SizeOfX, 150 SizeOfY - (PosY - EFI_GLYPH_HEIGHT - 1), 151 SizeOfX * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) 152 ); 153 } else { 154 Status = UgaDraw->Blt ( 155 UgaDraw, 156 (EFI_UGA_PIXEL *) &Color, 157 EfiUgaVideoFill, 158 0, 159 0, 160 0, 161 PosY - EFI_GLYPH_HEIGHT - 1, 162 SizeOfX, 163 SizeOfY - (PosY - EFI_GLYPH_HEIGHT - 1), 164 SizeOfX * sizeof (EFI_UGA_PIXEL) 165 ); 166 } 167 } 168 // 169 // Show progress by drawing blocks 170 // 171 for (Index = PreviousValue; Index < BlockNum; Index++) { 172 PosX = Index * BlockWidth; 173 if (GraphicsOutput != NULL) { 174 Status = GraphicsOutput->Blt ( 175 GraphicsOutput, 176 &ProgressColor, 177 EfiBltVideoFill, 178 0, 179 0, 180 PosX, 181 PosY, 182 BlockWidth - 1, 183 BlockHeight, 184 (BlockWidth) * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) 185 ); 186 } else { 187 Status = UgaDraw->Blt ( 188 UgaDraw, 189 (EFI_UGA_PIXEL *) &ProgressColor, 190 EfiUgaVideoFill, 191 0, 192 0, 193 PosX, 194 PosY, 195 BlockWidth - 1, 196 BlockHeight, 197 (BlockWidth) * sizeof (EFI_UGA_PIXEL) 198 ); 199 } 200 } 201 202 PrintXY ( 203 (SizeOfX - StrLen (Title) * EFI_GLYPH_WIDTH) / 2, 204 PosY - EFI_GLYPH_HEIGHT - 1, 205 &TitleForeground, 206 &TitleBackground, 207 Title 208 ); 209 210 return EFI_SUCCESS; 211 } 212 213 /** 214 Function waits for a given event to fire, or for an optional timeout to expire. 215 216 217 @param Event The event to wait for 218 219 @param Timeout An optional timeout value in 100 ns units. 220 221 @retval EFI_SUCCESS Event fired before Timeout expired. 222 @retval EFI_TIME_OUT Timout expired before Event fired.. 223 224 **/ 225 EFI_STATUS 226 WaitForSingleEvent ( 227 IN EFI_EVENT Event, 228 IN UINT64 Timeout OPTIONAL 229 ) 230 { 231 EFI_STATUS Status; 232 UINTN Index; 233 EFI_EVENT TimerEvent; 234 EFI_EVENT WaitList[2]; 235 236 if (Timeout != 0) { 237 // 238 // Create a timer event 239 // 240 Status = gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimerEvent); 241 if (!EFI_ERROR (Status)) { 242 // 243 // Set the timer event 244 // 245 gBS->SetTimer ( 246 TimerEvent, 247 TimerRelative, 248 Timeout 249 ); 250 251 // 252 // Wait for the original event or the timer 253 // 254 WaitList[0] = Event; 255 WaitList[1] = TimerEvent; 256 Status = gBS->WaitForEvent (2, WaitList, &Index); 257 gBS->CloseEvent (TimerEvent); 258 259 // 260 // If the timer expired, change the return to timed out 261 // 262 if (!EFI_ERROR (Status) && Index == 1) { 263 Status = EFI_TIMEOUT; 264 } 265 } 266 } else { 267 // 268 // No timeout... just wait on the event 269 // 270 Status = gBS->WaitForEvent (1, &Event, &Index); 271 ASSERT (!EFI_ERROR (Status)); 272 ASSERT (Index == 0); 273 } 274 275 return Status; 276 } 277 278 /** 279 Function show progress bar to wait for user input. 280 281 282 @param TimeoutDefault - The fault time out value before the system 283 continue to boot. 284 285 @retval EFI_SUCCESS User pressed some key except "Enter" 286 @retval EFI_TIME_OUT Timout expired or user press "Enter" 287 288 **/ 289 EFI_STATUS 290 ShowProgress ( 291 IN UINT16 TimeoutDefault 292 ) 293 { 294 EFI_STATUS Status; 295 CHAR16 *TmpStr; 296 EFI_GRAPHICS_OUTPUT_BLT_PIXEL Foreground; 297 EFI_GRAPHICS_OUTPUT_BLT_PIXEL Background; 298 EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color; 299 EFI_INPUT_KEY Key; 300 UINT16 TimeoutRemain; 301 302 if (TimeoutDefault == 0) { 303 return EFI_TIMEOUT; 304 } 305 306 DEBUG ((EFI_D_INFO, "\n\nStart showing progress bar... Press any key to stop it! ...Zzz....\n")); 307 308 SetMem (&Foreground, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), 0xff); 309 SetMem (&Background, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), 0x0); 310 SetMem (&Color, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), 0xff); 311 312 // 313 // Clear the progress status bar first 314 // 315 TmpStr = GetStringById (STRING_TOKEN (STR_START_BOOT_OPTION)); 316 if (TmpStr != NULL) { 317 PlatformBdsShowProgress (Foreground, Background, TmpStr, Color, 0, 0); 318 } 319 320 TimeoutRemain = TimeoutDefault; 321 while (TimeoutRemain != 0) { 322 DEBUG ((EFI_D_INFO, "Showing progress bar...Remaining %d second!\n", TimeoutRemain)); 323 324 Status = WaitForSingleEvent (gST->ConIn->WaitForKey, ONE_SECOND); 325 if (Status != EFI_TIMEOUT) { 326 break; 327 } 328 TimeoutRemain--; 329 330 // 331 // Show progress 332 // 333 if (TmpStr != NULL) { 334 PlatformBdsShowProgress ( 335 Foreground, 336 Background, 337 TmpStr, 338 Color, 339 ((TimeoutDefault - TimeoutRemain) * 100 / TimeoutDefault), 340 0 341 ); 342 } 343 } 344 gBS->FreePool (TmpStr); 345 346 // 347 // Timeout expired 348 // 349 if (TimeoutRemain == 0) { 350 return EFI_TIMEOUT; 351 } 352 353 // 354 // User pressed some key 355 // 356 Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key); 357 if (EFI_ERROR (Status)) { 358 return Status; 359 } 360 361 if (Key.UnicodeChar == CHAR_CARRIAGE_RETURN) { 362 // 363 // User pressed enter, equivalent to select "continue" 364 // 365 return EFI_TIMEOUT; 366 } 367 368 return EFI_SUCCESS; 369 } 370 371 /** 372 Return the default value for system Timeout variable. 373 374 @return Timeout value. 375 376 **/ 377 UINT16 378 EFIAPI 379 GetTimeout ( 380 VOID 381 ) 382 { 383 return PcdGet16 (PcdPlatformBootTimeOut); 384 } 385 386 387 /** 388 This is the Framework Setup Browser interface which displays a FormSet. 389 390 @param This The EFI_FORM_BROWSER_PROTOCOL context. 391 @param UseDatabase TRUE if the FormSet is from HII database. The Thunk implementation 392 only support UseDatabase is TRUE. 393 @param Handle The Handle buffer. 394 @param HandleCount The number of Handle in the Handle Buffer. It must be 1 for this implementation. 395 @param Packet The pointer to data buffer containing IFR and String package. Not supported. 396 @param CallbackHandle Not supported. 397 @param NvMapOverride The buffer is used only when there is no NV variable to define the 398 current settings and the caller needs to provide to the browser the 399 current settings for the the "fake" NV variable. If used, no saving of 400 an NV variable is possbile. This parameter is also ignored if Handle is NULL. 401 @param ScreenDimensions 402 Allows the browser to be called so that it occupies a portion of the physical 403 screen instead of dynamically determining the screen dimensions. 404 @param ResetRequired This BOOLEAN value denotes whether a reset is required based on the data that 405 might have been changed. The ResetRequired parameter is primarily applicable 406 for configuration applications, and is an optional parameter. 407 408 @retval EFI_SUCCESS If the Formset is displayed correctly. 409 @retval EFI_UNSUPPORTED If UseDatabase is FALSE or HandleCount is not 1. 410 @retval EFI_INVALID_PARAMETER If the *Handle passed in is not found in the database. 411 **/ 412 EFI_STATUS 413 EFIAPI 414 ThunkSendForm ( 415 IN EFI_FORM_BROWSER_PROTOCOL *This, 416 IN BOOLEAN UseDatabase, 417 IN FRAMEWORK_EFI_HII_HANDLE *Handle, 418 IN UINTN HandleCount, 419 IN EFI_IFR_PACKET *Packet, OPTIONAL 420 IN EFI_HANDLE CallbackHandle, OPTIONAL 421 IN UINT8 *NvMapOverride, OPTIONAL 422 IN FRAMEWORK_EFI_SCREEN_DESCRIPTOR *ScreenDimensions, OPTIONAL 423 OUT BOOLEAN *ResetRequired OPTIONAL 424 ) 425 { 426 EFI_STATUS Status; 427 EFI_BROWSER_ACTION_REQUEST ActionRequest; 428 HII_THUNK_CONTEXT *ThunkContext; 429 HII_THUNK_PRIVATE_DATA *Private; 430 EFI_FORMBROWSER_THUNK_PRIVATE_DATA *BrowserPrivate; 431 432 if (!UseDatabase) { 433 // 434 // ThunkSendForm only support displays forms registered into the HII database. 435 // 436 return EFI_UNSUPPORTED; 437 } 438 439 if (HandleCount != 1 ) { 440 return EFI_UNSUPPORTED; 441 } 442 443 BrowserPrivate = EFI_FORMBROWSER_THUNK_PRIVATE_DATA_FROM_THIS (This); 444 Private = BrowserPrivate->ThunkPrivate; 445 446 ThunkContext = FwHiiHandleToThunkContext (Private, *Handle); 447 if (ThunkContext == NULL) { 448 return EFI_INVALID_PARAMETER; 449 } 450 451 // 452 // Following UEFI spec to do auto booting after a time-out. This feature is implemented 453 // in Framework Setup Browser and moved to MdeModulePkg/Universal/BdsDxe. The auto booting is 454 // moved here in HII Thunk module. 455 // 456 if (CompareGuid (&gFrameworkBdsFrontPageFormsetGuid, &ThunkContext->FormSet->Guid) && !mFrontPageDisplayed) { 457 // 458 // Send form is called before entering the 459 // 460 mFrontPageDisplayed = TRUE; 461 Status = ShowProgress (GetTimeout ()); 462 463 if (EFI_ERROR (Status)) { 464 return Status; 465 } 466 } 467 468 if (NvMapOverride != NULL) { 469 ThunkContext->NvMapOverride = NvMapOverride; 470 } 471 472 Status = mFormBrowser2Protocol->SendForm ( 473 mFormBrowser2Protocol, 474 &ThunkContext->UefiHiiHandle, 475 1, 476 NULL, 477 0, 478 (EFI_SCREEN_DESCRIPTOR *) ScreenDimensions, 479 &ActionRequest 480 ); 481 482 if (ActionRequest == EFI_BROWSER_ACTION_REQUEST_RESET) { 483 *ResetRequired = TRUE; 484 } 485 486 return Status; 487 } 488 489 /** 490 491 Rountine used to display a generic dialog interface and return 492 the Key or Input from user input. 493 494 @param LinesNumber The number of lines for the dialog box. 495 @param HotKey Defines if a single character is parsed (TRUE) and returned in KeyValue 496 or if a string is returned in StringBuffer. 497 @param MaximumStringSize The maximum size in bytes of a typed-in string. 498 @param StringBuffer On return contains the typed-in string if HotKey is FALSE. 499 @param Key The EFI_INPUT_KEY value returned if HotKey is TRUE. 500 @param FirstString The pointer to the first string in the list of strings 501 that comprise the dialog box. 502 @param ... A series of NumberOfLines text strings that will be used 503 to construct the dialog box. 504 @retval EFI_SUCCESS The dialog is created successfully and user interaction was received. 505 @retval EFI_DEVICE_ERROR The user typed in an ESC. 506 @retval EFI_INVALID_PARAMETER One of the parameters was invalid.(StringBuffer == NULL && HotKey == FALSE). 507 **/ 508 EFI_STATUS 509 EFIAPI 510 ThunkCreatePopUp ( 511 IN UINTN LinesNumber, 512 IN BOOLEAN HotKey, 513 IN UINTN MaximumStringSize, 514 OUT CHAR16 *StringBuffer, 515 OUT EFI_INPUT_KEY *Key, 516 IN CHAR16 *FirstString, 517 ... 518 ) 519 { 520 VA_LIST Args; 521 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; 522 EFI_SIMPLE_TEXT_OUTPUT_MODE SavedConsoleMode; 523 UINTN Columns; 524 UINTN Rows; 525 UINTN Column; 526 UINTN Row; 527 UINTN NumberOfLines; 528 UINTN MaxLength; 529 CHAR16 *String; 530 UINTN Length; 531 CHAR16 *Line; 532 UINTN EventIndex; 533 534 if (!HotKey) { 535 return EFI_UNSUPPORTED; 536 } 537 538 if (MaximumStringSize == 0) { 539 // 540 // Blank strint to output 541 // 542 return EFI_INVALID_PARAMETER; 543 } 544 545 // 546 // Determine the length of the longest line in the popup and the the total 547 // number of lines in the popup 548 // 549 MaxLength = StrLen (FirstString); 550 NumberOfLines = 1; 551 VA_START (Args, FirstString); 552 while ((String = VA_ARG (Args, CHAR16 *)) != NULL) { 553 MaxLength = MAX (MaxLength, StrLen (String)); 554 NumberOfLines++; 555 } 556 VA_END (Args); 557 558 // 559 // If the total number of lines in the popup is not same to the input NumberOfLines 560 // the parameter is not valid. Not check. 561 // 562 // if (NumberOfLines != LinesNumber) { 563 // return EFI_INVALID_PARAMETER; 564 // } 565 566 // 567 // If the maximum length of all the strings is not same to the input MaximumStringSize 568 // the parameter is not valid. Not check. 569 // 570 // if (MaxLength != MaximumStringSize) { 571 // return EFI_INVALID_PARAMETER; 572 // } 573 574 // 575 // Cache a pointer to the Simple Text Output Protocol in the EFI System Table 576 // 577 ConOut = gST->ConOut; 578 579 // 580 // Save the current console cursor position and attributes 581 // 582 CopyMem (&SavedConsoleMode, ConOut->Mode, sizeof (SavedConsoleMode)); 583 584 // 585 // Retrieve the number of columns and rows in the current console mode 586 // 587 ConOut->QueryMode (ConOut, SavedConsoleMode.Mode, &Columns, &Rows); 588 589 // 590 // Disable cursor and set the foreground and background colors specified by Attribute 591 // 592 ConOut->EnableCursor (ConOut, FALSE); 593 ConOut->SetAttribute (ConOut, EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE); 594 595 // 596 // Limit NumberOfLines to height of the screen minus 3 rows for the box itself 597 // 598 NumberOfLines = MIN (NumberOfLines, Rows - 3); 599 600 // 601 // Limit MaxLength to width of the screen minus 2 columns for the box itself 602 // 603 MaxLength = MIN (MaxLength, Columns - 2); 604 605 // 606 // Compute the starting row and starting column for the popup 607 // 608 Row = (Rows - (NumberOfLines + 3)) / 2; 609 Column = (Columns - (MaxLength + 2)) / 2; 610 611 // 612 // Allocate a buffer for a single line of the popup with borders and a Null-terminator 613 // 614 Line = AllocateZeroPool ((MaxLength + 3) * sizeof (CHAR16)); 615 ASSERT (Line != NULL); 616 617 // 618 // Draw top of popup box 619 // 620 SetMem16 (Line, (MaxLength + 2) * 2, BOXDRAW_HORIZONTAL); 621 Line[0] = BOXDRAW_DOWN_RIGHT; 622 Line[MaxLength + 1] = BOXDRAW_DOWN_LEFT; 623 Line[MaxLength + 2] = L'\0'; 624 ConOut->SetCursorPosition (ConOut, Column, Row++); 625 ConOut->OutputString (ConOut, Line); 626 627 // 628 // Draw middle of the popup with strings 629 // 630 VA_START (Args, FirstString); 631 String = FirstString; 632 while ((String != NULL) && (NumberOfLines > 0)) { 633 Length = StrLen (String); 634 SetMem16 (Line, (MaxLength + 2) * 2, L' '); 635 if (Length <= MaxLength) { 636 // 637 // Length <= MaxLength 638 // 639 CopyMem (Line + 1 + (MaxLength - Length) / 2, String , Length * sizeof (CHAR16)); 640 } else { 641 // 642 // Length > MaxLength 643 // 644 CopyMem (Line + 1, String + (Length - MaxLength) / 2 , MaxLength * sizeof (CHAR16)); 645 } 646 Line[0] = BOXDRAW_VERTICAL; 647 Line[MaxLength + 1] = BOXDRAW_VERTICAL; 648 Line[MaxLength + 2] = L'\0'; 649 ConOut->SetCursorPosition (ConOut, Column, Row++); 650 ConOut->OutputString (ConOut, Line); 651 String = VA_ARG (Args, CHAR16 *); 652 NumberOfLines--; 653 } 654 VA_END (Args); 655 656 // 657 // Draw bottom of popup box 658 // 659 SetMem16 (Line, (MaxLength + 2) * 2, BOXDRAW_HORIZONTAL); 660 Line[0] = BOXDRAW_UP_RIGHT; 661 Line[MaxLength + 1] = BOXDRAW_UP_LEFT; 662 Line[MaxLength + 2] = L'\0'; 663 ConOut->SetCursorPosition (ConOut, Column, Row++); 664 ConOut->OutputString (ConOut, Line); 665 666 // 667 // Free the allocated line buffer 668 // 669 FreePool (Line); 670 671 // 672 // Restore the cursor visibility, position, and attributes 673 // 674 ConOut->EnableCursor (ConOut, SavedConsoleMode.CursorVisible); 675 ConOut->SetCursorPosition (ConOut, SavedConsoleMode.CursorColumn, SavedConsoleMode.CursorRow); 676 ConOut->SetAttribute (ConOut, SavedConsoleMode.Attribute); 677 678 // 679 // Wait for a keystroke 680 // 681 if (Key != NULL) { 682 gBS->WaitForEvent (1, &gST->ConIn->WaitForKey, &EventIndex); 683 gST->ConIn->ReadKeyStroke (gST->ConIn, Key); 684 } 685 686 return EFI_SUCCESS; 687 } 688 689 /** 690 691 Initialize string packages in HII database. 692 693 **/ 694 VOID 695 InitSetBrowserStrings ( 696 VOID 697 ) 698 { 699 // 700 // Initialize strings to HII database 701 // 702 gStringPackHandle = HiiAddPackages ( 703 &gEfiHiiThunkProducerGuid, 704 NULL, 705 STRING_ARRAY_NAME, 706 NULL 707 ); 708 ASSERT (gStringPackHandle != NULL); 709 } 710