The MacView

Virtual Instrumentation from a Mac perspective

Friday, February 24, 2006

Using Libraries (not LLBs) To Group Your VIs

In previous posts, I looked at how to wrap OS calls in LabVIEW. Now I will look at packaging them up. While I will be referring to how to package up the calls into the OS, it is also a general topic.

The easiest way to create a library is to first create a project (File -> New Project). Control-Click (Right-Click) on My Computer and select New -> Library. Now select File -> Save All, and save the project and library to disk (create a new folder in the file dialog).

The next question is "What do I name my library?" I would suggest using your reverse DNS name. For instance, If I was making a library for Carbon, I may use "com.Apple.Carbon" as the name. Now preferrably Apple would make this, but if you don't think they would, you can use their name. In the higher level code that uses the Carbon APIs, I may name that library "com.ni.Carbon".

Now inside of Libraries, you can have a library. For instance, say you wanted to have a CoreFoundation library inside of Carbon. So you would control-click (right-click) on com.Apple.Carbon, and select New -> Library. Then save that library as CoreFoundation (or CF). You may want to make a new directory in the com.Apple.Carbon directory to save the library. And then within the CoreFoundation library, you could have a String library (for CFStringRef functions).

What creating levels of libraries like this does is enable you to have VIs named the same (in different libraries) and still be able to work in LabVIEW together. For instance, say you had a VI called GetTypeID that wrapped CFStringGetTypeID (). The full name of that VI would be com.Apple.Carbon:CoreFoundation:String:GetTypeID, so it could peacefully coexist with com.Apple.Carbon:CoreFoundation:Array:GetTypeID. You could have them both on the same diagram and there would be no problems.

I would suggest having a namespace for each library you call into. That library would then be just thin wrappers around the calls to the entry points in the library (like com.Apple.Carbon). Then have a separate library that are convert the calls into more useful LabVIEW calls (like com.NI.Carbon). Inside each library, add a library for each group of calls (in Carbon it would be a library for each manager). If the manager is divided into different objects it works with, then create another library level for each of the different objects. If you use any clusters, save them as typedefs in that library. If you have helper functions you can make them private in that library (or public if their usefulness extends beyond the library).

Anyway, just some basic ideas on how to make a well packaged wrapper around a Shared Library.

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


Thursday, February 23, 2006

Change in Posting Frequency

I have not been happy with the quality of my posts up to this point. They have been heavy on details, and sometimes difficult to follow. I would like to spend more time developing each entry, and adding images to make the material more clear.

To this end, I will now be updating once a month instead of once a week. This will give me the time to flush out the topics better and generate the images to better explain the ideas.

I appreciate all the comments and support so far, and I will be actively responding to comments on postings.

Have a Mac day!

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


Friday, February 17, 2006

ADVANCED: How To Call The Mac OS Directly From LabVIEW Part VII: Putting It All Together

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

Now let's take a look at some advanced techniques for handling some very complicated structures in calls. Take a look at vi.lib/Platform/FileManager.llb/PBGetCatInfo.vi. Discussing how PBGetCatInfoSync (Apple's Documentation) is far beyond the scope of this post. Let's just say that it is a kitchen-sink-included type function. We'll just look at some of the techniques used to make this call more manageable.

First off, because of the complexity of the structure (a union that contains two structs and each contain structs, arrays pointers, etc.) I decided to just treat the whole block as an array of bytes. We create the aray of bytes of the correct size with the Initialize Array primitive (Programming -> Array). There are a few parameters that need to be set up first: ioName (PStrPtr to a 32 character buffer), ioFDirIndex (I16), ioVRefNum (I16) and ioDirID/ioDrDirID (I32). Since these are inputs, we need to set those spots in the struct to the input data. So we convert them to byte arrays (U8[]) and then replace the Replace Array Subset (Programming -> Array) to set them. This is where finding the offsets of each element of a struct come in handy. Notice that for ioName, we call NewPtr and BlockMove to create a buffer and fill it in with whatever input they gave us. Then we convert that pointer into an array of bytes and put it into the buffer.

We then make the call to PBGetCatInfoSync. We do the traditional error handling with the return value. We take the array returned and pull out the pointer so we can call DisposePtr.

