Jump to content

Recommended Posts

Posted

I’m thrilled to introduce my IStream UDF, a sophisticated library designed to handle the COM IStream interface in AutoIt. This UDF enables management of data streams, whether from files, memory, or other sources, leveraging Windows IStream APIs. It provides a reliable solution for reading, writing, and administering streams in your AutoIt scripts. Whether you need to process large files, manipulate in-memory data, or handle transactional streams, this UDF is crafted to be versatile, well-documented, and user-friendly.Key Features
  • Full Implementation of the IStream Interface: Supports all methods of the IStream interface (Read, Write, Seek, SetSize, CopyTo, Commit, Revert, LockRegion, UnlockRegion, Stat, Clone).
  • Support for File and Memory Streams: Create streams from files using _SHCreateStreamOnFileEx or from in-memory data with _StreamCreateFromData, _SHCreateMemStream, and _StreamCreateFromDataOnHGlobal.
  • Advanced Error Handling: Includes a detailed HRESULT error table ($tagIStreamErrorTable) and a _IStream_GetErrorInfo function for clear, understandable error messages.
  • Utility Functions: Functions like _StreamGetSize, _StreamGetName, _StreamGetType, and _StreamStatDisplay simplify access to stream metadata.
  • Comprehensive Documentation: Each function comes with standard UDF-format documentation, including syntax, parameters, return values, remarks, MSDN links, and practical examples.
  • Compatibility: Works with AutoIt 3.3+ and relies on standard libraries (AutoItObject, WinAPI, Memory, Date).
Use Cases
  • Reading and writing large files without loading their entire content into memory.
  • Handling in-memory binary data for applications such as network stream processing or serialized data manipulation.
  • Supporting transactional streams for secure operations (e.g., via StgCreateDocfile).
  • Integration with other COM interfaces requiring IStream.

you need: AutoItObject.au3

Example:  copy data between streams

#include "IStream.au3"

