1{ 2 This file is part of the Free Pascal run time library. 3 Copyright (c) 2008 by Giulio Bernardi 4 5 Resource support as external files 6 7 See the file COPYING.FPC, included in this distribution, 8 for details about the copyright. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 14 **********************************************************************} 15{ 16 This file implements two kinds of external resource support: 17 - one for systems that support the mmap call (usually unix-like oses) 18 - one fallback implementation based on pascal files and GetMem/FreeMem 19 20 Be sure to define EXTRES_MMAP or EXTRES_GENERIC before including this file! 21} 22{$IF defined(EXTRES_MMAP) and defined(EXTRES_GENERIC)} 23{$FATAL EXTRES_MMAP and EXTRES_GENERIC can't be defined together} 24{$ENDIF} 25 26{$IF (not defined(EXTRES_MMAP)) and (not defined(EXTRES_GENERIC))} 27{$FATAL EXTRES_MMAP or EXTRES_GENERIC must be defined} 28{$ENDIF} 29 30const 31 FPCRES_MAGIC = 'FPCRES'; 32 FPCRES_VERSION = 1; 33 {$IFDEF ENDIAN_BIG} 34 FPCRES_ENDIAN = 1; 35 {$ENDIF} 36 {$IFDEF ENDIAN_LITTLE} 37 FPCRES_ENDIAN = 2; 38 {$ENDIF} 39 FPCRES_EXT = '.fpcres'; 40 41type 42 TExtHeader = packed record 43 magic : array[0..5] of char;//'FPCRES' 44 version : byte; //EXT_CURRENT_VERSION 45 endianess : byte; //EXT_ENDIAN_BIG or EXT_ENDIAN_LITTLE 46 count : longword; //resource count 47 nodesize : longword; //size of header (up to string table, excluded) 48 hdrsize : longword; //size of header (up to string table, included) 49 reserved1 : longword; 50 reserved2 : longword; 51 reserved3 : longword; 52 end; 53 PExtHeader = ^TExtHeader; 54 55 TResInfoNode = packed record 56 nameid : longword; //name offset / integer ID / languageID 57 ncounthandle : longword; //named sub-entries count/resource handle 58 idcountsize : longword; //id sub-entries count / resource size 59 subptr : longword; //first sub-entry offset 60 end; 61 PResInfoNode = ^TResInfoNode; 62 63 {$IFDEF EXTRES_GENERIC} 64 TResHandle = record 65 info : PResInfoNode; 66 ptr : Pointer; 67 end; 68 PResHandle = ^TResHandle; 69 {$ENDIF} 70 71var ResHeader : PExtHeader = nil; 72 usedhandles : longword = 0; 73 {$IFDEF EXTRES_MMAP} 74 fd : integer; 75 fd_size : longword; 76 reshandles : PPointer = nil; 77 {$ENDIF} 78 {$IFDEF EXTRES_GENERIC} 79 fd : file; 80 reshandles : PResHandle = nil; 81 {$ENDIF} 82 83(***************************************************************************** 84 Private Helper Functions 85*****************************************************************************) 86 87//resource functions are case insensitive... copied from genstr.inc 88function ResStrIComp(Str1, Str2 : PChar): SizeInt; 89var 90 counter: SizeInt; 91 c1, c2: char; 92begin 93 counter := 0; 94 c1 := upcase(str1[counter]); 95 c2 := upcase(str2[counter]); 96 while c1 = c2 do 97 begin 98 if (c1 = #0) or (c2 = #0) then break; 99 inc(counter); 100 c1 := upcase(str1[counter]); 101 c2 := upcase(str2[counter]); 102 end; 103 ResStrIComp := ord(c1) - ord(c2); 104end; 105 106{!fixme!} 107//function InternalIsIntResource(aStr : pchar; out aInt : PtrUint) : boolean; 108function InternalIsIntResource(aStr : pchar; var aInt : PtrUint) : boolean; 109var i : integer; 110 s : shortstring; 111 code : word; 112begin 113 InternalIsIntResource:=((PtrUInt(aStr) shr 16)=0); 114 if InternalIsIntResource then aInt:=PtrUInt(aStr) 115 else 116 begin 117 //a string like #number specifies an integer id 118 if aStr[0]='#' then 119 begin 120 i:=1; 121 while aStr[i]<>#0 do 122 inc(i); 123 if i>256 then i:=256; 124 s[0]:=chr(i-1); 125 Move(aStr[1],s[1],i-1); 126 Val(s,aInt,code); 127 InternalIsIntResource:=code=0; 128 end; 129 end; 130end; 131 132function GetResInfoPtr(const offset : longword) : PResInfoNode; inline; 133begin 134 GetResInfoPtr:=PResInfoNode(PtrUInt(ResHeader)+offset); 135end; 136 137function GetPchar(const offset : longword) : Pchar; inline; 138begin 139 GetPchar:=Pchar(PtrUInt(ResHeader)+offset); 140end; 141 142function GetPtr(const offset : longword) : Pointer; inline; 143begin 144 GetPtr:=Pointer(PtrUInt(ResHeader)+offset); 145end; 146 147procedure FixResEndian; 148var ptr : plongword; 149 blockend : plongword; 150begin 151 //all info nodes reside in a contiguos block of memory. 152 //they are all 16 bytes long and made by longwords 153 //so, simply swap each longword in the block 154 ptr:=GetPtr(sizeof(TExtHeader)); 155 blockend:=GetPtr(ResHeader^.nodesize); 156 while ptr<blockend do 157 begin 158 ptr^:=SwapEndian(ptr^); 159 inc(ptr); 160 end; 161end; 162 163function GetExtResPath : pchar; 164var len, i : integer; 165 pathstr : shortstring; 166begin 167 pathstr:=paramstr(0); 168 len:=byte(pathstr[0]); 169 i:=len; 170 //writeln('exe name is ',pathstr); 171 //find position of extension 172 while (i>0) and (not (pathstr[i] in ['.',DirectorySeparator])) do 173 dec(i); 174 if (i>0) and (pathstr[i]='.') then dec(i) 175 else i:=len; 176 pathstr[0]:=Chr(i); 177 pathstr:=pathstr+FPCRES_EXT; 178 len:=byte(pathstr[0]); 179 GetExtResPath:=GetMem(len+1); 180 Move(pathstr[1],GetExtResPath[0],len); 181 GetExtResPath[len]:=#0; 182 //writeln('Resource file is ',GetExtResPath); 183end; 184 185function BinSearchStr(arr : PResInfoNode; query : pchar; left, right : integer) 186: PResInfoNode; 187var pivot, res : integer; 188 resstr : pchar; 189begin 190 BinSearchStr:=nil; 191 while left<=right do 192 begin 193 pivot:=(left+right) div 2; 194 resstr:=GetPchar(arr[pivot].nameid); 195 res:=ResStrIComp(resstr,query); 196 if res<0 then left:=pivot+1 197 else if res>0 then right:=pivot-1 198 else 199 begin 200 BinSearchStr:=@arr[pivot]; 201 exit; 202 end; 203 end; 204end; 205 206function BinSearchInt(arr : PResInfoNode; query : pchar; left, right : integer) 207: PResInfoNode; 208var pivot : integer; 209begin 210 BinSearchInt:=nil; 211 while left<=right do 212 begin 213 pivot:=(left+right) div 2; 214 if arr[pivot].nameid<PtrUInt(query) then left:=pivot+1 215 else if arr[pivot].nameid>PtrUInt(query) then right:=pivot-1 216 else 217 begin 218 BinSearchInt:=@arr[pivot]; 219 exit; 220 end; 221 end; 222end; 223 224function BinSearchRes(root : PResInfoNode; aDesc : PChar) : PResInfoNode; 225var aID : PtrUint; 226begin 227 if InternalIsIntResource(aDesc,aID) then 228 BinSearchRes:=BinSearchInt(GetResInfoPtr(root^.subptr),PChar(aID), 229 root^.ncounthandle,root^.ncounthandle+root^.idcountsize-1) 230 else 231 BinSearchRes:=BinSearchStr(GetResInfoPtr(root^.subptr),aDesc,0, 232 root^.ncounthandle-1); 233end; 234 235//Returns a pointer to a name node. 236function InternalFindResource(ResourceName, ResourceType: PChar): 237 PResInfoNode; 238begin 239 InternalFindResource:=nil; 240 if ResHeader=nil then exit; 241 InternalFindResource:=GetResInfoPtr(sizeof(TExtHeader)); 242 243 InternalFindResource:=BinSearchRes(InternalFindResource,ResourceType); 244 if InternalFindResource<>nil then 245 InternalFindResource:=BinSearchRes(InternalFindResource,ResourceName); 246end; 247 248function FindSubLanguage(aPtr : PResInfoNode; aLangID : word; aMask: word) : PResInfoNode; 249var arr : PResInfoNode; 250 i : longword; 251begin 252 FindSubLanguage:=nil; 253 arr:=GetResInfoPtr(aPtr^.subptr); 254 i:=0; 255 while i<aPtr^.idcountsize do 256 begin 257 if (PtrUInt(arr[i].nameid) and aMask)=(aLangID and aMask) then 258 begin 259 FindSubLanguage:=@arr[i]; 260 exit; 261 end; 262 inc(i); 263 end; 264end; 265 266{$IFDEF EXTRES_MMAP} 267procedure InitResources; 268const 269 PROT_READ = 1; 270 PROT_WRITE = 2; 271var respath : pchar; 272 fdstat : stat; 273begin 274 respath:=GetExtResPath; 275// writeln('respath ',respath); 276 fd:=FpOpen(respath,O_RDONLY,0); 277// writeln('fpopen returned ',fd); 278 FreeMem(respath); 279 if fd=-1 then exit; 280 if FpFStat(fd,fdstat)<>0 then 281 begin 282// writeln('fpfstat failed'); 283 FpClose(fd); 284 exit; 285 end; 286// writeln('fpfstat suceeded'); 287 fd_size:=fdstat.st_size; 288 ResHeader:=PExtHeader(Fpmmap(nil,fd_size,PROT_READ or PROT_WRITE, 289 MAP_PRIVATE,fd,0)); 290// writeln('fpmmap returned ',PtrInt(ResHeader)); 291 if PtrInt(ResHeader)=-1 then 292 begin 293 FpClose(fd); 294 exit; 295 end; 296 if (ResHeader^.magic<>FPCRES_MAGIC) or 297 (ResHeader^.version<>fpcres_version) then 298 begin 299 FpClose(fd); 300 exit; 301 end; 302// writeln('magic ok'); 303 if ResHeader^.endianess<>FPCRES_ENDIAN then 304 begin 305 ResHeader^.count:=SwapEndian(ResHeader^.count); 306 ResHeader^.nodesize:=SwapEndian(ResHeader^.nodesize); 307 ResHeader^.hdrsize:=SwapEndian(ResHeader^.hdrsize); 308 FixResEndian; 309 end; 310 reshandles:=GetMem(sizeof(Pointer)*ResHeader^.count); 311 FillByte(reshandles^,sizeof(Pointer)*ResHeader^.count,0); 312end; 313 314procedure FinalizeResources; 315begin 316 if ResHeader=nil then exit; 317 FreeMem(reshandles); 318 Fpmunmap(ResHeader,fd_size); 319 FpClose(fd); 320end; 321{$ENDIF} 322 323{$IFDEF EXTRES_GENERIC} 324procedure InitResources; 325var respath : pchar; 326 tmp : longword; 327 tmpptr : pbyte; 328label ExitErrMem, ExitErrFile, ExitNoErr; 329begin 330 respath:=GetExtResPath; 331// writeln('respath ',respath); 332 Assign(fd,respath); 333 FreeMem(respath); 334 {$I-} 335 Reset(fd,1); 336 {$I+} 337 if IOResult<>0 then exit; 338// writeln('file opened'); 339 ResHeader:=GetMem(sizeof(TExtHeader)); 340 if ResHeader=nil then goto ExitErrFile; 341 {$I-} 342 BlockRead(fd,ResHeader^,sizeof(TExtHeader),tmp); 343 {$I+} 344 if (IOResult<>0) or (tmp<>sizeof(TExtHeader)) then goto ExitErrMem; 345 if (ResHeader^.magic<>FPCRES_MAGIC) or (ResHeader^.version<>fpcres_version) 346 then goto ExitErrMem; 347// writeln('magic ok'); 348 if ResHeader^.endianess<>FPCRES_ENDIAN then 349 begin 350 ResHeader^.count:=SwapEndian(ResHeader^.count); 351 ResHeader^.nodesize:=SwapEndian(ResHeader^.nodesize); 352 ResHeader^.hdrsize:=SwapEndian(ResHeader^.hdrsize); 353 end; 354 SysReallocMem(ResHeader,ResHeader^.hdrsize); 355 if ResHeader=nil then goto ExitErrFile; 356 tmpptr:=pbyte(ResHeader); 357 inc(tmpptr,sizeof(TExtHeader)); 358 {$I-} 359 BlockRead(fd,tmpptr^,ResHeader^.hdrsize-sizeof(TExtHeader),tmp); 360 {$I+} 361 if (IOResult<>0) or (tmp<>ResHeader^.hdrsize-sizeof(TExtHeader)) then goto ExitErrMem; 362 if ResHeader^.endianess<>FPCRES_ENDIAN then 363 FixResEndian; 364 reshandles:=GetMem(sizeof(TResHandle)*ResHeader^.count); 365 FillByte(reshandles^,sizeof(TResHandle)*ResHeader^.count,0); 366 goto ExitNoErr; 367 368 ExitErrMem: 369 FreeMem(ResHeader); 370 ResHeader:=nil; 371 ExitErrFile: 372 {$I-} 373 Close(fd); 374 {$I+} 375 ExitNoErr: 376end; 377 378procedure FinalizeResources; 379begin 380 if ResHeader=nil then exit; 381 FreeMem(reshandles); 382 FreeMem(ResHeader); 383 Close(fd); 384end; 385{$ENDIF} 386 387(***************************************************************************** 388 Public Resource Functions 389*****************************************************************************) 390 391Function ExtHINSTANCE : TFPResourceHMODULE; 392begin 393 ExtHINSTANCE:=0; 394end; 395 396function ExtEnumResourceTypes(ModuleHandle : TFPResourceHMODULE; EnumFunc : EnumResTypeProc; lParam : PtrInt) : LongBool; 397var ptr : PResInfoNode; 398 totn, totid, i : longword; 399 pc : pchar; 400begin 401 ExtEnumResourceTypes:=False; 402 if ResHeader=nil then exit; 403 ptr:=GetResInfoPtr(sizeof(TExtHeader)); 404 totn:=ptr^.ncounthandle; 405 totid:=totn+ptr^.idcountsize; 406 ptr:=GetResInfoPtr(ptr^.subptr); 407 ExtEnumResourceTypes:=true; 408 i:=0; 409 while i<totn do //named entries 410 begin 411 pc:=GetPChar(ptr[i].nameid); 412 if not EnumFunc(ModuleHandle,pc,lParam) then exit; 413 inc(i); 414 end; 415 while i<totid do 416 begin 417 if not EnumFunc(ModuleHandle,PChar(ptr[i].nameid),lParam) then exit; 418 inc(i); 419 end; 420end; 421 422function ExtEnumResourceNames(ModuleHandle : TFPResourceHMODULE; ResourceType : PChar; EnumFunc : EnumResNameProc; lParam : PtrInt) : LongBool; 423var ptr : PResInfoNode; 424 totn, totid, i : longword; 425 pc : pchar; 426begin 427 ExtEnumResourceNames:=False; 428 if ResHeader=nil then exit; 429 ptr:=GetResInfoPtr(sizeof(TExtHeader)); 430 431 ptr:=BinSearchRes(ptr,ResourceType); 432 if ptr=nil then exit; 433 434 totn:=ptr^.ncounthandle; 435 totid:=totn+ptr^.idcountsize; 436 ptr:=GetResInfoPtr(ptr^.subptr); 437 ExtEnumResourceNames:=true; 438 i:=0; 439 while i<totn do //named entries 440 begin 441 pc:=GetPChar(ptr[i].nameid); 442 if not EnumFunc(ModuleHandle,ResourceType,pc,lParam) then exit; 443 inc(i); 444 end; 445 while i<totid do 446 begin 447 if not EnumFunc(ModuleHandle,ResourceType,PChar(ptr[i].nameid),lParam) then exit; 448 inc(i); 449 end; 450end; 451 452function ExtEnumResourceLanguages(ModuleHandle : TFPResourceHMODULE; ResourceType, ResourceName : PChar; EnumFunc : EnumResLangProc; lParam : PtrInt) : LongBool; 453var ptr : PResInfoNode; 454 tot, i : integer; 455begin 456 ExtEnumResourceLanguages:=False; 457 ptr:=InternalFindResource(ResourceName,ResourceType); 458 if ptr=nil then exit; 459 460 tot:=ptr^.idcountsize; 461 ptr:=GetResInfoPtr(ptr^.subptr); 462 ExtEnumResourceLanguages:=true; 463 i:=0; 464 while i<tot do 465 begin 466 if not EnumFunc(ModuleHandle,ResourceType,ResourceName,PtrUInt(ptr[i].nameid),lParam) then exit; 467 inc(i); 468 end; 469end; 470 471Function ExtFindResource(ModuleHandle: TFPResourceHMODULE; ResourceName, ResourceType: PChar) 472: TFPResourceHandle; 473var ptr : PResInfoNode; 474begin 475 ExtFindResource:=0; 476 ptr:=InternalFindResource(ResourceName,ResourceType); 477 if ptr=nil then exit; 478 479 //first language id 480 ptr:=GetResInfoPtr(ptr^.subptr); 481 if ptr^.ncounthandle=0 then 482 begin 483 {$IFDEF EXTRES_MMAP} 484 reshandles[usedhandles]:=ptr; 485 {$ENDIF} 486 {$IFDEF EXTRES_GENERIC} 487 reshandles[usedhandles].info:=ptr; 488 {$ENDIF} 489 inc(usedhandles); 490 ptr^.ncounthandle:=usedhandles; 491 end; 492 ExtFindResource:=ptr^.ncounthandle; 493end; 494 495Function ExtFindResourceEx(ModuleHandle: TFPResourceHMODULE; ResourceType, 496 ResourceName: PChar; Language : word): TFPResourceHandle; 497const LANG_NEUTRAL = 0; 498 LANG_ENGLISH = 9; 499var nameptr,ptr : PResInfoNode; 500begin 501 ExtFindResourceEx:=0; 502 nameptr:=InternalFindResource(ResourceName,ResourceType); 503 if nameptr=nil then exit; 504 505 //try exact match 506 ptr:=FindSubLanguage(nameptr,Language,$FFFF); 507 //try primary language 508 if ptr=nil then 509 ptr:=FindSubLanguage(nameptr,Language,$3FF); 510 //try language neutral 511 if ptr=nil then 512 ptr:=FindSubLanguage(nameptr,LANG_NEUTRAL,$3FF); 513 //try english 514 if ptr=nil then 515 ptr:=FindSubLanguage(nameptr,LANG_ENGLISH,$3FF); 516 //nothing found, return the first one 517 if ptr=nil then 518 ptr:=GetResInfoPtr(nameptr^.subptr); 519 520 if ptr^.ncounthandle=0 then 521 begin 522 {$IFDEF EXTRES_MMAP} 523 reshandles[usedhandles]:=ptr; 524 {$ENDIF} 525 {$IFDEF EXTRES_GENERIC} 526 reshandles[usedhandles].info:=ptr; 527 {$ENDIF} 528 inc(usedhandles); 529 ptr^.ncounthandle:=usedhandles; 530 end; 531 ExtFindResourceEx:=ptr^.ncounthandle; 532end; 533 534{$IFDEF EXTRES_MMAP} 535Function ExtLoadResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): TFPResourceHGLOBAL; 536begin 537 ExtLoadResource:=0; 538 if ResHeader=nil then exit; 539 if (ResHandle<=0) or (ResHandle>usedhandles) then exit; 540 ExtLoadResource:=TFPResourceHGLOBAL(GetPtr(PResInfoNode(reshandles[ResHandle-1])^.subptr)); 541end; 542 543Function ExtFreeResource(ResData: TFPResourceHGLOBAL): LongBool; 544begin 545 ExtFreeResource:=(ResHeader<>nil); 546end; 547 548Function ExtSizeofResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): LongWord; 549begin 550 ExtSizeofResource:=0; 551 if ResHeader=nil then exit; 552 if (ResHandle<=0) or (ResHandle>usedhandles) then exit; 553 ExtSizeofResource:=PResInfoNode(reshandles[ResHandle-1])^.idcountsize; 554end; 555{$ENDIF} 556 557{$IFDEF EXTRES_GENERIC} 558(* 559Resource data memory layout: 560-2*sizeof(pointer) Reference count 561 -sizeof(pointer) Pointer to resource info 562 0 Resource data 563*) 564Function ExtLoadResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): TFPResourceHGLOBAL; 565var ptr : PPtrUInt; 566 tmp : longword; 567begin 568 ExtLoadResource:=0; 569 if ResHeader=nil then exit; 570 if (ResHandle<=0) or (ResHandle>usedhandles) then exit; 571 572 if reshandles[ResHandle-1].ptr=nil then 573 begin 574 {$I-} 575 Seek(fd,reshandles[ResHandle-1].info^.subptr); 576 {$I+} 577 if IOResult<>0 then exit; 578 ptr:=GetMem(reshandles[ResHandle-1].info^.idcountsize+2*sizeof(PtrUint)); 579 if ptr=nil then exit; 580 ptr^:=1; //refcount 581 inc(ptr); 582 ptr^:=PtrUInt(reshandles[ResHandle-1].info); //ptr to resource info 583 inc(ptr); 584 {$I-} 585 BlockRead(fd,ptr^,reshandles[ResHandle-1].info^.idcountsize,tmp); 586 {$I+} 587 if (IOResult<>0) or (tmp<>reshandles[ResHandle-1].info^.idcountsize) then 588 begin 589 FreeMem(ptr); 590 exit; 591 end; 592 reshandles[ResHandle-1].ptr:=ptr; 593 end 594 else 595 begin 596 ptr:=reshandles[ResHandle-1].ptr; 597 dec(ptr,2); 598 inc(ptr^,1); //increase reference count 599 end; 600 ExtLoadResource:=TFPResourceHGLOBAL(reshandles[ResHandle-1].ptr); 601end; 602 603Function ExtFreeResource(ResData: TFPResourceHGLOBAL): LongBool; 604var ptrinfo : PResInfoNode; 605 ptr : PPtrUInt; 606begin 607 ExtFreeResource:=(ResHeader<>nil); 608 if not ExtFreeResource then exit; 609 ptr:=PPtrUInt(ResData); 610 dec(ptr,2); 611 dec(ptr^); //decrease reference count 612 if ptr^=0 then 613 begin 614 inc(ptr); 615 ptrinfo:=PResInfoNode(ptr^); 616 dec(ptr); 617 FreeMem(ptr); 618 reshandles[ptrinfo^.ncounthandle-1].ptr:=nil; 619 end; 620 ExtFreeResource:=true; 621end; 622 623Function ExtSizeofResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): LongWord; 624var ptrinfo : PResInfoNode; 625begin 626 ExtSizeofResource:=0; 627 if ResHeader=nil then exit; 628 if (ResHandle<=0) or (ResHandle>usedhandles) then exit; 629 ptrinfo:=PResInfoNode(reshandles[ResHandle-1].info); 630 ExtSizeofResource:=ptrinfo^.idcountsize; 631end; 632{$ENDIF} 633 634Function ExtLockResource(ResData: TFPResourceHGLOBAL): Pointer; 635begin 636 ExtLockResource:=Nil; 637 if ResHeader=nil then exit; 638 ExtLockResource:=Pointer(ResData); 639end; 640 641Function ExtUnlockResource(ResData: TFPResourceHGLOBAL): LongBool; 642begin 643 ExtUnlockResource:=(ResHeader<>nil); 644end; 645 646const 647 ExternalResourceManager : TResourceManager = 648 ( 649 HINSTANCEFunc : @ExtHINSTANCE; 650 EnumResourceTypesFunc : @ExtEnumResourceTypes; 651 EnumResourceNamesFunc : @ExtEnumResourceNames; 652 EnumResourceLanguagesFunc : @ExtEnumResourceLanguages; 653 FindResourceFunc : @ExtFindResource; 654 FindResourceExFunc : @ExtFindResourceEx; 655 LoadResourceFunc : @ExtLoadResource; 656 SizeofResourceFunc : @ExtSizeofResource; 657 LockResourceFunc : @ExtLockResource; 658 UnlockResourceFunc : @ExtUnlockResource; 659 FreeResourceFunc : @ExtFreeResource; 660 ); 661