Jump to content

ned

Members
  • Posts

    571
  • Joined

  • Last visited

  • Days Won

    14

Posts posted by ned

  1. Hi Jed - I'm also in San Francisco and had the same question when I moved three years ago. There's a rarely-used Google group, NorCalFirst-Mentors, which you might want to join if you haven't already. Try posting there, it might get you replies from teams looking for help. If you don't get any responses, try again when the school year starts. Also see the Western Region Robotics Forum. They organize CalGames, an October repeat of the previous season's FIRST game, to get teams ready for the upcoming season. After posting to the NorCalFirst-Mentors list, Cecilia "Ceal" from WRRF suggested that I attend CalGames and look for her there; she introduced me to a team that needed help. Finally, the team I've been working with, in San Mateo, might welcome more help. I'm not sure what their situation will be this season, but it's been mostly one teacher and me for the past two years.

  2. I wouldn't worry about it. I was just pointing out that byte alignment padding is not the same across platforms and assuming clusters are byte aligned can yield unexpected results on them.

    Yes, thanks for the clarification. I meant that LabVIEW will remove padding when flattening to a string; a flattened string is portable between LabVIEW platforms (although different platforms may not produce identical strings or values due to differences in floating-point representations). Since it sounds like your data is stored without any padding (it would be unusual if it did include the padding bytes, especially on an old system designed when storage space was more expensive) it is likely you can flatten or unflatten on any LabVIEW platform without problems.

     

    ShaunR's comments apply when manipulating LabVIEW data in memory on different platforms, for example when passing a LabVIEW cluster to a C dll. That's not what's happening here, though.

  3. I'm quite positive that I can't just typecast a LabVIEW cluster to a byte array and still have this work, so I am assuming much of this formatting will have to be done manually, including padding/aligning the data. I am looking for direction as to first steps to take to make sure I'm not overlooking anything. If you need more information please let me know; I just put in the bare bones of what I thought would be enough.

    What makes you positive that the typecast won't work? If "byte alignment option set to 1" means there's no padding (aligned on bytes, rather than multiples of bytes) then it's possible that you won't need much more than a typecast, since LabVIEW packs clusters with no padding. I'd recommend that you flatten to/unflatten from a string instead of typecast since that gives you the option to swap endianness. You might want to use data structures that do not exactly match the ones in C. For example you could create a cluster that is only the header and then separate clusters for each item of the union, even if those are all part of the same struct in C. Then you read only the header cluster, and extract the relevant field to determine which other cluster to use for the data that follows. I'm making some assumptions here about how the struct is written to disk; if the WriteFile function does something interesting with it, then you'll need to look at that.

     

    I assume you have some sample files sitting around. I'd start simple by trying to read them with a simple unflatten from string. If you get the right values for any one-byte values and anything longer is wrong, swap the endianness and try again.

  4. This comment from Aristos Queue about variants might be enlightening: "VARIANTS AREN'T FLAT. The data is picked up off the wire whole, with all the hair hanging off of it (arrays of clusters of arrays of clusters of arrays of... etc) and put in the variant along with the type of the wire."

     

    Based on that, it would seem that the variant you're passing is just a pointer (or handle) to a new type description plus a pointer to the data. Since the data itself isn't copied, there should be some reference counting, the same way LabVIEW would do normally with multiple copies of the same data. Not sure this helps you pull that data apart, though.

  5. You wrote earlier:

    I don't create this DLL, I don't program Labview. I only get a header file from Labiew project with calling parameters knowing that this is a variable for a Com Port.

    Do you have the ability to change the DLL? If so, then there are several ways to make it work. If not, then you're out of luck.

  6. None of those declarations are correct, and the errorIn and errorOut parameters are probably not optional - they're passed by reference and should be allocated properly. Passing a pointer to an incompatible data type will likely lead to problems. It will be difficult to get the errorIn and errorOut declarations correct in VisualBasic, though, because it would involve calling functions in the LabVIEW library to allocate an LStr and then creating a handle to it. ALL of the parameters, except color and len, are by reference (that's what the asterisk in front of them in the function prototype indicates). Further, as everyone else has explained, the ComPort parameter is a reference to a VISA session that is already open (specifically it's a pointer to an integer, where that integer value has meaning only as a reference to a particular session). Passing a constant that corresponds to the COM port you want to open WILL NOT WORK. You cannot call this function successfully without a prior call to open the session. If that function does not exist in the DLL, then you need to ask the DLL creator to add it for you. At the same time they should remove the errorIn and errorOut parameters, or at the least replace them with a data type that is more compatible with VisualBasic.

    • Like 1
  7. On thing you can do (although I haven't tested this with RT FIFOs) is create a type definition for the cluster that is the fixed-size array. Then, put that cluster inside the cluster that contains the integer. It is then easy to convert the nested cluster into an array (using cluster to array). Going the other direction is only slightly more complicated. If you know that the array size won't change, you can use array to cluster. Otherwise you can typecast the array to the cluster type definition; this may be less efficient (my understanding is that typecast will always flatten to a string, then unflatten to the new representation, instead of a more direct conversion) but is more flexible.

    • Like 1
  8. This brings up an ethical question I've had about the possibility of building an executable for someone with a student version of the software.  What sayeth y'all?  Is it expressly verboten in the EULA?  Is it shady?  Is it just plain wrong?

    Not that I've done this, and I haven't read through the entire EULA lately, but I don't see any problem with it. I can't imagine that the application builder license limits you to building applications only from VIs that you develop. I'm sure there are companies with several LabVIEW developers but only one has an application builder license, and there's no reason that one person can't build applications for the others. This isn't too far of an extension from that. That said, I'm not about to start a side business building applications for people who can't afford a full license.

    • Like 1
  9. Many of us would probably find that content interesting, but we aren't allowed to access it. Any chance you can share those presentations somewhere public, so that those of us who are not CLAs can learn from them? If not, could you please refrain from posting links to private communities in public locations?

    • Like 1
  10. Yes, that's exactly right. Take a look at the Visitor example I linked - the parent Visitor class has a method for each type of DataItem, but they don't do anything. All the real work is in the Visitor child implementations. This allows you to call any Receiver class from within Message, without knowing the specific Receiver implementation.

     

    The pattern also allows you to easily drop in a new receiver for a different application, which it sounds like is desirable for you. You just override all the parent's methods in a new class with the appropriate logic, and add a constant of that new receiver class as the desired implementation.

  11. The message class is statically linked to the parent Receiver class, which doesn't actually implement anything. When you receive a message you use the child Receiver class, which contains all the logic and links to other classes.

     

    No cast is necessary to get the correct child Receiver class; it will just work through the magic of class inheritance and dynamic dispatch, because you will dynamically dispatch on the Receiver class when you call the specific Receive method for that particular message type.

  12. I can see now how setting things up this way could be very useful. You have visitors that do all kind of different things, but you have to only write the iteration code once-- in this case it's just a for loop in DataItemCollection.lvclass, but I can see that if you had oh, binary trees or oct trees or graphs doing it that way would be awfully useful, since visiting every member there involves a bit more than dropping a for loop on the block diagram.

    Poke through the code a little bit more - a DataItemCollection is a DataItem that contains an array of DataItems, so it does implement a tree (the reason the visitors are reentrant - they can be called recursively). That's why I mentioned one good exercise with it is to write a visitor that formats the structure into a tree for display.

  13. That's quite the framework you've built - a lot of functionality and still readable enough that I could understand what it's doing pretty quickly.

    Here's the discussion about the Visitor pattern: http://lavag.org/topic/16696-ah-yes-another-oo-architecture-question/

    The way I am sending messages now is to call a remote VI in the receiver and give it the message object and the name of the receiver's process that the message is directed at.  The receiver code then puts the message in the queue for the correct process, which then de-queues it and dynamic dispatches to the execute method in the message's class.

     

    As a result, my messages do not know if they are sent between applications or just within an application.  It sounds like I need to put another layer in that translates the received message into an internal only message and then passes it to the proper internal process.  Am I understanding your idea correctly?

    You don't need separate internal messages; the idea is that the messages stay the same with the exception of adding a Receive method and possibly an accessor or two if needed. In Remote Send (inside the Remote Extensions library), put a constant of the child (implemented) Receiver class on the block diagram and pass it to the Receive method of the message that arrives. The message class only needs to be linked to the parent Receiver class, and all the dependencies should be in the children that actually implement receiving the message. If I've understood correctly, that accomplishes your goal.

     

    If you're only going to have one receiver class then you don't strictly need the two layers of dynamic dispatch, although I think it helps keep things modular. It also allows you to add new actions on Messages fairly easily - you could handle sending the same way. You'd just want to name the method in Message something more generic than Receive, since it could do any action that's passed to it (in the form of a class).



    But I am not sure how the receiver class will work.  How can I get the dynamic dispatch to work when I am passing a message object?  If I create a generic method that gets overridden by the child receiver for that message, what triggers the dispatch?  Where does the original receiver child object get created to allow the dispatch to happen?

    There's a manual programming step that makes this work. Inside the message's Receive method, it needs to make a call to the specific Receive method for that message type. That is, a message of type Message1 has a Receive method that calls Receiver.ReceiveMessage1. Message2 calls Receiver.ReceiveMessage2, and can't call ReceiveMessage1 because the connectors don't match (it has a connector for Message2, not for Message1). You don't have a separate Receiver for each type of Message; you have one child Receiver that has a separate method for each message type. That child Receiver can be a constant on the block diagram of the VI that gets called through VI Server.

  14. Move all the receiving logic into a separate class, Receiver, that mirrors the message structure (you'll need one method per type of message that you want to receive), similar to the Visitor pattern that was recently discussed in another thread. Add a method to the message class that takes, as a parameter, a class of the receiver type, and calls a method inside that receiver class, passing itself (the message) as a parameter. Then all the dependencies are inside the receiving class. The parent receiving class can basically be a no-op on the sending side with no dependencies since it should never be called there. On the receiving side, you implement a child receiver that has all the logic and links.

     

    Say we have an instance of Message of type Message1, and an instance of Receiver (both are classes). Upon receipt of a message, the following happens:

    1) code calls Message.Receive(Receiver). This dynamically dispatches to the correct instance for Message1.

    2) the implementation of Receive for Message1 calls Receiver.ReceiveMessage1(Message).

    3) ReceiveMessage now has strictly-typed instances of both Receiver and Message, and can do the correct operations to handle that message.

  15. Here's another Visitor Pattern example that follows the traditional pattern more closely than the NI one. I learned the Visitor Pattern years ago in a compilers class that was taught in Java, and it was an interesting exercise to put together this example in LabVIEW. I apologize for the lack of comments and icons, I put it together rather quickly, but I hope it's helpful and I'm happy to fill in details if necessary.

     

    The example contains two class hierarchies: DataItem, and Visitor. The DataItem class has three children: IntegerDataItem, StringDataItem, and DataItemCollection (an array of DataItems). The Visitor class has two children: Generate DataItem Collection, and To Comma Separated String. The first uses the Visitor pattern to build up a DataItem Collection containing random numbers. Strings get negative numbers, Integers get positive numbers. The second visits each DataItem in the collection, and if it's a string or integer it converts to a string (if needed) and appends it to a comma-separated list.

     

    Each child of the Visitor class has a method for every child of DataItem (Visit DataItemCollection, Visit IntegerDataItem, and Visit DataItemString). To add a new action, create a new class that inherits from Visitor and implements those three methods. To get familiar with the pattern, here are some ideas for exercises:

    - Create a Sum class that adds all the numbers in the DataItemCollection (converting the strings to integers)

    - Create a TreeDisplay class that formats the DataItemCollection into a nice format for display, showing the levels of nested collections

    - Add a new DataItem type (for example, floating point) and add appropriate classes to each Visitor class.

     

    Note that when you want to do an action on the DataItem, you pass the Visitor (action) class to the DataItem in a call to the Do VI. This is the first level of dynamic dispatch. Then, the Do VI calls the appropriate action from the Visitor and passes the DataItem to it, adding a second level of dynamic dispatch. This is why the pattern is also known as double-dispatch.

     

    This example is in LabVIEW 2012.

    Visitor Pattern Example.zip

    • Like 1
  16. The controls in every element of the array need to be identical; you cannot have a different items in the combobox in different elements of the array. You'll need to come up with some other approach. One option might be a table indicator with a combobox on top of the active cell, which would let you change the contents of the combobox each time you display it in a new location. I provided some code that demonstrates the idea here: http://forums.ni.com/t5/LabVIEW/array-of-cluster/m-p/1822451#M625032 although that has a ring control, not a combobox, with constant contents. However, it would be easy to update the ring elements each time it's moved.

  17. What makes you think it's the TCP port? I assume by "same port" you mean that all the servers listen on the same port. On the client side, the operating system assigns each connection a different random port when the connection opens. You would see the same behavior even if all the servers were on different ports.

     

    My guess is either that you're running out of threads to handle all the instances of the reentrant VI, or at some much lower level access to some part of the TCP/IP system is serialized. Can you try a few experiments? First, try doing all the writes first, then doing all the reads once all the writes have completed. Or, add a short wait after the write such that when you call the read the data should have already arrived. Also try using threadconfig to increase the number of threads in the execution subsystem in which your re-entrant VIs run.

  18. A couple of additional comments:

    1) One common solution is to make two function calls: one that returns the size of the array that will need to be allocated, and a second that then fills the pre-allocated array with data. The second function should still accept a size parameter so it can confirm that the data will fit and return an error if it will not. Some Windows functions combine these into one function, by passing the size parameter by pointer. If the array pointer is null, or the value pointed to by the size parameter is 0, then the function returns the needed size in the size parameter; otherwise, it fills the array.

     

    You should not use Pascal strings, it will be a pain for anyone calling the library from C. If you're using a string and not an array of bytes, then you need to keep in mind that C strings are null-terminated (the last byte in them is 0) and the size needs to include that terminating byte. When you call a DLL from within LabVIEW and you pass the parameter as a string, LabVIEW automatically adds the null termination. However, if you pass the string as an array of U8, then LabVIEW doesn't know it's a string and will NOT add that null terminator. The DLL, having been built in LabVIEW, will expect to find that null terminator to determine the appropriate string length and will likely generate an error if it doesn't find a null byte before the end of the string (a null before the end of the string is fine although you may lose data that follows it; no null at all in the string is not fine).

     

    2) Yes, you should use C calling conventions, mostly because it's more common (with the important exception of almost all DLLs that are part of Windows).

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.