AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

FileExtract / FileExtract_ToMem (counterpart of FileInstall)

 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
Lexikos



Joined: 17 Oct 2006
Posts: 2474
Location: Australia, Qld

PostPosted: Wed Mar 26, 2008 8:05 am    Post subject: FileExtract / FileExtract_ToMem (counterpart of FileInstall) Reply with quote

FileExtract: Extracts a file from this compiled script by using a dynamic FileInstall.
FileExtract_ToMem: Extracts a FileInstall'd file into memory without writing it to disk.

I wrote this mostly to demonstrate two techniques:
  • Modify a line of script at run-time to allow variable references in the source parameter of FileInstall, and prevent Ahk2Exe from interpreting it. (Turn FileCopy into FileInstall.)
  • Use machine code (compiled C++) to achieve multi-threading. (Read from the pipe while FileInstall is writing to it.)


FileExtract.ahk:
Code:
/*
    Function: FileExtract
   
    Extracts a file from this compiled script by using a dynamic FileInstall.
   
    Syntax:
        FileExtract( Source, Dest [, Flag ] )
   
    Parameters:
        Source  - The source string used in the original FileInstall.
        Dest    - The name of the file to be created.
        Flag    - Specify 1 to overwrite existing files, otherwise omit.
   
    Remarks:
        Unlike FileInstall, FileExtract() allows variables and expressions for Source,
        and does not cause Ahk2Exe to include a file into the executable.
*/
FileExtract(Source, Dest, Flag=0) {
    static init
    if !init
        cb := RegisterCallback("FileExtract_")
        ; cb->func->mJumpToLine->mActionType := ACT_FILEINSTALL
        , NumPut(159, NumGet(NumGet(cb+28)+4), 0, "UChar")
        , DllCall("GlobalFree", "uint", cb)
    return FileExtract_(Source, Dest, Flag)
}
FileExtract_(Source, Dest, Flag) {
    FileCopy, %Source%, %Dest%, %Flag%
    return !ErrorLevel
}

