Jump to content

Dll and SafeArray


Recommended Posts

I recently added some rgb fans to my desktop and I thought it would be cool if I could control them. So I found the documentation here and starting working on what I hope will be a simple UDF. Unfortunately, I hit a snag with my second function: I don't understand SafeArrays. I read about them from LarsJ's various posts, but (like much of his writing) I failed to comprehend about half of his post.

I'm trying to implement MLAPI_GetDeviceInfo, which (as I understand) accepts two pointers to safe arrays that haven't been filled out yet. I was hoping that I could do something like this:

#include <SafeArray.au3>
#include <Array.au3>

Global __g_hMysicDLL = DLLOpen(@UserProfileDir & "\Downloads\Mystic_light_SDK\MysticLight_SDK.dll")

Local $aRet = DllCall($__g_hMysicDLL, "int", "MLAPI_Initialize")
If $aRet[0] <> 0 Then Exit ConsoleWrite("Failed to init" & @CRLF)

; Create an empty SafeArray of bstr -- DevType seems to be bstr
Local $pDevType = SafeArrayCreateEmptyWr("bstr")
; Create an empty SafeArray of bstr -- LedCount seems to be int
Local $pLedCount = SafeArrayCreateEmptyWr("int")

$aRet = DllCall($__g_hMysicDLL, "int", "MLAPI_GetDeviceInfo", "ptr", $pDevType, "ptr", $pLedCount)
If $aRet[0] <> 0 Then Exit ConsoleWrite("Failed to get device info" & @CRLF)
ConsoleWrite("Success!" & @CRLF)
_ArrayDisplay($aRet[1], "Dev Types")
_ArrayDisplay($aRet[2], "LED Count")

I thought initially that displaying the arrays was the issue, but the code exits silently and the exit code is -1073741783 and sometimes -1073741784... so I'm not sure where I went wrong.

Do I need to provide a pointer to empty space and the dllcall will create the SafeArray for me to read there?

All my code provided is Public Domain... but it may not work. ;) Use it, change it, break it, whatever you want.

Spoiler

My Humble Contributions:
Personal Function Documentation - A personal HelpFile for your functions
Acro.au3 UDF - Automating Acrobat Pro
ToDo Finder - Find #ToDo: lines in your scripts
UI-SimpleWrappers UDF - Use UI Automation more Simply-er
KeePass UDF - Automate KeePass, a password manager
InputBoxes - Simple Input boxes for various variable types

Link to comment
Share on other sites

what is a bstr? I'm pretty familiar with most types and thats a first.  To create a ptr to fill the easiest way that I know is using a dll struct.  obviously the struct needs to be big enough to hold the data being passed to it.  

Local $pDevType = DllStructCreate("char[1028]") ;this is suspect bc bstr? not sure if that stands for binary but not a type i'm aware of

Local $pLedCount = DllStructCreate("int")

$aRet = DllCall($__g_hMysicDLL, "int", "MLAPI_GetDeviceInfo", "ptr", DllStructGetPtr($pDevType), "ptr", DllStructGetPtr($pLedCount))

$dt=DllStructGetData($pDevType,1)
$lc=DllStructGetData($pLedCount,1)

no guarantees that this is error free.  

Link to comment
Share on other sites

On 1/23/2021 at 6:04 AM, seadoggie01 said:

I'm trying to implement MLAPI_GetDeviceInfo, which (as I understand) accepts two pointers to safe arrays

I'm pretty sure MLAPI_GetDeviceInfo() directly returns two safearray pointers.

You can use this code to display the information in the BSTRs in the $pDevType safearray:

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7

#AutoIt3Wrapper_UseX64=y

Opt( "MustDeclareVars", 1 )

#include "Variant.au3"
#include "SafeArray.au3"

ReadSafeArray( $pDevType )

Func ReadSafeArray( $pDevType )
  ; SafeArray elements
  Local $iUBound
  SafeArrayGetUBound( $pDevType, 1, $iUBound )
  If Not $iUBound Then Return ConsoleWrite( "SafeArrayGetUBound ERR" & @CRLF )
  ConsoleWrite( "SafeArrayGetUBound = " & $iUBound & @CRLF )

  ; SafeArray data
  Local $pArrayData
  SafeArrayAccessData( $pDevType, $pArrayData )
  If Not $pArrayData Then Return ConsoleWrite( "SafeArrayAccessData ERR" & @CRLF )
  ConsoleWrite( "SafeArrayAccessData OK" & @CRLF )

  ; Read the BSTRs from the safearray
  ; In this case where the safearray is a safearray of BSTRs, array data is simply three pointers
  Local $tArrayData = DllStructCreate( "ptr[" & $iUBound & "]", $pArrayData )
  ConsoleWrite( "$pBSTR1 = " & SysReadString( DllStructGetData( $tArrayData, 1, 1 ) ) & @CRLF )
  ConsoleWrite( "$pBSTR2 = " & SysReadString( DllStructGetData( $tArrayData, 1, 2 ) ) & @CRLF )
  ConsoleWrite( "$pBSTR3 = " & SysReadString( DllStructGetData( $tArrayData, 1, 3 ) ) & @CRLF )
  ; Continue up to and including $iUBound + 1

  ; Unaccess SafeArray data
  SafeArrayUnaccessData( $pDevType )