; Example demonstrating the use of _StreamCopyToEx to copy data between streams
Func Example_StreamCopyToEx()
    
    ; Create a source stream with the content "Strong" using _StreamCreateFromDataOnHGlobal
    ConsoleWrite("Creating source stream with 'Strong'..." & @CRLF)
    Local $iSrcSize,$aError
    Local $oSrcStream = _StreamCreateFromDataOnHGlobal("Strong", $iSrcSize, True)
    If @error Or Not IsObj($oSrcStream) Then
        ; Handle errors by displaying the HRESULT code and details from $tagIStreamErrorTable
        ConsoleWrite("Error: Failed to create source stream. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        $aError = _IStream_GetErrorInfo(@extended)
        If IsArray($aError) Then
            ConsoleWrite("Name: " & $aError[0] & @CRLF)
            ConsoleWrite("Description: " & $aError[1] & @CRLF)
            ConsoleWrite("Affected methods: " & $aError[2] & @CRLF)
        EndIf
    
        Return
    EndIf
    ConsoleWrite("Source stream created, size: " & $iSrcSize & " bytes" & @CRLF)

    ; Create a destination stream with the content "AutoIt is " using _StreamCreateFromDataOnHGlobal
    ConsoleWrite("Creating destination stream with 'AutoIt is '..." & @CRLF)
    Local $iDestSize
    Local $oDestStream = _StreamCreateFromDataOnHGlobal("AutoIt is ", $iDestSize, True)
    If @error Or Not IsObj($oDestStream) Then
        ; Handle errors for destination stream creation
        ConsoleWrite("Error: Failed to create destination stream. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        $aError = _IStream_GetErrorInfo(@extended)
        If IsArray($aError) Then
            ConsoleWrite("Name: " & $aError[0] & @CRLF)
            ConsoleWrite("Description: " & $aError[1] & @CRLF)
            ConsoleWrite("Affected methods: " & $aError[2] & @CRLF)
        EndIf
        ; Release the source stream and COM resources before exiting
        _StreamRelease($oSrcStream)
        
        Return
    EndIf
    ConsoleWrite("Destination stream created, size: " & $iDestSize & " bytes" & @CRLF)

    ; Copy the content from the source stream to the destination stream using _StreamCopyToEx
    ConsoleWrite("Copying 'Strong' to destination stream..." & @CRLF)
    Local $iBytesRead, $iBytesWritten
    If Not _StreamCopyToEx($oSrcStream, $oDestStream, $iSrcSize, $iBytesRead, $iBytesWritten) Then
        ; Handle errors during the copy operation
        ConsoleWrite("Error: _StreamCopyToEx failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        $aError = _IStream_GetErrorInfo(@extended)
        If IsArray($aError) Then
            ConsoleWrite("Name: " & $aError[0] & @CRLF)
            ConsoleWrite("Description: " & $aError[1] & @CRLF)
            ConsoleWrite("Affected methods: " & $aError[2] & @CRLF)
        EndIf
        ; Release both streams and COM resources before exiting
        _StreamRelease($oDestStream)
        _StreamRelease($oSrcStream)
        
        Return
    EndIf
    ConsoleWrite("Copied " & $iBytesRead & " bytes read, " & $iBytesWritten & " bytes written." & @CRLF)

    ; Read and display the content of the destination stream
    ConsoleWrite("Reading destination stream content..." & @CRLF)
    Local $iNewDestSize = _StreamGetSize($oDestStream)
    If @error Then
        ; Handle errors when retrieving the destination stream size
        ConsoleWrite("Error: Failed to get destination stream size. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        $aError = _IStream_GetErrorInfo(@extended)
        If IsArray($aError) Then
            ConsoleWrite("Name: " & $aError[0] & @CRLF)
            ConsoleWrite("Description: " & $aError[1] & @CRLF)
            ConsoleWrite("Affected methods: " & $aError[2] & @CRLF)
        EndIf
        ; Release both streams and COM resources before exiting
        _StreamRelease($oDestStream)
        _StreamRelease($oSrcStream)
        
        Return
    EndIf
    ConsoleWrite("Destination stream size after copy: " & $iNewDestSize & " bytes" & @CRLF)

    ; Position the stream pointer at the beginning for reading
    Local $pDestMem, $iDestRead
    _StreamSeek($oDestStream, 0, $STREAM_SEEK_SET)
    If @error Then
        ; Handle errors when seeking in the destination stream
        ConsoleWrite("Error: Failed to seek destination stream. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        $aError = _IStream_GetErrorInfo(@extended)
        If IsArray($aError) Then
            ConsoleWrite("Name: " & $aError[0] & @CRLF)
            ConsoleWrite("Description: " & $aError[1] & @CRLF)
            ConsoleWrite("Affected methods: " & $aError[2] & @CRLF)
        EndIf
        _StreamRelease($oDestStream)
        _StreamRelease($oSrcStream)
        
        Return
    EndIf

    ; Read the content of the destination stream
    _StreamRead($oDestStream, $iNewDestSize, $pDestMem, $iDestRead)
    If @error Then
        ; Handle errors when reading the destination stream
        ConsoleWrite("Error: Failed to read destination stream. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        $aError = _IStream_GetErrorInfo(@extended)
        If IsArray($aError) Then
            ConsoleWrite("Name: " & $aError[0] & @CRLF)
            ConsoleWrite("Description: " & $aError[1] & @CRLF)
            ConsoleWrite("Affected methods: " & $aError[2] & @CRLF)
        EndIf
        ; Free the memory buffer if allocated, then release streams and COM resources
        If $pDestMem Then _MemGlobalFree($pDestMem)
        _StreamRelease($oDestStream)
        _StreamRelease($oSrcStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf

    ; Convert the read data to a string and display it
    Local $bufSize
    Local $bDestData = _WinAPI_GetBufferData($pDestMem, $bufSize)
    ConsoleWrite("Destination stream content: " & BinaryToString($bDestData, 4) & @CRLF)
    ; Free the memory buffer
    If $pDestMem Then _MemGlobalFree($pDestMem)

    ; Release both streams to prevent memory leaks
    _StreamRelease($oDestStream)
    _StreamRelease($oSrcStream)
   
    ConsoleWrite("COM uninitialized, resources freed." & @CRLF)
EndFunc   ;==>Example_StreamCopyToEx

; Run the example
Example_StreamCopyToEx()

 

example demonstrating multiple IStream interface methods

#include "IStream.au3"

; Example demonstrating multiple IStream interface methods
Func Example_IStream()
    ; Initialize the COM object model (required for COM API calls)
    _WinAPI_CoInitialize()
    ConsoleWrite("=== Creating Stream with SHCreateMemStream ===" & @CRLF)

    ; Create a memory stream with initial content "Hello, IStream UDF!"
    Local $sData = "Hello, IStream UDF!"
    Local $iSize = 0
    Local $oStream = _StreamCreateFromData($sData, $iSize)
    If @error Then
        ; Handle stream creation error and display details
        ConsoleWrite("Error: Failed to create stream. @error = " & @error & @CRLF)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ConsoleWrite("Stream created successfully. Size: " & $iSize & " bytes" & @CRLF)

    ; Test IUnknown::QueryInterface to verify the IStream interface
    ConsoleWrite("Testing QueryInterface..." & @CRLF)
    Local $IID_IStream = "{0000000C-0000-0000-C000-000000000046}"
    Local $pQueriedStream = _StreamQueryInterface($oStream, $IID_IStream)
    If @error Then
        ; Handle QueryInterface error and release resources
        ConsoleWrite("Error: QueryInterface failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ConsoleWrite("QueryInterface succeeded." & @CRLF)
    ; Release the queried interface to prevent memory leaks
    DllCall("ole32.dll", "ulong", "Release", "ptr", $pQueriedStream)

    ; Test IStream::Write by appending additional data
    ConsoleWrite("Testing Write..." & @CRLF)
    Local $sNewData = " Additional data." ; 16 characters
    Local $iNewSize = 0
    Local $pMemory = _StreamCreateMemoryBuffer($sNewData, $iNewSize)
    If @error Then
        ; Handle memory buffer creation error
        ConsoleWrite("Error: Failed to create memory buffer. @error = " & @error & @CRLF)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    Local $iWrittenSize = 0
    ; Set the stream size to accommodate new data
    If Not _StreamSetSize($oStream, $iSize + $iNewSize) Then
        ConsoleWrite("Error: SetSize failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        _WinAPI_FreeMemory($pMemory)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ; Move the stream pointer to the end for appending
    If Not _StreamSeek($oStream, $iSize, $STREAM_SEEK_SET) Then
        ConsoleWrite("Error: Seek failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        _WinAPI_FreeMemory($pMemory)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ; Write the new data to the stream
    If Not _StreamWrite($oStream, $pMemory, $iNewSize, $iWrittenSize) Then
        ConsoleWrite("Error: Write failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        _WinAPI_FreeMemory($pMemory)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ConsoleWrite("Wrote " & $iWrittenSize & " bytes." & @CRLF)
    ; Free the memory buffer
    _WinAPI_FreeMemory($pMemory)

    ; Test IStream::Seek to move the pointer to the beginning
    ConsoleWrite("Testing Seek..." & @CRLF)
    Local $iNewPosition = _StreamSeek($oStream, 0, $STREAM_SEEK_SET)
    If @error Then
        ConsoleWrite("Error: Seek failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ConsoleWrite("Stream pointer moved to: " & $iNewPosition & @CRLF)

    ; Test IStream::Read to retrieve the entire stream content
    ConsoleWrite("Testing Read..." & @CRLF)
    Local $pMemoryRead = 0, $iReadSize = 0
    If Not _StreamRead($oStream, $iSize + $iNewSize, $pMemoryRead, $iReadSize) Then
        ConsoleWrite("Error: Read failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        _WinAPI_FreeMemory($pMemoryRead)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    Local $bData = _WinAPI_GetBufferData($pMemoryRead, $iReadSize)
    ConsoleWrite("Read " & $iReadSize & " bytes: " & BinaryToString($bData) & @CRLF)
    _WinAPI_FreeMemory($pMemoryRead)
    ; Verify that the read data matches the expected content
    If $iReadSize <> $iSize + $iNewSize Or BinaryToString($bData) <> $sData & $sNewData Then
        ConsoleWrite("Error: Read data does not match expected: " & $sData & $sNewData & @CRLF)
    EndIf

    ; Test IStream::CopyTo by copying the stream to a new destination stream
    ConsoleWrite("Testing CopyTo..." & @CRLF)
    ; Create a destination stream
    Local $pDestStream = _WinAPI_CreateStreamOnHGlobal(0, True)
    If Not $pDestStream Then
        ConsoleWrite("Error: Failed to create destination stream." & @CRLF)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ; Move the source stream pointer to the beginning
    $iNewPosition = _StreamSeek($oStream, 0, $STREAM_SEEK_SET)
    If @error Then
        ConsoleWrite("Error: Seek failed on source stream. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        DllCall("ole32.dll", "ulong", "Release", "ptr", $pDestStream)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ; Copy the content to the destination stream
    Local $iBytesRead = 0, $iBytesWritten = 0
    If Not _StreamCopyTo($oStream, $pDestStream, $iSize + $iNewSize, $iBytesRead, $iBytesWritten) Then
        ConsoleWrite("Error: CopyTo failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        DllCall("ole32.dll", "ulong", "Release", "ptr", $pDestStream)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ConsoleWrite("Copied " & $iBytesRead & " bytes read, " & $iBytesWritten & " bytes written." & @CRLF)
    ; Create an IStream object from the destination stream pointer
    Local $oDestStream = _AutoItObject_WrapperCreate($pDestStream, $tagIStream)
    If @error Then
        ConsoleWrite("Error: Failed to create destination stream object. @error = " & @error & @CRLF)
        DllCall("ole32.dll", "ulong", "Release", "ptr", $pDestStream)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ; Read and verify the destination stream content
    _StreamSeek($oDestStream, 0, $STREAM_SEEK_SET)
    Local $pDestMemory, $iDestReadSize
    If Not _StreamRead($oDestStream, $iSize + $iNewSize, $pDestMemory, $iDestReadSize) Then
        ConsoleWrite("Error: Reading destination stream failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        _WinAPI_FreeMemory($pDestMemory)
        _StreamRelease($oDestStream)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    Local $bDestData = _WinAPI_GetBufferData($pDestMemory, $iDestReadSize)
    ConsoleWrite("Destination stream content: " & BinaryToString($bDestData, $SB_UTF8) & @CRLF)
    _WinAPI_FreeMemory($pDestMemory)
    ; Verify that the copied data matches the expected content

    ; Test IStream::Commit
    ConsoleWrite("Testing Commit..." & @CRLF)
    If Not _StreamCommit($oDestStream, $STGC_DEFAULT) Then
        ConsoleWrite("Error: Commit failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
    Else
        ConsoleWrite("Commit succeeded." & @CRLF)
    EndIf

    ; Test IStream::Revert
    ConsoleWrite("Testing Revert..." & @CRLF)
    If Not _StreamRevert($oDestStream) Then
        ; Note that Revert may not be supported by memory streams

        ConsoleWrite("Note: Revert may not be supported. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
    Else
        ConsoleWrite("Revert succeeded (unexpected for memory stream)." & @CRLF)
    EndIf

    ; Test IStream::LockRegion and UnlockRegion
    ConsoleWrite("Testing LockRegion..." & @CRLF)
    If Not _StreamLockRegion($oStream, 0, 10, 0) Then
        ; Note that LockRegion may not be supported by memory streams
        ConsoleWrite("Note: LockRegion may not be supported. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        __IStreamErrorInfo(@extended)
    Else
        ConsoleWrite("LockRegion succeeded." & @CRLF)
        ConsoleWrite("Testing UnlockRegion..." & @CRLF)
        If Not _StreamUnlockRegion($oStream, 0, 10, 0) Then
            ConsoleWrite("Error: UnlockRegion failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        Else
            ConsoleWrite("UnlockRegion succeeded." & @CRLF)
        EndIf
    EndIf

    ; Test IStream::Stat to retrieve stream metadata
    ConsoleWrite("Testing GetStat..." & @CRLF)
    Local $pStatFlag = 0
    If Not _StreamGetStat($oStream, $pStatFlag) Then
        ConsoleWrite("Error: GetStat failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        _StreamRelease($oDestStream)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ConsoleWrite("Displaying stream metadata:" & @CRLF)
    _StreamStatDisplay($pStatFlag)
    _WinAPI_CoTaskMemFree($pStatFlag)

    ; Test IStream::GetType and GetName
    ConsoleWrite("Testing GetType and GetName..." & @CRLF)
    Local $sType = _StreamGetType($oStream)
    If @error Then
        ConsoleWrite("Error: GetType failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
    Else
        ConsoleWrite("Stream type: " & $sType & @CRLF)
    EndIf
    Local $sName = _StreamGetName($oStream)
    If @error Then
        ConsoleWrite("Error: GetName failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
    Else
        ConsoleWrite("Stream name: " & $sName & @CRLF)
    EndIf

    ; Test IStream::Clone
    ConsoleWrite("Testing Clone..." & @CRLF)
    Local $pCloned
    Local $oCloneStream = _StreamClone($oStream, $pCloned)
    If @error Then
        ConsoleWrite("Error: Clone failed. @error = " & @error & ", @extended = 0x" & Hex(@extended, 8) & @CRLF)
        _StreamRelease($oDestStream)
        _StreamRelease($oStream)
        _WinAPI_CoUninitialize()
        Return
    EndIf
    ConsoleWrite("Clone stream created successfully." & @CRLF)
    _StreamRelease($oCloneStream)
    DllCall("ole32.dll", "ulong", "Release", "ptr", $pCloned)

    ; Clean up all resources
    _StreamRelease($oDestStream)
    _StreamRelease($oStream)
    _WinAPI_CoUninitialize()
    ConsoleWrite("All streams released and COM uninitialized." & @CRLF)
EndFunc   ;==>Example_IStream

; Run the example
Example_IStream()

 

The help file is included with the UDF

IStream.zip

Posted
2 hours ago, argumentum said:

Nice !.
The $tagIStreamErrorTable is in French. I don't mind because am a polyglot ( via google ) but in English would be better :) 

Shall we do a copy-paste? 😁
 
; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: $tagIStreamErrorTable
; Description ...: 2D array containing possible HRESULT error codes for the IStream interface, along with their symbolic names and descriptions.
; Fields ........: HRESULT (hex) | Symbolic name | Description | Affected methods
; Author ........: Numeric1
; Modified ......: 2025-07-27
; Remarks .......: HRESULT codes starting with 0x8003 are specific to storage errors (STG_E_*). Use this array to translate HRESULT codes returned by IStream methods.
; Related .......: IStream UDF
; ===============================================================================================================================
Global Const $tagIStreamErrorTable = [ _
        ["0x80030001", "STG_E_INVALIDFUNCTION", "Function invalid for this stream type (e.g., Revert on a non-transactional stream).", "Revert, Commit, LockRegion, UnlockRegion"], _
        ["0x80030002", "STG_E_FILENOTFOUND", "The specified file or stream does not exist.", "Stat"], _
        ["0x80030008", "STG_E_TOOMANYOPENFILES", "Too many files or streams are open.", "Open, Create"], _
        ["0x8003001D", "STG_E_INVALIDPOINTER", "Invalid pointer passed to the method.", "Read, Write, Seek, SetSize, CopyTo, Stat, Clone"], _
        ["0x80030020", "STG_E_LOCKVIOLATION", "Lock violation (e.g., accessing a locked region).", "Read, Write, LockRegion, UnlockRegion"], _
        ["0x80030021", "STG_E_FILEALREADYEXISTS", "The stream or file already exists.", "Create"], _
        ["0x80030050", "STG_E_INVALIDNAME", "Invalid stream name.", "Stat"], _
        ["0x80030057", "STG_E_INVALIDPARAMETER", "Invalid parameter passed to the method.", "Seek, CopyTo, Commit, LockRegion, UnlockRegion"], _
        ["0x80030070", "STG_E_MEDIUMFULL", "The storage medium is full.", "Write, SetSize, CopyTo"], _
        ["0x800300F0", "STG_E_READFAULT", "Error reading from the stream.", "Read, CopyTo"], _
        ["0x800300FA", "STG_E_WRITEFAULT", "Error writing to the stream.", "Write, CopyTo, SetSize"], _
        ["0x80030102", "STG_E_REVERTED", "The stream was reverted and is no longer valid.", "Read, Write, Seek"], _
        ["0x80030103", "STG_E_NOTCURRENT", "The stream has been modified since the last operation.", "Commit, Revert"], _
        ["0x80030104", "STG_E_CANTSAVE", "Unable to save changes to the stream.", "Commit"], _
        ["0x8007000E", "E_OUTOFMEMORY", "Insufficient memory to perform the operation.", "Read, Write, SetSize, CopyTo, Clone"], _
        ["0x80070057", "E_INVALIDARG", "One or more arguments are invalid.", "Read, Write, Seek, SetSize, CopyTo, Commit, LockRegion, UnlockRegion, Stat, Clone"], _
        ["0x80004001", "E_NOTIMPL", "The method is not implemented for this stream type (e.g., LockRegion on a memory stream).", "LockRegion, UnlockRegion"], _
        ["0x80004005", "E_FAIL", "Unspecified operation failure.", "All methods"], _
        ["0x80030006", "STG_E_INSUFFICIENTMEMORY", "Insufficient memory for the operation (similar to E_OUTOFMEMORY).", "Read, Write, SetSize, CopyTo, Clone"] _
        ]

thanks

Posted

🔧 Proposal: COM Interface Validation via QueryInterface (_IsObjType, _IsStream)

Hi everyone 👋,

In the IStream.au3 UDF I recently shared, some internal functions rely on IsObj() to validate the COM objects passed as parameters. While convenient, this check does not guarantee that the object truly implements the expected COM interface (e.g., IStream).

I’m suggesting a more robust alternative that directly uses the COM mechanism QueryInterface() to verify if a given object supports a specific interface via its Interface Identifier (IID).

Goal

- Ensure a COM object actually implements the required interface (IStream, IMarshal, IPersistStream, etc.).
- Avoid runtime crashes when calling methods on unsupported interfaces.
- Follow COM best practices: validate interface compatibility through QueryInterface() rather than relying only on IsObj().

 

🔍 Difference: IsObj() vs _IsObjType()

; Comparison Table: IsObj() vs _IsObjType()

; +----------------+-------------------+--------------+---------------------------------------------+
; |   Check Type   |   Reliability     | Performance  |                   Comment                   |
; +----------------+-------------------+--------------+---------------------------------------------+
; |   IsObj()      | Basic (any object)|   Fast       | Does NOT verify interface type              |
; | _IsObjType()   | Strong (uses QI)  |   Slower     | Uses QueryInterface for real type checking  |
; +----------------+-------------------+--------------+---------------------------------------------+


 

⚠️ Performance in loops

Using QueryInterface() internally, _IsObjType() is naturally more expensive than IsObj():
- It converts the IID to a binary structure
- Calls the COM method QueryInterface
- Releases the interface pointer safely to avoid memory leaks

This performance impact is usually negligible in typical use.  
However, within loops (e.g. enumerating files or objects via IEnumSTATSTG, IEnumUnknown), repeatedly calling _IsObjType() could slow down execution.

💡 In such cases:
- Perform the check once before the loop
- Or cache the result locally

 

🔧 Main Function: _IsObjType()

Func _IsObjType(ByRef $oObject, $sIID, $blTag = True)
    If Not IsObj($oObject) Then Return SetError(1, 0, 0)
    If Not IsString($sIID) Or $sIID = "" Then Return SetError(2, 0, 0)

    Local $tRIID = _WinAPI_GUIDFromString($sIID)
    If Not IsDllStruct($tRIID) Then Return SetError(3, 0, 0)

    Local $aCall = ($blTag) ? _
        $oObject.QueryInterface("hresult", "ptr", DllStructGetPtr($tRIID), "ptr*", 0) : _
        $oObject.QueryInterface(DllStructGetPtr($tRIID), 0)

    If @error Then
        If Not IsArray($aCall) Then Return SetError(4, @error, 0)
        If $aCall[0] <> 0 Then Return SetError(5, $aCall[0], 0)
    EndIf

    Local $pInterface = $aCall[2]
    If $pInterface Then
        DllCall("ole32.dll", "ulong", "Release", "ptr", $pInterface)
        Return 1
    EndIf
    Return SetError(6, 0, 0)
EndFunc

 

🔁 _IsStream() simplified example

Func _IsStream(ByRef $oObject)
    Return _IsObjType($oObject, "{0000000C-0000-0000-C000-000000000046}")
EndFunc

---

🔗 Suggested Integration in UDF

I recommend using these functions instead of IsObj() in your COM wrappers (especially those expecting IStream or other specific interfaces).  
For example, replace:

If Not IsObj($oStream) Then Return SetError(1, 0, 0)


with:
 

If Not _IsStream($oStream) Then Return SetError(1, 0, 0)

 

📎 References
- IUnknown::QueryInterface (MSDN)
- IStream interface (MSDN)

 

What do you think?  
Should I integrate these improvements into the next version of the IStream.au3 UDF?  
All feedback and suggestions are welcome! 🙏
 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...