/*
    Function: FileExtract_ToMem
   
    Extracts a FileInstall'd file into memory.
   
    Syntax:
        FileExtract_ToMem( Source, pData, DataSize [, InitialBufferSize, InitialBuffer ] )
   
    Parameters:
        Source       [in] - The source string used in the original FileInstall.
        pData    [in/out] - A pointer to the buffer where file data is written. See remarks.
        DataSize     [in] - If pData is zero, this indicates the initial buffer size.
                    [out] - Receives the number of bytes written to the buffer.
   
    Remarks:
        pData must specify zero or a valid pointer to memory allocated with GlobalAlloc().
       
        If the caller specifies a non-zero pData, it is used as the initial data buffer.
        If the buffer is too small, the function will reallocate it and update pData.
        The function does not delete the buffer on failure unless the caller specified zero.
       
        Once the data is no longer needed, free it using GlobalFree:
       
            DllCall("GlobalFree","uint",pData)
       
        DataSize indicates the amount of data written, not the size of the buffer.
        To determine the actual size of the buffer, use GlobalSize:
       
            MemSize := DllCall("GlobalSize","uint",pData)
*/
FileExtract_ToMem(Source, ByRef pData, ByRef DataSize)
{
    static ReadPipe, ConnectNamedPipe, ReadFile, GlobalReAlloc
    if !VarSetCapacity(ReadPipe)
    {
        ; Initialize the machine code function for reading from the pipe.
        hex =
        ( LTrim Join
        558BEC81EC0004000053568B75085733FF397E080F848D000000397E040F848400000057
        FF36FF561057BB00040000EB5E8B46088B4D088BD02B560C3BD1732803C08946088B560C
        2BC23BC1730503D18956086A02FF7608FF7604FF561885C074458B4D088946048B460C03
        460433FF85C976168D9500FCFFFF2BD08A0C0288088B4D0847403BF972F2014E0C6A008D
        450850538D8500FCFFFF50FF36FF561485C0758D40EB0233C05F5E5BC9C20400
        )
        ;~ MCode() - http://www.autohotkey.com/forum/viewtopic.php?t=21172
        VarSetCapacity(ReadPipe,StrLen(hex)//2)
        Loop % StrLen(hex)//2
           NumPut("0x" . SubStr(hex,2*A_Index-1,2), ReadPipe, A_Index-1, "Char")
        ;~ end
       
        ; Resolve ReadPipe()'s dependencies for later.
        hKernel32 := DllCall("GetModuleHandle", "str", "kernel32.dll")
        ConnectNamedPipe := DllCall("GetProcAddress", "uint", hKernel32, "str", "ConnectNamedPipe")
        ReadFile         := DllCall("GetProcAddress", "uint", hKernel32, "str", "ReadFile")
        GlobalReAlloc    := DllCall("GetProcAddress", "uint", hKernel32, "str", "GlobalReAlloc")
    }
   
    UserOwnsData := !!pData ; True if pData is not [blank or zero].
    if !pData
    {   ; If DataSize is non-numeric or < 1, default to 1024.
        if (DataSize+0 < 1)
            DataSize := 1024
        pData := DllCall("GlobalAlloc","uint",0,"uint",DataSize)
    }
    else
    {   ; Get the actual size of the memory block,
        DataSize := DllCall("GlobalSize","uint",pData)
    }
   
    VarSetCapacity(ReadPipeStruct, 28, 0) ; ReadPipeStruct

    ; Fill ReadPipeStruct with ReadPipe()'s dependencies.
    NumPut(ConnectNamedPipe, ReadPipeStruct, 16)
    NumPut(ReadFile, ReadPipeStruct, 20)
    NumPut(GlobalReAlloc, ReadPipeStruct, 24)
   
    Random, pipe_name
   
    ; Create a named pipe (with an unpredictable name) for writing the file into.
    hNamedPipe := DllCall("CreateNamedPipe", "str", "\\.\pipe\" pipe_name, "uint", 3
                    , "uint", 0, "uint", 255, "uint",0, "uint",0, "uint",0, "uint",0)
    ; Set the parameters for the pipe-reading thread.
    NumPut(hNamedPipe, ReadPipeStruct, 0)
    NumPut(pData, ReadPipeStruct, 4)
    NumPut(DataSize, ReadPipeStruct, 8)

    ; Create a thread to read from the pipe into memory.
    ; The thread will start immediately, but will wait for a pipe connection.
    hReadThread := DllCall("CreateThread", "uint", 0, "uint", 0, "uint", &ReadPipe
                            , "uint", &ReadPipeStruct, "uint", 0, "uint*", ReadThreadID)
   
    ; "Replace flag" *must* be specified since the pipe... exists.
    FileExtractResult := FileExtract(Source, "\\.\pipe\" pipe_name, 1)
   
    if !FileExtractResult
    {   ; Open and close a connection to the pipe to terminate the thread.
        FileAppend,, \\.\pipe\%pipe_name%
    }
   
    Loop {
        ; Wait for the thread to terminate, or any window message to be received.
        r := DllCall("MsgWaitForMultipleObjectsEx", "uint", 1, "uint*", hReadThread
                                            , "uint", -1, "uint", 0x4FF, "uint", 0x6)
        if (r = 0) || (r = -1) ; WAIT_OBJECT_0 or WAIT_FAILED
            break
        Sleep, 1 ; Allow AutoHotkey to dispatch messages.
    }

    DllCall("DisconnectNamedPipe", "uint", hNamedPipe)
    DllCall("CloseHandle", "uint", hNamedPipe)
    DllCall("CloseHandle", "uint", hReadThread)

    if FileExtractResult || UserOwnsData
    {
        ; Either it was a success and we are returning the extracted data,
        ; or it failed and we are returning the memory to the caller, since
        ; they may want to reuse it.
        pData := NumGet(ReadPipeStruct,4)
        DataSize := NumGet(ReadPipeStruct,12)
    }
    else
    {
        ; If ReadPipe() fails because of low memory, pData may have been reallocated,
        ; so always use the value in the structure.
        DllCall("GlobalFree", "uint", NumGet(ReadPipeStruct,4))
        pData := DataSize := 0
    }
    return FileExtractResult
}
FileExtractDemo.ahk - Self-compiling Example Script:
Code:
; Uncomment this or put FileExtract.ahk in your function library:
;#include FileExtract.ahk

if !A_IsCompiled
{   ; Self-compile-and-run.
    RunWait, %A_AhkPath%\..\Compiler\Ahk2Exe.exe /in "%A_ScriptFullPath%"
    Run % SubStr(A_ScriptFullPath, 1, InStr(A_ScriptFullPath,".",1,0)-1) ".exe"
    ExitApp
   
    ; Not executed by AutoHotkey, but interpreted by Ahk2Exe:
    FileInstall, FileExtractDemo.ahk, ~
}

Menu, Tray, MainWindow

VarSetCapacity(important, 40, Asc("="))
GuiOut(important " Actual Script")

; Initialize pData since it has no value yet:
pData := 0
; DataSize specifies the desired initial size of the buffer, or zero for "don't care".
DataSize := 0

; The script itself is either >AUTOHOTKEY SCRIPT< or >AHK WITH ICON<.
if FileExtract_ToMem(">AUTOHOTKEY SCRIPT<", pData, DataSize)
    || FileExtract_ToMem(">AHK WITH ICON<", pData, DataSize)
{   ; Show the string data in a GUI.
    GuiOut(GetStrN(pData, DataSize))
}
else
{   ; Note: At this point, AutoHotkey has probably already shown an error dialog.
    GuiOut("-- Failed! --")
}

GuiOut(important " FileExtractDemo.ahk")

; Extract a FileInstall'd file.
if FileExtract_ToMem("FileExtractDemo.ahk", pData, DataSize)
{
    GuiOut(GetStrN(pData, DataSize))
}
else
{
    GuiOut("-- Failed! --")
}

; Free the buffer.
DllCall("GlobalFree", "uint", pData)

return

GuiOut(Text, GuiNum=37) {
    static GuiText
    Gui, %GuiNum%: Default
    if (GuiText = "") {
        GuiText := Text "`n"
        Gui, Font,, Courier New
        Gui, Add, Edit, ReadOnly W600 H400, %GuiText%
    } else
        GuiControl,, Edit1, % GuiText .= Text "`n"
    Gui, Show
    Gui, +LastFound +LabelGuiOut
    ControlSend, Edit1, ^{End}
    return
    GuiOutClose:
    GuiOutEscape:
    ExitApp
}

GetStrN(Pointer, Length) {
    VarSetCapacity(String, Length)
    DllCall("lstrcpyn", "str", String, "uint", Pointer, "int", Length+1)
    return String
}

ReadPipe (the machine code function in FileExtract_ToMem) was written in C++. The source code is as follows: (note you don't need this to use the function)
Code:
#define MCODE_API extern "C" __declspec(dllexport)

//...

struct ReadPipeStruct
{
    HANDLE named_pipe;  // Pipe to read from, set by caller.
    char *buffer;       // Caller sets this to a fixed memory block returned by GlobalAlloc().
                        // ReadPipe() may reallocate the memory with GlobalReAlloc() and update this.
    DWORD buffer_size;  // Caller must set this to the size of the buffer.
                        // ReadPipe() updates this if/when it reallocates the buffer.
    DWORD data_size;    // Caller must set this to the offset within buffer to write data at.
                        // ReadPipe() sets this to the offset of the byte after the last byte of data (<= buffer_size.)

    // The following must be set by the caller, to point to the named functions.
    BOOL (WINAPI * ConnectNamedPipe)(HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped);
    BOOL (WINAPI * ReadFile)(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
    HGLOBAL (WINAPI * GlobalReAlloc)(HGLOBAL hMem, SIZE_T dwBytes, UINT uFlags);
};

MCODE_API DWORD WINAPI ReadPipe(ReadPipeStruct *rp)
{
    DWORD nNumberOfBytesRead;

    char *&buffer = rp->buffer;
    DWORD &buffer_size = rp->buffer_size;
    DWORD &data_size = rp->data_size;

    // Caller must specify non-NULL buffer.
    if (!(buffer_size && buffer))
        return FALSE;
   
    // Wait for a connection to the pipe.
    rp->ConnectNamedPipe(rp->named_pipe, NULL);

    // Use a temporary buffer for reading so we know when lpBuffer should be resized.
    // (Don't want to reallocate the buffer only to find that the pipe has ended...)
    char read_buffer[1024];
    DWORD i;
    char *pointer;

    #define BUFFER_REMAINING (buffer_size - data_size)

    while (rp->ReadFile(rp->named_pipe, read_buffer, sizeof(read_buffer), &nNumberOfBytesRead, NULL))
    {
        if (BUFFER_REMAINING < nNumberOfBytesRead)
        {   // Double the buffer size.
            buffer_size *= 2;
            // Ensure at least the required amount will be allocated.
            if (BUFFER_REMAINING < nNumberOfBytesRead)
                buffer_size = data_size + nNumberOfBytesRead;
            // Do the actual memory reallocation.
            pointer = (char*)rp->GlobalReAlloc((HGLOBAL)buffer, buffer_size, GMEM_MOVEABLE);
            if (!pointer)
                return FALSE;
            buffer = pointer;
        }
        // Copy the data we just read into the output buffer.
        pointer = buffer + data_size;
        for (i = 0; i < nNumberOfBytesRead; ++i)
            pointer[i] = read_buffer[i];
        // Advance the buffer position.
        data_size += nNumberOfBytesRead;
    }
    return TRUE;
}
Back to top
View user's profile Send private message
majkinetor



Joined: 24 May 2006
Posts: 3600
Location: Belgrade

PostPosted: Wed Mar 26, 2008 9:43 am    Post subject: Reply with quote

thx m8.

Very informative as always.
_________________
Back to top
View user's profile Send private message MSN Messenger
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group