EndFunc

Variant and SafeArray UDFs can be downloaded here.

 

Demo code:

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7

#AutoIt3Wrapper_UseX64=y

Opt( "MustDeclareVars", 1 )

#include "Variant.au3"
#include "SafeArray.au3"

Global $pSafeArray



WriteSafeArray()

Func WriteSafeArray()
  ; Create SafeArray
  Local $tsaBound = DllStructCreate( $tagSAFEARRAYBOUND )
  DllStructSetData( $tsaBound, "cElements", 3 )
  DllStructSetData( $tsaBound, "lLbound", 0 )
  $pSafeArray = SafeArrayCreate( $VT_BSTR, 1, $tsaBound )
  If Not $pSafeArray Then Return ConsoleWrite( "SafeArrayCreate ERR" & @CRLF )
  ConsoleWrite( "SafeArrayCreate OK" & @CRLF )

  ; Create three BSTRs (basic strings)
  ; A BSTR is represented by a pointer indicating the memory location where data is stored
  Local $pBSTR1 = SysAllocString( "one" )
  Local $pBSTR2 = SysAllocString( "two" )
  Local $pBSTR3 = SysAllocString( "three" )

  ; SafeArray data
  Local $pArrayData
  SafeArrayAccessData( $pSafeArray, $pArrayData )
  If Not $pArrayData Then Return ConsoleWrite( "SafeArrayAccessData ERR" & @CRLF )
  ConsoleWrite( "SafeArrayAccessData OK" & @CRLF )

  ; Store the BSTRs in the safearray
  ; In this case where the safearray is a safearray of BSTRs, array data is simply three pointers
  Local $tArrayData = DllStructCreate( "ptr[3]", $pArrayData )
  DllStructSetData( $tArrayData, 1, $pBSTR1, 1 )
  DllStructSetData( $tArrayData, 1, $pBSTR2, 2 )
  DllStructSetData( $tArrayData, 1, $pBSTR3, 3 )

  ; Unaccess SafeArray data
  SafeArrayUnaccessData( $pSafeArray )
EndFunc



ReadSafeArray()

Func ReadSafeArray()
  Local $pDevType = $pSafeArray

  ; SafeArray data
  Local $pArrayData
  SafeArrayAccessData( $pDevType, $pArrayData )
  If Not $pArrayData Then Return ConsoleWrite( "SafeArrayAccessData ERR" & @CRLF )
  ConsoleWrite( "SafeArrayAccessData OK" & @CRLF )

  ; Read the BSTRs from the safearray
  ; In this case where the safearray is a safearray of BSTRs, array data is simply three pointers
  Local $tArrayData = DllStructCreate( "ptr[3]", $pArrayData )
  ConsoleWrite( "$pBSTR1 = " & SysReadString( DllStructGetData( $tArrayData, 1, 1 ) ) & @CRLF )
  ConsoleWrite( "$pBSTR2 = " & SysReadString( DllStructGetData( $tArrayData, 1, 2 ) ) & @CRLF )
  ConsoleWrite( "$pBSTR3 = " & SysReadString( DllStructGetData( $tArrayData, 1, 3 ) ) & @CRLF )

  ; Unaccess SafeArray data
  SafeArrayUnaccessData( $pDevType )
EndFunc

 

Link to comment
Share on other sites

I just wanted to post this just to show that the type isn't really super critical unless its a struct or something but even then at the end of the day its still 1s and 0s in there.  The only downside that I came across messing with the following code was that autoit automatically allocates 4 bytes if you pull a byte out of memory.  It may change based on what version of autoit your' using but don't quote me on that.  The following just mimics the way a function would fill a pointer which is just a memcpy.  Then it demonstrates that regardless of the type how to pull the data out.  1 byte can only hold a value between 0-255, which can then in turn be converted to binary, int, char or a string.  now obviously if the data is bigger than 1 byte you can try combinations of different common sizes on different sections to pull out what you need but generally 1,2,4 are the most common. 

 

#include <WinAPI.au3>
#include <Memory.au3>
Global $size
Main()

