Extract data from memoryblock

To extract data from a windows function I made the following function:

if System.IsFunctionAvailable("GetSystemPowerStatus","Kernel32.dll") then
  Soft Declare Function SystemPowerStatus lib "Kernel32.dll" Alias "GetSystemPowerStatus" (ByRef Result As Ptr) as boolean
  Var Result as Ptr
  Var MB as new MemoryBlock(8)
  if SystemPowerStatus(Result) then
    'Pointer to memoryblock
    MB = Result
    
    'With these lines the app shuts down
    Var ACLineStatus as byte = mb.UInt8Value(0)
    Var BatteryStatus as byte = mb.UInt8Value(1)
    Var BatteryPercentage as byte = mb.UInt8Value(2)
    Var BatteryPowerSavingStatus as byte = mb.UInt8Value(3)
    Var BatteryLifeTime as UInt16 = mb.UInt16Value(4)
    Var BatteryTotalLifeTime as Uint16 = mb.UInt16Value(6)
      end if
end if

This doesn’t work and it closes my program without any error

Where am I going wrong?

Define “doesn’t work”. And all these Vars you declare (such as BatteryStatus). Are you expecting to have access to them outside the function?

GetSystemPowerStatus doesn’t allocate memory for the data, it expects you to provide a memoryblock for it to populate. So instead of passing a Ptr byref, pass the memoryblock directly:

Soft Declare Function SystemPowerStatus lib "Kernel32.dll" Alias "GetSystemPowerStatus" (Result As Ptr) as boolean
Var Result as New MemoryBlock(16) ' memoryblock large enough to contain the data
if SystemPowerStatus(Result) then ' populate memoryblock
  Var ACLineStatus as byte = Result.UInt8Value(0) '  read from memoryblock
1 Like

I suspect @Andrew_Lambert has the right answer.

Think of it this way. In Xojo…

  • A Pointer (Ptr) points to a memory block
  • A ByRef variable in a Declare is passing a Pointer to the variable. [This allows the calling function to change the value of the variable.]

Therefore, passing a ByRef Ptr is passing a Pointer to a Pointer to a memory block.

While this pattern is sometimes used, it’s rare, and would only be used if the function you call is allocating the memory for you.

Another hint:

This kind of code will work:

 Var ACLineStatus as byte = mb.UInt8Value(0)
 Var BatteryStatus as byte = mb.UInt8Value(1)
 Var BatteryPercentage as byte = mb.UInt8Value(2)
 Var BatteryPowerSavingStatus as byte = mb.UInt8Value(3)
 Var BatteryLifeTime as UInt16 = mb.UInt16Value(4)

But there’s a much easier way to do it. Define a Structure Structure — Xojo documentation and you can then define each field in the structure (e.g. ACLineStatus is a UInt8 at offset 0…)

#tag Structure, Name = SYSTEM_POWER_STATUS, Flags = &h1
  ACLineStatus as Uint8
  BatteryFlag as Uint8
  BatteryLifePercent as Uint8
  BatteryLifeTime as Uint16
  BatteryFullLifeTime as UINT16
#tag EndStructure

This makes accessing the structure much easier, as you can simply use dot notation for each field:

 Var Result as New MemoryBlock(16)
if SystemPowerStatus(Result) then ' copy to structure
  var sps as SYSTEM_POWER_STATUS = result
 
  if sps.ACLineStatus = 0 and sps.BatteryStatus = 0 then
  [  etc. ]
1 Like

Also, if you define a structure then you can pass it directly to the function, bypassing the memoryblock entirely. Although, in this case, you do pass it ByRef:

Soft Declare Function SystemPowerStatus lib "Kernel32.dll" Alias "GetSystemPowerStatus" (ByRef Result As SYSTEM_POWER_STATUS) as boolean
Var Result as SYSTEM_POWER_STATUS
if SystemPowerStatus(Result) then
  Var ACLineStatus as byte = Result.ACLineStatus
1 Like

I’m sorry, my brain isn’t braining right now. Why would you pass it ByRef? The declare will receive it as a pointer to a section of memory and fill it out. The only reason for ByRef is if the declare is going to allocate it’s own memory and return a pointer to that. But maybe I’m really confused.

From the Xojo docs on Structures:

ByRef works the same with Structure as it does with other non-intrinsic data types: a ByRef Structure passes the pointer to the Structure.

I’m not sure how Xojo passes a ByVal structure to a declare, but ByVal crashes in this case presumably from a memory access violation.

Thanks everyone for the suggestions.
Using structures doesn’ t give good results.
After various failed attempts I wrote a working function.
It is OK but I ask myself if there is a less complicated procedure to do this.

The code:

'Load System power info
Try
  
  If System.IsFunctionAvailable("GetSystemPowerStatus","Kernel32.dll") Then
    Declare Function SystemPowerStatus Lib "Kernel32.dll" Alias "GetSystemPowerStatus" (ByRef Result As Ptr) As Boolean
    Var Result As Ptr
    Var Looper As Integer
    
    If SystemPowerStatus(Result) Then
      Var VariantResult As Variant = Result
      Var StringArray() As String = VariantResult.StringValue.SplitBytes("")
      
      Var MB As New MemoryBlock((StringArray.LastIndex+1)/2)
      MB.LittleEndian = False
      
      Var TempHex As String
      Var MBIndex As Integer = ((StringArray.LastIndex+1)/2)-1
      
      'Convert hex characters to byte value and mirroring the sequence
      For looper = 0 To StringArray.LastIndex Step 2
        TempHex = StringArray(looper) + StringArray(looper+1)
        MB.UInt8Value(MBIndex) = UInt8.FromHex(TempHex)
        MBIndex = MBIndex -1
      Next
      
      'Get data
      Var ACLineStatus As Byte = MB.UInt8Value(0)
      Var BatteryStatus As Byte = MB.UInt8Value(1)
      Var BatteryPercentage As Byte = MB.UInt8Value(2)
      Var BatteryPowerSavingStatus As Byte = MB.UInt8Value(3)
      Var BatteryLifeTime As Int16 = MB.Int16Value(4)
      Var BatteryTotalLifeTime As Int16 = MB.Int16Value(6)
      
    End If
  End If
Catch
End Try

Here’s code that I’ve tested to work:

#if TargetWindows
  
  var sps as SYSTEM_POWER_STATUS
  
  Soft Declare Function SystemPowerStatus lib "Kernel32.dll" Alias "GetSystemPowerStatus" (Byref Result As SYSTEM_POWER_STATUS) as boolean
  
  if not SystemPowerStatus(sps) then
    MessageBox "SPS Failed"
    return
  end if
  
  messageBox "SPS.BatteryPercentage = " + sps.BatteryLifePercent.ToString
  
  
#endif

There was an error above, make sure SYSTEM_POWER_STATUS Structure is defined like this:


Structure SYSTEM_POWER_STATUS
  ACLineStatus as Uint8
  BatteryFlag as Uint8
  BatteryLifePercent as Uint8
  SystemStatusFlag as Uint8
  BatteryLifeTime as Uint16
  BatteryFullLifeTime as Uint16
End Structure
1 Like

Thanks for the clarification. I assumed structures worked like memoryblocks.

1 Like

Me too !

I agree it’s a little confusing - I think the reason that you have to use

ByRef Result As SYSTEM_POWER_STATUS

is that a Structure could be from 1 to 8 bytes in length, in which case the compiler could just pass the contents of the structure by Value (as a 32 or 64 bit integer).

Most structures are bigger of course, but the semantics of allowing both ByValue and ByRef makes sense.