;; @module winscript ;; @description Embedded VBScript/JScript in newLISP. ;; @version 0.23 ;; @author m35 ;; ;; @location http://www.autohotkey.net/~easycom/winscript.lsp ;; ;; http://www.autohotkey.net/~easycom/winscript.lsp ;; This module uses the COM/OLE Microsoft Scripting Control object to execute ;; VBScript or JScript. Most computers have it installed by default. It can ;; also be downloaded directly from Microsoft. ;; ;; http://www.microsoft.com/downloads/details.aspx?FamilyId=D7E31492-2595-49E6-8C02-1426FEC693AC ;; ;; Change log: ;; ;; 0.10 - Initial release. ;; ;; 0.11 - Fixed memory leak when unhandled type is returned. ;; Improved get-short and get-single functions. ;; ;; 0.12 - Fixed handling of automation errors. ;; ;; 0.20 - Large internal changes to properly handle errors. This fixes ;; memory leaks and provides more comprehensive error information. ;; Added '(WINSCRIPT:LastResult)' function to return the results of ;; win32api calls. ;; ;; 0.21 - Added missing cleanup if failure during initialization. ;; ;; 0.22 - Minor documentation improvements. ;; Added check at initialization if already initialized. ;; Fixed error handling when the error doesn't come from the script control. ;; Lots of internal cleaning and comments. ;; ;; 0.23 - changed import type constant 265 to 2312 as required since 10.4.0 , L.M. ;; ;; Tested with newLISP 9.2.0 and 9.4.3. ;; ;; Contact me (m35) in the newLISP Fan Club forum ;; @link http://www.alh.net/newlisp/phpbb/viewtopic.php?t=1983 ;; http://www.alh.net/newlisp/phpbb/viewtopic.php?t=1983 ;; ;; A good list of automation error descriptions can be found in ;; @link http://support.microsoft.com/kb/186063 KB186063 ;; @example ;; > (WINSCRIPT:Initialize) ;; true ;; > (WINSCRIPT:Exec {foo = "bar"}) ;; true ;; > (WINSCRIPT:Eval {foo}) ;; "bar" ;; > (WINSCRIPT:Uninitialize) ;; true ;; @example ;; > (WINSCRIPT:Initialize "VBScript") ;; true ;; > (WINSCRIPT:Exec {Set oSp = CreateObject("SAPI.SpVoice")}) ;; true ;; > (WINSCRIPT:Exec {oSp.Speak "newLISP: Puts the fun back in LISP"}) ;; true ;; @example ;; ; this example requires Microsoft Excel to be installed. ;; > (WINSCRIPT:Initialize) ;; true ;; > (WINSCRIPT:Exec {Set xl = CreateObject("Excel.Application")}) ;; true ;; > (WINSCRIPT:Exec {xl.Visible = True}) ;; true ;; > (WINSCRIPT:Exec {Set rng = xl.Workbooks.Add().Worksheets(1).Cells(1, 1)}) ;; true ;; > (WINSCRIPT:Exec {rng.Value = 1.2345}) ;; true ;; > (WINSCRIPT:Eval {rng.Value}) ;; 1.2345 ;; > (WINSCRIPT:Exec {xl.DisplayAlerts = False}) ;; true ;; > (WINSCRIPT:Exec {xl.Quit}) ;; true ;; > (WINSCRIPT:Uninitialize) ;; true (context 'WINSCRIPT) (constant 'IID_IDispatch "{00020400-0000-0000-C000-000000000046}" ) (constant 'IID_IUnknown "{00000000-0000-0000-C000-000000000046}" ) (constant 'ProgId_ScriptControl "MSScriptControl.ScriptControl" ) (constant 'CLSID_ScriptControl "{0E59F1D5-1FBE-11D0-8FF2-00A0D10038BC}" ) (constant 'IID_ScriptControl "{0E59F1D3-1FBE-11D0-8FF2-00A0D10038BC}" ) (constant 'CLSCTX_INPROC_SERVER 1 ) (constant 'CLSCTX_INPROC_HANDLER 2 ) (constant 'CLSCTX_LOCAL_SERVER 4 ) (constant 'CLSCTX_INPROC_SERVER16 8 ) (constant 'CLSCTX_REMOTE_SERVER 16 ) (define __iScriptControlObj__ nil) (define __iScriptErrorObj__ nil) (define __sScriptLanguage__ nil) (define __LastResult__ '()) ;; @syntax (WINSCRIPT:Initialize [="VBScript"]) ;; @param either '"VBScript"' or '"JScript"' ;; @return true on success, '(throw-error)' on error ;; Initializes the scripting environment. Must be called before any other ;; functions can be used. ;; ;; If the scripting environment has already been initialized, ;; this function does nothing. (define (Initialize (sLanguage "VBScript") , @iCoInit @iScriptCtrl @iScriptErr @xLangRet) ; Check if already initialized (if (not (and (integer? __iScriptControlObj__) (!= __iScriptControlObj__ 0))) (begin ; initialize COM (setq @iCoInit (_CoInitialize)) (if (failed? @iCoInit) (throw-error (last (setq __LastResult__ (err-msg @iCoInit) ))) ) ; create a scripting control (setq @iScriptCtrl (__CreateObjectProgId ProgId_ScriptControl IID_ScriptControl)) (if (failed? @iScriptCtrl) (begin (Uninitialize) (throw-error (last (setq __LastResult__ (join-err @iCoInit @iScriptCtrl) ))) )) ; save it (setq __iScriptControlObj__ (ret-val @iScriptCtrl)) ; get the scripting error object (setq @iScriptErr (IScriptControl.Error __iScriptControlObj__)) (if (failed? @iScriptErr) (begin (Uninitialize) (throw-error (last (setq __LastResult__ (join-err @iCoInit @iScriptCtrl @iScriptErr) ))) )) ; save it (setq __iScriptErrorObj__ (ret-val @iScriptErr)) ; set the scripting language (setq @xLangRet (IScriptControl.Language __iScriptControlObj__ sLanguage)) (if (failed? @xLangRet) (begin (Uninitialize) (throw-error (last (setq __LastResult__ (join-err @iCoInit @iScriptCtrl @iScriptErr @xLangRet) ))) )) ; save it (setq __sScriptLanguage__ sLanguage) ; save the result stack (setq __LastResult__ (join-err @iCoInit @iScriptCtrl @iScriptErr @xLangRet) ) ));/if not already initialized true ; no error occured ) (import "ole32.dll" "CoUninitialize") ;; @syntax (WINSCRIPT:Uninitialize) ;; @return 'true' ;; Releases memory for scripting environment. Does not have to be called before ;; exiting the program, but it is good practice. ;; ;; If the scripting environment is not initialized, this function does nothing. (define (Uninitialize) (if (and (integer? __iScriptErrorObj__) (!= __iScriptErrorObj__ 0)) (IUnknown.Release __iScriptErrorObj__) ) (if (and (integer? __iScriptControlObj__) (!= __iScriptControlObj__ 0)) (IUnknown.Release __iScriptControlObj__) ) (CoUninitialize) (setq __iScriptControlObj__ nil) (setq __iScriptErrorObj__ nil) (setq __sScriptLanguage__ nil) (setq __LastResult__ '()) true ; return ) ;; @syntax (WINSCRIPT:Exec ) ;; @param code to execute ;; @return 'true' on success, '(throw-error)' on error. ;; Executes the scripting code. (define (Exec sCode , @varReturn) ; make sure WINSCRIPT has been initialized (if (nil? __iScriptControlObj__) (throw-error "Windows scripting has not been initialized") ) ; try to execute the code (setq @varReturn (IScriptControl.ExecuteStatement __iScriptControlObj__ sCode)) ; was it successful? (if (success? @varReturn) ; then (begin ; set __LastResult__ to the returned stack of 'errors' (setq __LastResult__ (err-msg @varReturn)) true ; return ) ; else (begin (_HandleError @varReturn) ; __LastResult__ now holds all the 'errors' up to this point ; clear the scripting error (IScriptError.Clear __iScriptErrorObj__) ; throw-error only the top-most error on the stack (throw-error (last __LastResult__)) ) ;/begin ) ) ;; @syntax (WINSCRIPT:Eval ) ;; @param code to evaluate ;; @return resulting value on success, '(throw-error)' on error. ;; Evaluates the scripting code and returns the result. Returned types can be ;; String, Byte, Integer, Long, Float, Double, Boolean ('true' or 'nil'), ;; or object pointer. Uninitialized variables ("Empty") are returned as an ;; empty list. The remaining types (Arrays, Currency, Date, VARIANT*, and ;; DECIMAL*) are not handled and will cause an error if returned. Convert these ;; unhandled types to handled type (such as String) to return the value. ;; ;; @example ;; > (WINSCRIPT:Initialize) ;; true ;; > (WINSCRIPT:Eval "Now()") ;; user error : Unhandled variant type ;; called from user defined function WINSCRIPT:Eval ;; > (WINSCRIPT:Eval "CStr(Now())") ;; "1/9/2008 10:07:10 PM" (define (Eval sCode , @varReturn @nlRet) ; make sure WINSCRIPT has been initialized (if (nil? __iScriptControlObj__) (throw-error "Windows scripting has not been initialized") ) ; try to evaluate the code (setq @varReturn (IScriptControl.Eval __iScriptControlObj__ sCode)) ; was it successful? (if (success? @varReturn) ; [then] (begin ; try to convert the return VARIANT to a newlisp variable (setq @nlRet (__UnpackVARIANT (ret-val @varReturn))) ; regardless of success, pack the 'error' stack into __LastResult__ (setq __LastResult__ (join-err @varReturn @nlRet) ) ; now if it was actually successful... (if (success? @nlRet) ; return the result (ret-val @nlRet) ; return ; or throw an error (throw-error (last __LastResult__)) ) ) ; [else] (begin (_HandleError @varReturn) ; __LastResult__ now holds all the 'errors' up to this point ; clear the scripting error (IScriptError.Clear __iScriptErrorObj__) ; throw-error only the top-most error on the stack (throw-error (last __LastResult__)) ) ) ) ; Sets __LastResult__ with all the 'errors' found. (define (_HandleError @varReturn , @sErrorDesc @iErrNum ) ; If there was an error calling ; IScriptControl.ExecuteStatement or IScriptControl.Eval ; (this is kinda sloppy, but it'll work) (if (starts-with (last (err-msg @varReturn)) "IScriptControl.E") ; [then] (begin ; try to get the error description (setq @sErrorDesc (IScriptError.Description __iScriptErrorObj__)) (if (or (= "" (ret-val @sErrorDesc)) ; if we got an empty string (nil? (ret-val @sErrorDesc)) ; or a null string (failed? @sErrorDesc)) ; or failed to get anything ; [then] (begin ; try to get the error number instead (setq @iErrNum (IScriptError.Number __iScriptErrorObj__)) ; did we get the number? (if (success? @iErrNum) ; [then] ; set __LastResult__ to the stack of 'errors' ; from all the calls up to this point, ; including the automation error (setq __LastResult__ (join-err @varReturn @sErrorDesc @iErrNum (string "Automation error " (ret-val @iErrNum)) ) ) ; [else] ; set __LastResult__ to the stack of 'errors' ; from all the calls up to this point ; (if we've reached this point, there has been a ; major failure somewhere) (setq __LastResult__ (join-err @varReturn @sErrorDesc @iErrNum) ) ) ) ; [else] ; we got an error description, set __LastResult__ to the stack ; of 'errors' from previous calls, including the error description. (setq __LastResult__ (join-err @varReturn @sErrorDesc (ret-val @sErrorDesc)) ) ) ;/if we didn't get any error message ) ; [else] (begin (setq __LastResult__ @varReturn) ) ) nil ; this is a void function ) ;; @syntax (WINSCRIPT:LastResult) ;; @return list of win32api function results during the last WINSCRIPT call. ;; The returned list of strings will hold the results of all win32api functions ;; that could have failed during the last operation. If the most recent ;; operation threw an error, the last item in the list will be the error message ;; thrown. ;; ;; @example ;; > (WINSCRIPT:Initialize) ;; true ;; > (WINSCRIPT:Exec "#$@#$$^$&*&&") ;; user error : Expected statement ;; called from user defined function WINSCRIPT:Exec ;; > (WINSCRIPT:LastResult) ;; ("MultiByteToWideChar ok" "SysAllocString ok" ;; "IScriptControl.ExecuteStatement -2146827264" ;; "IScriptError.Description 0" "WideCharToMultiByte ok" "Expected statement") (define (LastResult) __LastResult__ ) ;############################################################################### ; == Tier 1 COM Internals ====================================================== (import "ole32.dll" "CoInitialize") ; Initializes COM ; @return (success? ret-val errmsg) (define (_CoInitialize , iErr) (setq iErr (CoInitialize 0)) (return (>= iErr 0) ; success? nil ; never a return value (list (string "CoInitialize " iErr)) ; error result ) ) ; == Tier 2 COM Internals ====================================================== ; Creates an object from a Prog ID ; (e.g. "Excel.Application") ; @return (success? ret-val errmsg) (define (__CreateObjectProgId sProgId sIId , @sbinClsId @sbinIId @iObjPtr) (catch (begin ; get the binary class id from the prog id (setq @sbinClsId (__CLSIDFromProgID sProgId)) (if (failed? @sbinClsId) (throw (return nil ; fail nil (err-msg @sbinClsId) ) )) ; convert the interface id to its binary form (setq @sbinIId (__IIDFromString sIId)) (if (failed? @sbinIId) (throw (return nil ; fail nil (join-err @sbinClsId @sbinIId) ) )) ; create the object (setq @iObjPtr (__CreateInstance (ret-val @sbinClsId) (ret-val @sbinIId))) (return (success? @iObjPtr) (ret-val @iObjPtr) (join-err @sbinClsId @sbinIId @iObjPtr) ) )) ) ; == Tier 3 COM Internals ====================================================== (import "ole32.dll" "CoCreateInstance") ; Creates an object from the binary form of its Class ID and Interface ID ; @return (success? ret-val errmsg) (define (__CreateInstance sbinClassId sbinIId , iObjPtr iErr) (setq iObjPtr 0) (setq iErr (CoCreateInstance sbinClassId 0 (| CLSCTX_INPROC_SERVER CLSCTX_LOCAL_SERVER) sbinIId (address iObjPtr))) (return (>= iErr 0) (if (>= iErr 0) iObjPtr nil) (list (string "CoCreateInstance " iErr)) ) ) (import "ole32.dll" "CLSIDFromProgID") ; Looks up the binary Class ID of a Program ID (define (__CLSIDFromProgID sProgId , @wsProgId sbinClassId iErr) (catch (begin ; convert the progid to unicode (setq @wsProgId (__ANSI2Unicode sProgId)) (if (failed? @wsProgId) (throw (return nil ; fail nil (err-msg @wsProgId) )) ) ; get the binary class id for the prog id (setq sbinClassId (dup " " 16)) (setq iErr (CLSIDFromProgID (ret-val @wsProgId) sbinClassId)) (return (>= iErr 0) ; success? (if (>= iErr 0) sbinClassId nil) ; return value (join-err @wsProgId (string "CLSIDFromProgID " iErr)) ; error ) )) ) (import "ole32.dll" "IIDFromString") ; Converts a string Interface ID to a binary Interface ID ; (I really don't see why Win API has a separate function to do this) ; @return (success? ret-val errmsg) (define (__IIDFromString sIId , @wsIId sbinIId iErr) (catch (begin ; convert the interface id to unicode (setq @wsIId (__ANSI2Unicode sIId)) (if (failed? @wsIId) (throw (return nil ; fail nil (err-msg @wsIId) ) )) ; convert it to binary (setq sbinIId (dup " " 16)) (setq iErr (IIDFromString (ret-val @wsIId) sbinIId)) (return (>= iErr 0) ; success? (if (>= iErr 0) sbinIId nil) ; return (join-err @wsIId (string "IIDFromString " iErr)) ; error ) )) ) ; ## IScriptControl ############################################################ ; The entire IScriptControl Vtable (only the * members are implemented) ; 0 call_QueryInterface - Returns a pointer to a specified interface on an ; object to which a client currently holds an ; interface pointer ; 1 call_AddRef - Increments the reference count for an interface ; on an object ; 2 call_Release - Decrements the reference count for the calling ; interface on a object ; 3 call_GetTypeInfoCount - Retrieves the number of type information ; interfaces that an object provides (either 0 or 1) ; 4 call_GetTypeInfo - Retrieves the type information for an object ; 5 call_GetIDsOfNames - Maps a single member and an optional set of ; argument names to a corresponding set of integer ; DISPIDs ; 6 call_Invoke - Provides access to properties and methods exposed ; by an object. ; 7 * get_Language - Language engine to use ; 8 * put_Language - Language engine to use ; 9 get_State - State of the control ;10 put_State - State of the control ;11 put_SitehWnd - hWnd used as a parent for displaying UI ;12 get_SitehWnd - hWnd used as a parent for displaying UI ;13 get_Timeout - Length of time in milliseconds that a script can ; execute before being considered hung ;14 put_Timeout - Length of time in milliseconds that a script can ; execute before being considered hung ;15 get_AllowUI - Enable or disable display of the UI ;16 put_AllowUI - Enable or disable display of the UI ;17 get_UseSafeSubset - Force script to execute in safe mode and disallow ; potentially harmful actions ;18 put_UseSafeSubset - Force script to execute in safe mode and disallow ; potentially harmful actions ;19 get_Modules - Collection of modules for the ScriptControl ;20 * get_Error - The last error reported by the scripting engine ;21 get_CodeObject - Object exposed by the scripting engine that ; contains methods and properties defined in the ; code added to the global module ;22 get_Procedures - Collection of procedures that are defined in the ; global module ;23 call__AboutBox - ;24 call_AddObject - Add an object to the global namespace of the ; scripting engine ;25 call_Reset - Reset the scripting engine to a newly created ; state ;26 call_AddCode - Add code to the global module ;27 * call_Eval - Evaluate an expression within the context of the ; global module ;28 * call_ExecuteStatement - Execute a statement within the context of the ; global module ;29 call_Run - Call a procedure defined in the global module ; ; @syntax (IScriptControl.Language [sLanguage]) ; @param (int) Pointer to script control object ; @param (optional string) Scripting language (VBScript or JScript) ; @return (success? ret-val errmsg) ; ; If sLanguage is provided, sets the scripting language. Otherwise returns the ; currenet scripting language. ; Note: Changing the scripting language seems to reset the environment (define (IScriptControl.Language ppvScriptControl (sLanguage nil) , @bstrLang iErr) (catch (if sLanguage ; [then] Put language (begin ; convert string to BSTR (setq @bstrLang (__SysAllocStringA sLanguage)) (if (failed? @bstrLang) (throw (return nil ; fail nil (err-msg @bstrLang) ) )) ; put the language (setq iErr ((ptr-to-fn (__VTable ppvScriptControl 8)) ppvScriptControl (ret-val @bstrLang))) (SysFreeString (ret-val @bstrLang)) ; return (return (>= iErr 0) nil ; fail (join-err @bstrLang (string "IScriptControl.Language put " iErr) ) ) ) ; [else] Get language (begin ; get the language (setq @bstrLang 0) (setq iErr ((ptr-to-fn (__VTable ppvScriptControl 7)) ppvScriptControl (address @bstrLang))) (if (< iErr 0) (throw (return nil ; failed nil ; no return value (list (string "IScriptControl.Language get " iErr)) ; error ) )) ; convert the BSTR language to string (setq sLanguage (__Unicode2ANSI @bstrLang)) (SysFreeString @bstrLang) ; free the BSTR (return (success? sLanguage) (if (success? sLanguage) (ret-val sLanguage) nil) (join-err (string "IScriptControl.Language get " iErr) sLanguage ) ) ) );/if );/catch ) ; Pointer to Error object ; @return (success? ret-val errmsg) (define (IScriptControl.Error ppvScriptControl , ppvScriptError iErr) (setq ppvScriptError 0) (setq iErr ((ptr-to-fn (__VTable ppvScriptControl 20)) ppvScriptControl (address ppvScriptError))) (return (>= iErr 0) (if (>= iErr 0) ppvScriptError nil) (list (string "IScriptControl.Error " iErr)) ) ) ; @return (success? ret-val errmsg) (define (IScriptControl.Eval ppvScriptControl sExpression , @bstrExpression VariantRet iErr) (catch (begin ; convert code to BSTR (setq @bstrExpression (__SysAllocStringA sExpression)) (if (failed? @bstrExpression) (throw (return nil nil (err-msg @bstrExpression) ) )) ; execute the code (setq VariantRet (dup " " 16)) (setq iErr ((ptr-to-fn (__VTable ppvScriptControl 27)) ppvScriptControl (ret-val @bstrExpression) VariantRet)) ; free the BSTR (SysFreeString (ret-val @bstrExpression)) (return (>= iErr 0) (if (>= iErr 0) VariantRet nil) (join-err @bstrExpression (string "IScriptControl.Eval " iErr)) ) )) ) ; @return (success? ret-val errmsg) (define (IScriptControl.ExecuteStatement ppvScriptControl sStatement , @bstrExpression iErr) (catch (begin ; convert code to BSTR (setq @bstrExpression (__SysAllocStringA sStatement)) (if (failed? @bstrExpression) (throw (return nil ; fail nil (err-msg @bstrExpression) ) )) ; execute the code (setq iErr ((ptr-to-fn (__VTable ppvScriptControl 28)) ppvScriptControl (ret-val @bstrExpression))) ; free the BSTR (SysFreeString (ret-val @bstrExpression)) (return (>= iErr 0) nil (join-err @bstrExpression (string "IScriptControl.ExecuteStatement " iErr)) ) )) ) ; ## IScriptError ############################################################## ;The entire IScriptError Vtable (only the * members are implemented) ; 0 call_QueryInterface - Returns a pointer to a specified interface on an ; object to which a client currently holds an ; interface pointer ; 1 call_AddRef - Increments the reference count for an interface ; on an object ; 2 call_Release - Decrements the reference count for the calling ; interface on a object ; 3 call_GetTypeInfoCount - Retrieves the number of type information ; interfaces that an object provides (either 0 or 1) ; 4 call_GetTypeInfo - Retrieves the type information for an object ; 5 call_GetIDsOfNames - Maps a single member and an optional set of ; argument names to a corresponding set of ; integer DISPIDs ; 6 call_Invoke - Provides access to properties and methods ; exposed by an object. ; 7 * get_Number - Error number ; 8 get_Source - Source of the error ; 9 * get_Description - Friendly description of error ;10 get_HelpFile - File in which help for the error can be found ;11 get_HelpContext - Context ID for the topic with information on ; the error ;12 get_Text - Line of source code on which the error occurred ;13 get_Line - Source code line number where the error occurred ;14 get_Column - Source code column position where the ; error occurred ;15 * call_Clear - Clear the script error ; @return (success? ret-val errmsg) (define (IScriptError.Number ppvScriptError , iNum iErr) (setq iNum 0) (setq iErr ((ptr-to-fn (__VTable ppvScriptError 7)) ppvScriptError (address iNum))) (if (>= iErr 0) ; [then] (return true ; success (first (unpack "ld" (address iNum))) ; make signed int (list (string "IScriptError.Number " iErr)) ) ; [else] (return nil ; fail nil (list (string "IScriptError.Number " iErr)) ) ) ) ; Returns either an error string, or nil if there is no error string. ; @return (success? ret-val errmsg) (define (IScriptError.Description ppvScriptError , bstrDescription sAnsi iErr) (catch (begin (setq bstrDescription 0) (setq iErr ((ptr-to-fn (__VTable ppvScriptError 9)) ppvScriptError (address bstrDescription))) (if (< iErr 0) (throw (return nil ; fail nil (list (string "IScriptError.Description " iErr)) ) )) ; if BSTR is null (if (zero? bstrDescription) ; then (return true ; success nil ; but no description (list (string "IScriptError.Description " iErr)) ) ; else (begin ; convert to newlisp string (setq sAnsi (__Unicode2ANSI bstrDescription)) ; free BSTR (SysFreeString bstrDescription) (return (success? sAnsi) (ret-val sAnsi) (join-err (string "IScriptError.Description " iErr) sAnsi) ) ) ) )) ) ; @return (success? ret-val errmsg) (define (IScriptError.Clear ppvScriptError , iErr) (setq iErr ((ptr-to-fn (__VTable ppvScriptError 15)) ppvScriptError)) (return (>= iErr 0) nil (list (string "IScriptError.Clear " iErr)) ) ) ; ## IUnknown ################################################################## ;The entire IUnknown Vtable (all members are implemented) ; 0 call_QueryInterface - Returns a pointer to a specified interface on an ; object to which a client currently holds an ; interface pointer ; 1 call_AddRef - Increments the reference count for an interface ; on an object ; 2 call_Release - Decrements the reference count for the calling ; interface on a object ; @return (success? ret-val errmsg) (define (IUnknown.QueryInterface ppVtbl sInterfaceId , @binInterfaceId ppvNewInterface iErr) (catch (begin ; convert interface id string to binary (setq @binInterfaceId (__IIDFromString sInterfaceId)) (if (failed? @binInterfaceId) (throw (return nil ; failed nil (err-msg @binInterfaceId) ) )) ; call QueryInterface (setq ppvNewInterface 0) (setq iErr ((ptr-to-fn (__VTable ppVtbl 0)) ppVtbl (ret-val @binInterfaceId) (address ppvNewInterface))) (return (>= iErr 0) (if (>= iErr 0) ppvNewInterface nil) (join-err @binInterfaceId (string "IUnknown.QueryInterface " iErr)) ) )) ) ; @return number of references that remain (define (IUnknown.Release ppv) ((ptr-to-fn (__VTable ppv 2)) ppv) ; return ) ; ## Helper functions ########################################################## (define (__VTable ppVtbl iIdx) (get-int (+ (get-int ppVtbl) (* 4 iIdx))) ) ; Helper function to convert a pointer to a function (define (ptr-to-fn iPtr , fnFuncPtr) ; get function template (set 'fnFuncPtr import) ; change type to library import and OS calling conventions (cpymem (pack "ld" 2312) (first (dump fnFuncPtr)) 4) ; Win32 stdcall ; set code pointer (cpymem (pack "ld" iPtr) (+ (first (dump fnFuncPtr)) 12) 4) fnFuncPtr ) (import "oleaut32" "VariantInit") (import "oleaut32" "VariantClear") (import "oleaut32" "SysFreeString") (import "oleaut32" "SysAllocString") ; @return (success? ret-val errmsg) (define (__SysAllocStringA sAnsi , @sUnicode iBstrPtr) (catch (begin ; convert string to unicode (setq @sUnicode (__ANSI2Unicode sAnsi)) (if (failed? @sUnicode) (throw (return nil ; failed nil (err-msg @sUnicode) ) )) ; convert to BSTR (setq iBstrPtr (SysAllocString (ret-val @sUnicode))) (if (zero? iBstrPtr) ; [then] (return nil ; failed nil (join-err @sUnicode "SysAllocString fail") ) ; [else] (return true ; success iBstrPtr (join-err @sUnicode "SysAllocString ok") ) ) )) ) ; Converts a VARIANT structure to a normal AHK variable. ; Not all VARIANT types are handled. ; @return (success? ret-val errmsg) (define (__UnpackVARIANT sVariantStruct , iVariantType pData s) (setq iVariantType (get-short (address sVariantStruct))) (setq pData (+ (address sVariantStruct) 8)) (case iVariantType ; VT_BSTR (0x0008 (setq s (__Unicode2ANSI (get-int pData))) (VariantClear sVariantStruct) (if (success? s) (return true (ret-val s) (err-msg s)) (return nil nil (err-msg s)) ) ) (0x4008 (setq s (__Unicode2ANSI (get-int (get-int pData)))) (VariantClear sVariantStruct) (if (success? s) (return true (ret-val s) (err-msg s)) (return nil nil (err-msg s)) ) ) ; VT_EMPTY (0x0000 (return true '() '())) ; VT_UI1 (0x0011 (return true (get-char pData) '())) (0x4011 (return true (get-char (get-int pData)) '())) ; VT_I2 (0x0002 (return true (get-short pData) '())) (0x4002 (return true (get-short (get-int pData)) '())) ; VT_I4 (0x0003 (return true (get-int pData) '())) (0x4003 (return true (get-int (get-int pData)) '())) ; VT_R4 (0x0004 (return true (get-single pData) '())) (0x4004 (return true (get-single (get-int pData)) '())) ; VT_R8 (0x0005 (return true (get-float pData) '())) (0x4005 (return true (get-float (get-int pData)) '())) ; VT_BOOL (0x000B (return true (!= 0 (get-short pData)) '())) (0x400B (return true (!= 0 (get-short (get-int pData))) '())) ; VT_ERROR (0x000A (return true (get-int pData) '())) (0x400A (return true (get-int (get-int pData)) '())) ; VT_DISPATCH (0x0009 (return true (get-int pData) '())) (0x4009 (return true (get-int (get-int pData)) '())) ; VT_UNKNOWN (0x000D (return true (get-int pData) '())) (0x400D (return true (get-int (get-int pData)) '())) (true ; Unhandled VARIANT types: ; Array, Currency, Date, VARIANT*, and DECIMAL* (VariantClear sVariantStruct) (return nil nil '("Unhandled variant type")) ) ) ) (define (get-short int-address) (first (unpack "d" int-address)) ) (define (get-single int-address) (first (unpack "f" int-address)) ) (import "kernel32.dll" "MultiByteToWideChar") ; @return (success? ret-val errmsg) (define (__ANSI2Unicode sAnsi , iSize sUtf16) (catch (begin ; TODO: Maybe check if this is utf8 enabled newLISP and convert from utf8 instead of ANSI (setq iSize (MultiByteToWideChar 0 ; from CP_ACP (ANSI) 0 ; no flags sAnsi -1 ; until NULL 0 ; NULL 0)) (if (< iSize 1) (throw (return nil ; fail nil (list (string "MultiByteToWideChar failed to convert string " sAnsi)) ) )) (setq sUtf16 (dup " " (* iSize 2))) (setq iSize (MultiByteToWideChar 0 ; from CP_ACP (ANSI) 0 ; no flags sAnsi -1 ; until NULL sUtf16 iSize)) (if (< iSize 1) (throw (return nil ; fail nil (list (string "MultiByteToWideChar failed to convert string " sAnsi)) ) )) (return true ; success sUtf16 (list "MultiByteToWideChar ok") ) )) ) (import "kernel32.dll" "WideCharToMultiByte") ; @param s|pUtf16 Can pass either a string or integer-pointer to utf16 string. ; @return (success? ret-val errmsg) (define (__Unicode2ANSI s|pUtf16 , iSize sAnsi) (catch (begin ; TODO: Maybe check if this is utf8 enabled newLISP and convert to utf8 instead of ANSI (setq iSize (WideCharToMultiByte 0 ; to CP_API (ANSI) 0 ; no flags s|pUtf16 -1 ; until NULL 0 ; NULL 0 ; Just find length 0 ; NULL 0) ; NULL ) (if (< iSize 1) (throw (return nil nil (list (string "WideCharToMultiByte failed to convert string " s|pUtf16)) ) )) (setq sAnsi (dup " " (+ iSize 1))) (setq iSize (WideCharToMultiByte 0 ; to CP_API (ANSI) 0 ; no flags s|pUtf16 -1 ; until NULL sAnsi iSize 0 ; NULL 0) ; NULL ) (if (< iSize 1) (throw (return nil ; fail nil (list (string "WideCharToMultiByte failed to convert string " s|pUtf16)) ) )) (return true ; success (get-string sAnsi) (list "WideCharToMultiByte ok") ) )) ) ;------------------------------------------------------------------------------- ; The following functions help with error handling. ; ; Most functions return a three item list: ; item 0 success?: true = function succeeded, false = function failed ; item 1 ret-val : on success, the actual return value. on failure, nil ; item 2 err-msg : a list of strings describing results of all win32 api calls ; ; Variables that hold this special type of list are prefixed with ; an at (@) symbol, followed by hungarian notaion of the ; data type found in the second item of the list. (define return list) (define (failed? lst) (not (lst 0)) ) (define (success? lst) (lst 0) ) (define (ret-val lst) (lst 1) ) (define (err-msg lst) (lst 2) ) ; joins all arguments into a list ; if an argument is a list, it is assume to be the ; (success? ret-val err-msg) format, so it only takes the 3rd item in the list. ; Otherwise it just uses the argument as is. (define (join-err) (apply append (map (fn (x) (if (list? x) (err-msg x) (list x)) ) (args) ) ) ) ;------------------------------------------------------------------------------- (context 'MAIN)