func Main()
$s=DllStructCreate("byte[1234]")
DllStructSetData($s,1,Binary(104),1);pretend this is the data that will be passed from the function to our pointer..."bstr"
DllStructSetData($s,1,Binary(101),2)
DllStructSetData($s,1,Binary(108),3)
DllStructSetData($s,1,Binary(108),4)
DllStructSetData($s,1,Binary(111),5)


$size=1234


$ss=DllStructCreate("char[1234]") ;allocate memory for our buffer...the type really doesn't matter as long as the size is big enough...
$Dest_ptr=DllStructGetPtr($ss)
$Source_ptr=DllStructGetPtr($s) ;get pointers
$hprocess=0
$iWritten=0

$hprocess=_WinAPI_OpenProcess($PROCESS_ALL_ACCESS,false,@AutoItPID)

if $hprocess then

    for $x=0 to $size-1 ;this is basically a memcopy which is how the buffer would be filled regardless

    _WinAPI_WriteProcessMemory($hprocess,$Dest_ptr,$Source_ptr,1,$iWritten)
    $Dest_ptr+=1
    $Source_ptr+=1

    Next
;start to pull the memory out, via creating a struct with a different tag... at the same pointer as the Dest_ptr bc its a bstr we'll do a byte type

$Dest_ptr=DllStructGetPtr($ss) ;the pointer changed in the last operation so reset it

dim $byteArray[$size]
Local $byte

    for $x=1 to $size

    $sss=DllStructCreate("byte",$Dest_ptr)
    $byte=Binary(DllStructGetData($sss,1))

        if Not int($byte) Then ExitLoop
        $byteArray[$x-1]=$byte
        $Dest_ptr+=1
    Next

Else
    ConsoleWrite("process failed to open")
    exit

EndIf

ConsoleWrite("as Binary (output as hex)"&@CRLF)
PrintByteArrayAsBinary($byteArray)
ConsoleWrite("as Binary (raw bits)"&@CRLF)
PrintByteArrayBits($byteArray)
ConsoleWrite("as ints"&@CRLF)
PrintByteArrayAsInts($byteArray)
ConsoleWrite("as chars"&@CRLF)
PrintByteArrayAsChars($byteArray)
ConsoleWrite("as a string "&@CRLF)
PrintByteArrayAsString($byteArray)

_WinAPI_CloseHandle($hprocess)
EndFunc

func PrintByteArrayAsBinary($byteArray)
    for $x=0 to $size-1
        if Not $byteArray[$x] then ExitLoop
        ConsoleWrite($byteArray[$x]&@CRLF)
    Next
EndFunc

func PrintByteArrayAsInts($byteArray)
    for $x=0 to $size-1
        if Not $byteArray[$x] then ExitLoop
        ConsoleWrite(int($byteArray[$x])&@CRLF)
    Next
EndFunc

func PrintByteArrayAsChars($byteArray)

    for $x=0 to $size-1
        if Not $byteArray[$x] then ExitLoop
        ConsoleWrite(BinaryToString($byteArray[$x]))
        ConsoleWrite(@CRLF)
    Next

EndFunc

func PrintByteArrayAsString($byteArray)
    for $x=0 to $size-1
        if Not $byteArray[$x] then ExitLoop
        ConsoleWrite(BinaryToString($byteArray[$x]))
    Next
    ConsoleWrite(@CRLF)
EndFunc

func PrintByteArrayBits($byteArray)
    for $x=0 to $size-1
        for $y=0 to 7
            if Not int($byteArray[$x]) then ExitLoop
            ConsoleWrite(checkbit($byteArray[$x],$y))
            if $y=3 then ConsoleWrite(" ")
        Next
      if Not int($byteArray[$x]) then ExitLoop
      ConsoleWrite(@CRLF)
    Next
EndFunc

Func checkbit($bp,$bi)

    if $bp = BitOR($bp,BitShift(1,-7+$bi)) Then
        return 1
    EndIf
return 0
EndFunc


;~ output:

;~ as Binary (output as hex)
;~ 0x68000000
;~ 0x65000000
;~ 0x6C000000
;~ 0x6C000000
;~ 0x6F000000
;~ as Binary (raw bits)
;~ 0110 1000
;~ 0110 0101
;~ 0110 1100
;~ 0110 1100
;~ 0110 1111
;~ as ints
;~ 104
;~ 101
;~ 108
;~ 108
;~ 111
;~ as chars
;~ h
;~ e
;~ l
;~ l
;~ o
;~ as a string 
;~ hello

 

Edited by markyrocks
Link to comment
Share on other sites

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
 Share

  • Recently Browsing   0 members

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