ADVANCED: How To Call The Mac OS Directly From LabVIEW Part IV: Pointers in structs
See Part I of this series for the obligatory warning about using the Call Library Function Node. If the warning in Part I gave you reason to pause, this section will really get to you.
Many times when working with data structures, you will have a pointer to data. Take for instance, GetProcessInformation (Apple's Documentation). The ProcessInfoRec (Apple's Documentation) contains a StringPtr (processName) and an FSSpecPtr (processAppSpec). LabVIEW does not allow you to get a pointer to put into these structures, so how do you handle this situation?
(You can see this example already coded up at vi.lib/Platform/ProcessManager.llb/GetProcessInformation.vi)
First, we create a cluster to represent the ProcessInfoRec. The order in which the elements are added to the cluster is important. To ensure that the ordering is what you think it is, right click on the cluster and select AutoSizing -> Arrange Vertically. Add the following to an empty cluster (in this order and set their labels as shown below:
U32 processInfoLength
U32 processName [note really a pointer]
U32 processSerialNumberHigh
U32 processSerialNumberLow
U32 processType
U32 processSignature
U32 processMode
U32 processLocation
U32 processSize
U32 processFreeMem
U32 processLauncherHigh
U32 processLauncherLow
U32 processLaunchDate
U32 processActiveTime
U32 processAppSpec
Take the constant cluster you just created, and use the Type Cast primitive (Programming -> Numeric -> Data Manipulation) to typecast the cluster to an array of bytes (U8[]). Set the processInfoLength field to the Array Size (Programming -> Array) of the cluster as an array using the Bundle By Name primitive (Programming -> Clusters & Variant) with the cluster constant we created as the input cluster.
Next we need to to allocate a buffer to hold the processName. We create a Call Library Function Node (Connectivity -> Libraries & Executables) to call into Carbon.* with the function NewPtr (Apple's Documentation). Wire a 256 constant to the byteCount input. Take the output from NewPtr Call Library Function Node as a U32 and wire it into the cluster we just set the processInfoLength on (you can grow the Bundle By Name primitive to accommodate more items).
We can set up a cluster and do similar handling to allocate space for a File Specification, but to simplify, we will just pass NULL (or zero (0)) to let the call know we are not interested in the location. We'll cover FSSpec handling in a future article.
Create a cluster of two U32s for the ProcessSerialNumber to get information on, and pass it to the first input of the Call Library Function Node for GetProcessInformation. The second input terminal we will wire our initialized cluster into.
Now the cluster coming out has all the information in it. The fun now is to interpret all the information.
Let's start with the easy ones. processType and processSignature are OSTypes. You can take the U32s and use the Type Cast primitive to typecast them directly to strings.
The real easy ones are the numbers that are just numbers.
Then we come to processName. How do we get information out of a pointer? We use the wonderful BlockMove funtion (Apple's Documentation) and GetPtrSize (Apple's Documentation), both in the Carbon.* library.
Since we used NewPtr to allocate the buffer, we can use GetPtrSize to get the size of it (we could just hard code in the size of the data to get out of the pointer).
We set srcPtr in the BlockMove Call Library Function Node paramter to be the U32 from our cluster (the address of the data).
For destPtr, we allocate an array of bytes large enough to hold the data, and wire it in as an array. We now have the array of data out of the pointer. Since it is a PStr, we get the first byte and use it as the string length. We then get that many bytes following the length byte and call the Byte Array To String primitive (Programming -> String -> Conversion) to convert them to a string.
The byteCount can be set to the size of the array passed to destPtr.
Now that we have the data from the pointer, we should call DisposePtr (Apple's Documentation).
The last part to handle is the error code. If it is a zero, it is no error, just return a no error cluster. If it is non-zero, set the error code in the error cluster and return it.
And that is it. You can now handle arbitrary structures with pointers to other structures/data. Now because this is a lot of pointer management, there is a chance for buffer overflow, overwriting memory, memory leaks and other nasty problems. You will likely crash LabVIEW when running VIs developed like this, so save often. you may want to learn how to use gdb to attach to LabVIEW to see how, where and why you crashed.
Next we will look at how to handle constant length arrays in clusters.
Many times when working with data structures, you will have a pointer to data. Take for instance, GetProcessInformation (Apple's Documentation). The ProcessInfoRec (Apple's Documentation) contains a StringPtr (processName) and an FSSpecPtr (processAppSpec). LabVIEW does not allow you to get a pointer to put into these structures, so how do you handle this situation?
(You can see this example already coded up at vi.lib/Platform/ProcessManager.llb/GetProcessInformation.vi)
First, we create a cluster to represent the ProcessInfoRec. The order in which the elements are added to the cluster is important. To ensure that the ordering is what you think it is, right click on the cluster and select AutoSizing -> Arrange Vertically. Add the following to an empty cluster (in this order and set their labels as shown below:
U32 processInfoLength
U32 processName [note really a pointer]
U32 processSerialNumberHigh
U32 processSerialNumberLow
U32 processType
U32 processSignature
U32 processMode
U32 processLocation
U32 processSize
U32 processFreeMem
U32 processLauncherHigh
U32 processLauncherLow
U32 processLaunchDate
U32 processActiveTime
U32 processAppSpec
Take the constant cluster you just created, and use the Type Cast primitive (Programming -> Numeric -> Data Manipulation) to typecast the cluster to an array of bytes (U8[]). Set the processInfoLength field to the Array Size (Programming -> Array) of the cluster as an array using the Bundle By Name primitive (Programming -> Clusters & Variant) with the cluster constant we created as the input cluster.
Next we need to to allocate a buffer to hold the processName. We create a Call Library Function Node (Connectivity -> Libraries & Executables) to call into Carbon.* with the function NewPtr (Apple's Documentation). Wire a 256 constant to the byteCount input. Take the output from NewPtr Call Library Function Node as a U32 and wire it into the cluster we just set the processInfoLength on (you can grow the Bundle By Name primitive to accommodate more items).
We can set up a cluster and do similar handling to allocate space for a File Specification, but to simplify, we will just pass NULL (or zero (0)) to let the call know we are not interested in the location. We'll cover FSSpec handling in a future article.
Create a cluster of two U32s for the ProcessSerialNumber to get information on, and pass it to the first input of the Call Library Function Node for GetProcessInformation. The second input terminal we will wire our initialized cluster into.
Now the cluster coming out has all the information in it. The fun now is to interpret all the information.
Let's start with the easy ones. processType and processSignature are OSTypes. You can take the U32s and use the Type Cast primitive to typecast them directly to strings.
The real easy ones are the numbers that are just numbers.
Then we come to processName. How do we get information out of a pointer? We use the wonderful BlockMove funtion (Apple's Documentation) and GetPtrSize (Apple's Documentation), both in the Carbon.* library.
Since we used NewPtr to allocate the buffer, we can use GetPtrSize to get the size of it (we could just hard code in the size of the data to get out of the pointer).
We set srcPtr in the BlockMove Call Library Function Node paramter to be the U32 from our cluster (the address of the data).
For destPtr, we allocate an array of bytes large enough to hold the data, and wire it in as an array. We now have the array of data out of the pointer. Since it is a PStr, we get the first byte and use it as the string length. We then get that many bytes following the length byte and call the Byte Array To String primitive (Programming -> String -> Conversion) to convert them to a string.
The byteCount can be set to the size of the array passed to destPtr.
Now that we have the data from the pointer, we should call DisposePtr (Apple's Documentation).
The last part to handle is the error code. If it is a zero, it is no error, just return a no error cluster. If it is non-zero, set the error code in the error cluster and return it.
And that is it. You can now handle arbitrary structures with pointers to other structures/data. Now because this is a lot of pointer management, there is a chance for buffer overflow, overwriting memory, memory leaks and other nasty problems. You will likely crash LabVIEW when running VIs developed like this, so save often. you may want to learn how to use gdb to attach to LabVIEW to see how, where and why you crashed.
Next we will look at how to handle constant length arrays in clusters.
0 Comments:
Post a Comment
<< Home