Now comes the fun part, getting all the data out. Since this structure has so many values to get out, I found it easier to group them by the data type and loop over an array of indexes (here again its helpful to pre-calculate the offset of each struct member) and do the conversion to that type. So you can see that we have OSType, 8-bit, 16-bit and 32-bit values we pull out. We also pull the string out of the PStr we allocated (before we disposed of it) and create an FSSpec from the parameters and the string.

Since it is a union, the data at certain offsets can be interpreted in different ways. So we return two different clusters, one for File and one for Directory (and a boolean saying which to look at). We grab the values for each and populate the output.

While at first glance this seems like a very complicated VI, it is actually not all that bad, there is just a lot of data to look at.

Well, that's it. With all of this, you can call the OS (specifically Carbon) and do all sorts of wonderful things.

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


Friday, February 10, 2006

ADVANCED: How To Call The Mac OS Directly From LabVIEW Part VI: Documentation and Error Handling

Let's take a break from the detailed "how to" of calling the OS, and spend some time talking about documentation and error handling.

First, lets talk about documentation. Because there is not always a one to one relationship between C code and LabVIEW code, its usually a good idea to include the information from the header file, and any accompanying documentation, on the call. This would include prototype, description, parameter descriptions, return value, side effects, conditions and any other documentation. You will also want to include struct, enum and constant declarations. When including struct documentation, calculate the offsets of each member of the struct and include them in the documentation.

I like to keep most of this information in labels on the block diagram. That way I can better see a relationship between the C code and the LabVIEW code. If you use any constants, set the label on the constant to the C name for the constant.

Any of the parameters that you expose on the front panel should have the parameter description and any other documentation from the function description that would be needed to make the call. You will probably also want to include much of the overview and description in the VI documentation also, so it can be read from Context Help.

As far as error handling, there are a few things to keep in mind. In most cases, if an error comes in, you do not want to make any calls into the OS. The exceptions would be disposeXXX, deleteXXX, freeXXX, releaseXXX, closeXXX, etc. Things that terminate a data type need to be called to do cleanup, and then you have to merge any errors you get with the error in cluster (which gets precedence).

Usually, there will be a big switch on the error in terminal, with the no error case making the calls. If the call returns an OSErr or an OSStatus, you can check for zero. If it is zero, just return the error in, otherwise, set status to true, the code to the OSErr or OSStatus, and put the VI name in the source (or something meaningful to track down the error).

If you allocate, retain, create, etc. something on your block diagram, try to make sure it is always cleaned up properly before your diagram ends.

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


Friday, February 03, 2006

ADVANCED: How To Call The Mac OS Directly From LabVIEW Part V: Constant Length Arrays in Structs

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

We've looked at several methods of handling different data types when calling into OS calls. Now we're going to look into how to handle constant length arrays in structs (clusters). In LabVIEW, an array in a cluster is really a Handle to an array (32 bit length prefixed), so we cannot just put a LabVIEW array of the type into the struct. There are two methods to handle this. The first is to put the values into the cluster, one by one. If the array is small, or the data does not need to be accessed by the user, this might be usefull (see vi.lib/Platform/FileManager.llb/FSRef.ctl).

If, however, you need to access the data and the data is non-trivial size (like a PStr32), this can just make code handling a nightmare. FSSpec (Apple's Documentation) is one such structure. This is where the second method comes in.

There are two functions that will flatten an FSSpec-like cluster into an array of bytes, and another to inflate it from an array of bytes. This way we have a very LabVIEW like type that looks a lot like the C data type, and we can convert it into something that can be passed to a call that takes an FSSpecPtr. (in vi.lib/Platform/FileManager.llb, FSSpecToRawData.vi and FSSpecFromRawData.vi).



The basics of these are that a PStr32 is a constant size, length prefixed string (array) in the FSSpec struct. We take each element and typecast it into/out of the array.

For instance, in FSSpecFromRawData.vi, we have a cluster constant that represents the first two items in the struct, and we get the first 6 bytes from the array and typecast it to that cluster and then map that into the output. Then we take the rest of the bytes in the input byte array, get the first byte and use that to determine how many following bytes to use. We then us the Byte Array To String primitive (Programming -> String -> Conversion) to get the string. We put it all together into the output cluster, and we're done.

So with what has been covered so far, you can handle most of Apple's API calls. The one thing I have found that you cannot handle, without writing C code in a Framework or Bundle, is passing a callback function to something.

Next we'll look at documenting your VIs that call into the OS.

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