Retreive data from compiled-in recources

Here is a piece of sample code to retrieve data from a compiled in resource. This sample was used to store and execute a SQL script from inside a dll or exe.
To create .res files i used XNReshourceEditor.exe (free download). Add the generated resource to library includes.

The trick is to: Do not mess up the HANDLEs!

Usage:

    GetResource('DATA.DLL', 'SQL', 'WHO_IS_ACTIVE_V11_32', SQL)
    Dummy{PROP:SQL} = SQL
    IF ERRORCODE() THEN Message(Dummy{PROP:SQL}, 'Error in GetResource script') END

Inside Global map:

         MODULE('Win32API')
           GetModuleHandle(*CSTRING pModuleName), HMODULE, PASCAL, RAW, NAME('GetModuleHandleA') ! return handle/instance of module
           GetModuleHandleRaw(UNSIGNED pModuleName), HMODULE, PASCAL, RAW, NAME('GetModuleHandleA') ! return handle/instance of module
           FindResource(HINSTANCE,*CSTRING,*CSTRING),HANDLE,PASCAL,RAW,NAME('FindResourceA')
           SizeofResource(HINSTANCE, HANDLE),DWORD,PASCAL
           LoadResource(HINSTANCE, HANDLE),HGLOBAL,PASCAL
           LockResource(HGLOBAL),LONG,PASCAL
           FreeResource(HGLOBAL),LONG,PASCAL
           MemCpy(LONG dest, LONG src, LONG count), LONG, RAW, PROC, NAME('_memcpy')    
         END

The code:

GetResource          PROCEDURE  (<STRING pModule>,STRING pType,STRING pID,*CSTRING pData) ! Declare Procedure
aResourceID             CSTRING(65)
aResourceType           CSTRING(65)
aModule                 CSTRING(65)

