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
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
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 StructureStructure — 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. ]
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
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.
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
#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
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.