The MacView

Virtual Instrumentation from a Mac perspective

My Photo
Name:
Location: Pflugerville, TX, United States

Friday, January 27, 2006

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.

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.


Friday, January 20, 2006

ADVANCED: How To Call The Mac OS Directly From LabVIEW Part III: Simple Parameter Types

See Part I of this series for the obligatory warning about using the Call Library Function Node.

Let's look at another example that shipped with LabVIEW 8.0. In vi.lib/Platform/Miscellaneous.llb, there is a VI, Check Connection To DNS Address.vi. This VI wraps a call to SCNetworkCheckReachabilityByName .

As you can see, the parameters for this call are a little more complicated. First, double click on the Call Library Function Node to see how it is configured. You can see that we are calling into SystemConfiguration.framework (as the documentation above states).

Notice the Parameter return value is Boolean. This string is the label of the return terminal on the Node. I used Boolean here so that the return value type would be more apparent. However, I did make a mistake. If you look in MacTypes.h, you'll notice that a Boolean is defined to be an unsigned char (or Unsigned 8-bit integer), however, I had the return type declared as an Unsigned 32-bit Integer. Luckily it just happens to work in this case, but it will be changed in a future release.

To look at the other parameters you can click on the pop-up next to Parameter. You should see three items in the list (the return type and the two parameters). Go ahead and select DNSName. Notice it is a C String Pointer. When you look at Apple's Documentation, it has a const char *nodename parameter, which is a C String Pointer.

Now lets look at the flags parameter. Notice it is a Pointer to Value. If you look at Apple's Documentation, it says that last parameter is a SCNetworkConnectionFlags, but doesn't say what a SCNetworkConnectionFlags is. If you look in the header file (SCNetwork.h), you can see that it is defined to be a uint32_t (Unsigned 32-bit Integer), and it is passed by pointer.

Now if you look at the dialog (cancel the configuration dialog), you can see that we can just wire a String into the first parameter. The second parameter is an out only parameter, and it is suggested you wire an invalid/NULL/zero value to out inputs.

We use similar techniques to the ones used in the previous post to get the bits out to return. Note that the Index Array primitive (Programming -> Array) starts at zero be default and increments by one. The last two bits we get out are 16 and 17, so we skip to 16 and the next one would be 17.

In part IV, we will look at complex data types passed into and out of the Call Library Function Node.

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.


Friday, January 13, 2006

ADVANCED: How To Call The Mac OS Directly From LabVIEW Part II: Converting Data for Ease of Use

See Part I of this series for the obligatory warning about using the Call Library Function Node.

Let's look at some examples that shipped with LabVIEW 8.0. In vi.lib/Platform, there is a VI, Mac Key Modifiers Down.vi. This VI wraps a call to GetCurrentEventKeyModifiers. Like TickCount in Part I, this function just returns a UInt32,

We could, just like in TickCount, return the UInt32. There are two reasons, in this case, not to. We're writing LabVIEW code and we are writing Mac code. Both reasons encourage us to make the interface more elegant. If you look at Table 2-1, you can see the C defined constants, but that doesn't help us much in LabVIEW. So if you look at Events.h you can see the rest of the constants. You can see that the keyboard bits don't start until bit 8 (cmdKey[Bit]).

So we take the output from the Call Library Function Node, and connect it to Number To Boolean Array primitive (Programming -> Boolean). Then we use an Index Array primitive (Programming -> Array), and grow it. We set the first index to 8 (bit 8) and the rest will be sequentially bit 9, 10, etc. We can then create a cluster indicator on the front panel that has all the boolean values we need.

The version of this VI that shipped with LabVIEW 8.0 used a cluster constant to wire into the Bundle by Name primitive (Programming -> Cluster & Variant). I have since decided on a better way. You can control-click (right-click) on the newly created indicator terminal and select Create -> Local Variable. Then control-click (right-click) on the new local variable and select Change To Read. Then wire the output from the local variable into the Bundle by Name primitive's input cluster. This way if you change the indicator, you do not need to change the constant, it makes the code more robust.

And there you have it, we have made a more usable interface for the data returned from a call to the OS. In part III, we will go more in-depth on data types passed into and out of the Call Library Function Node.

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.


Friday, January 06, 2006

ADVANCED: How To Call The Mac OS Directly From LabVIEW Part I: Call Library Function Node Basics

This is not for the faint of heart. Using the Call Library Function Node you can easily crash LabVIEW, corrupt memory and do some really bad things to LabVIEW.

While adding support for several features in LabVIEW on the Mac, we found it fairly easy to call the OS directly from LabVIEW. For some examples of the fruits of this, you can look at many of the VIs in vi.lib/Platform. This will require extensive use of the Call Library Function Node (Connectivity -> Libraries & Executables). It also requires quite a bit of knowledge about the C programming language and Apple's Documentation.

The first thing to become familiar with is how to use the Call Library Function Node. Once dropped on the block diagram, you double click on it to configure it to be the OS call you want. Most of the calls into the OS will be into Carbon.framework, so in the Library Name or Path, enter "Carbon.*" (without the quotes). The star (*) means framework on the Mac, DLL on Windows and so on Linux. The Carbon framework is not on any other OS, so I could just use .framework, but its less typing to just put a star (*).

The next step is to set the Function Name. For this example, we will use :TickCount" (again, without the quotes). If you look at Apple's Documentation on TickCount, you'll see that it takes no parameters and returns a UInt32. So we set the Type for the return type to be Numeric and set the Data Type to Unsigned 32-bit Integer. It should show the Function Prototype to be "unsigned long TickCount(void);"

One other thing to note is the Run in UI Thread pop-up in the upper-right corner. Many of Apple's APIs are thread-safe. Those that are thread-safe can be changed to Reentrant. If it is not thread-safe, you must keep it Run in UI Thread. When in doubt, use Run in UI Thread.

Now that you are done configuring the Call Library Function Node, you can control-click (or right-click) on the U32 output of the Node, and select Create Indicator. Then run the VI and you will get the tick count.

In Part II, we'll look at more advanced calls and more advanced data types.

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.