aModuleHandle           HMODULE
aResourceHandle         HANDLE
aResourceSize           DWORD
aResourceMemory         HGLOBAL
aPointer                LONG

  CODE
  GlobalErrors.SetProcedureName('GetResource')
  
  aModule = pModule
  aModuleHandle = GetModuleHandle(aModule)
  IF NOT aModuleHandle THEN ! Get Handle of current module by API null if not found by name
    aModuleHandle = GetModuleHandleRaw(0)  
    aModule = 0 ! set value for debugoutput
  END
  IF aModuleHandle THEN
    aResourceType = pType
    aResourceID = pID
    aResourceHandle = FindResource(aModuleHandle, aResourceID, aResourceType)
    IF aResourceHandle THEN
      aResourceSize = SizeOfResource(aModuleHandle, aResourceHandle)
      DebugOutput('Resource Size=' & aResourceSize & ' for ' & aModule & '\' & aResourceType & '\' & aResourceID)
      aResourceMemory = LoadResource(aModuleHandle, aResourceHandle)
      IF aResourceMemory THEN
        aPointer = LockResource(aResourceMemory)
        IF aPointer THEN
          MemCpy(ADDRESS(pData), aPointer, aResourceSize)
          pData[aResourceSize + 1] = '<0>' ! Resources are not required to end with a null character and could have null characters inside them.
          DebugOutput('Loaded Resource ' & aModule & '\' & aResourceType & '\' & aResourceID & ' data=' & SUB(pData, 1, CHOOSE(aResourceSize < 1024, aResourceSize, 1024)))
        ELSE
          MESSAGE('Could not retreive resource memory pointer for ' & aModule & '\' & aResourceType & '\' & aResourceID)
        END
        ! FreeResource(aResourceMemory) ! [This function is obsolete and is only supported for backward compatibility with 16-bit Windows. For 32-bit Windows applications, it is not necessary to free the resources loaded using LoadResource.]
      ELSE
        MESSAGE('Could not retreive resource memory handle for ' & aModule & '\' & aResourceType & '\' & aResourceID)
      END ! IF aResourceMemory
    ELSE
      MESSAGE('Could not retreive resource handle for ' & aModule & '\' & aResourceType & '\' & aResourceID)
    END ! IF aResourceHandle
  ELSE
    MESSAGE('Could not retreive module handle for ' & aModule)
  END ! IF aModuleHandle
  
  GlobalErrors.SetProcedureName()
  • Missing datatypes can be found in Windows.inc
2 Likes

Nice work, Eric! Thanks!

Thanks a lot for this. I was planning to research how to do it.

SV should use similar code in AnyScreen to extract on the fly and serve icons and images already embedded in the exe/dll.

You can actually add text files to the ā€˜Libraries, Objects and Resource Filesā€™ section in the Solution Explorer and those files are then embedded as an IMAGE resource.
That makes it easy to use Clarion to retrieve the scripts from either an EXE or a DLL using just the API calls belowā€¦

 MODULE('WINAPI')
     FindResource(UNSIGNED,LONG,LONG),UNSIGNED,PASCAL,RAW,NAME('FindResourceA')
     LoadResource(UNSIGNED,UNSIGNED),UNSIGNED,PASCAL, RAW
     SizeOfResource(UNSIGNED,UNSIGNED),LONG,PASCAL
     GetModuleHandle(*cstring lpModuleName),UNSIGNED,pascal,raw,name('GetModuleHandleA')
 END

The Variablesā€¦

RES_TYPE CSTRING(ā€˜IMAGEā€™)
RES_HANDLE UNSIGNED
SCRIPT_NAME CSTRING(128)
SCRIPT_SIZE UNSIGNED
HGLOBALMEM UNSIGNED

theScript &cstring
moduleName cstring(81)
moduleHandle UNSIGNED

Retrieval of script from EXE

        !*** load script from EXE ***
        SCRIPT_NAME = 'TESTSCRIPT_TXT'
        RES_HANDLE = FindResource(system{PROP:AppInstance},address(SCRIPT_NAME),address(RES_TYPE))
        theScript &= LoadResource(system{PROP:AppInstance},RES_HANDLE)
        SCRIPT_SIZE = SizeOfResource(system{PROP:AppInstance},RES_HANDLE)
        if LEN(theScript) <> SCRIPT_SIZE
            message('Ooops something went wrong')
        else                
            !message('TheScript||' & theScript)
            ?TEXT1{PROP:Text} = theScript
        end

Retrieval of script from resource DLL

       !*** load script from DLL ***
        MainSQLResources()
        SCRIPT_NAME = 'JOBCANDIDATE_SQL'
        moduleName = 'SQLRESOURCES.DLL'
        moduleHandle = GetModuleHandle(moduleName)
        RES_HANDLE = FindResource(moduleHandle,address(SCRIPT_NAME),address(RES_TYPE))
        theScript &= LoadResource(moduleHandle,RES_HANDLE)
        SCRIPT_SIZE = SizeOfResource(moduleHandle,RES_HANDLE)
        if LEN(theScript) <> SCRIPT_SIZE
            message('Ooops something went wrong')
        else                
            !message('TheScript||' & theScript)
            ?TEXT1{PROP:Text} = theScript
        end    

NB MainSQLResources is just an empty exported procedure - necessary to ensure Clarionā€™s smart linker actually links in the resource DLL

Advantage of doing it this way is that the scripts will be embedded as an IMAGE resource - so anyone hacking your EXE or DLL with a resource editor wonā€™t be able to see the scripts.

SQLResources.zip (14.1 KB)

2 Likes

Rather than have the GetModuleHandleRaw(LONG) prototype you can make the name Omittable <*CSTRING> and it will be passed as NULL if called ().

GetModuleHandle(<*CSTRING pModuleName>), HMODULE,PASCAL,RAW,DLL(1),NAME('GetModuleHandleA')

I would size ā€œaModule CSTRING(65)ā€ as (261) incase you have a LFN.

I would CLIP() the assignment aModule = CLIP(pModule).

If you put DLL(1) on the API prototypes you tip off the compiler it will always be external and it will write better code for the linker. See MSDN __DeclSpec(DllImport).

One more thing ā€¦ Since pModule is omittable you should test for that and not try to access it:

IF ~OMITTED(pModule) THEN
  aModule = CLIP(pModule)
  aModuleHandle = GetModuleHandle(aModule)
END