c_w_k Posted July 7, 2009 Report Share Posted July 7, 2009 hello, I'm wondering how I would send an array of clusters to my dll. What I'm wanting to do is to create an array of clusters based on the count of "boards" that are found, so if 2 boards are found, an array of 2 elements will be created.....with the elements being LabVIEW clusters. Getting the board count is no problem, but when I try to get the information from the boards by sending my array of clusters to the dll I'm hitting a brick wall. Here is what the dll function call looks like: extern "C" short CCONV acq_board_info(short cnt, MyStruct_info *info); Here is what MyStruct looks like: struct MyStruct_board_info { char name[32] short var1 short var2 short var3 short var4 short var5 char var6 char var7 long var8 long var9 long var10 long var11 char var12 char pad[3] }; If anyone can point me in the right direction I would greatly appreciate it...also if there is any information that would help out, please ask and i will do my best to provide it. By the way.....I'm using LabVIEW 8.6 Thanks CK Quote Link to comment
Popular Post Rolf Kalbermatter Posted July 8, 2009 Popular Post Report Share Posted July 8, 2009 The Call Library Node does not directly support to pass complex datatypes, resp. it does using the Adapt to Type but this passes an array as a LabVIEW handle which is absolutely not the same as a C array pointer. So you will have to trick this a bit. First configure the parameter as an array of bytes (8-bit integers). Then allocate a byte array of enough length with the Initiliaze Array function and pass this to the Call Library Node. The returned byte array can then be typecasted to a LabVIEW array of clusters and should after that be routed through a Swap Bytes and Swap Words node when you are on Windows. Alternately in LabVIEW 8.2 an newer you can use the Unflatten from String function (convert the byte array first to a string) where you set the endianess input to little endian, or even better native, host order, and the "data includes array or string size? (T)" set to false. With this you replace the Typecast and Swap Bytes and Swap Words. You will have to create a cluster that is compatible. For the char name[32] and char pad[3] you can NOT use a LabVIEW string but must instead insert another cluster of 32 resp. 3 8-bit integers. The get the length of the entire byte array typecast this entire cluster to a string and get the string length and multiply it with the number of struct elements you want to pass to the DLL function and then pass this value to the length input of the Initiliaze Array node. Rolf Kalbermatter 3 Quote Link to comment
c_w_k Posted August 5, 2009 Author Report Share Posted August 5, 2009 Hi Rolf, Sorry for the delay in communication...i was put on a different project for a few weeks, but now i'm back. I tried sending the array to the call function as an array of bytes and then typecasting and doing the Swap Bytes like you mentioned.......so far its working like a charm! Thank you very much Chad Quote Link to comment
Popular Post Adam Kemp Posted August 6, 2009 Popular Post Report Share Posted August 6, 2009 Rolf's suggestion is not the best way to do this. You actually can directly pass an array of clusters to C code and treat them as C structs, and you can even modify and resize that array. Here's how: Configure your call library node parameter as "Adapt to Type", and make sure that you have "Handles by Value" selected for the Data Format. Once that's done, wire up your array of clusters (if you start with an empty array, just create an empty array constant with the right type and wire that). Now, right-click on the call library node and choose "Create .c file...". That will generate a file containing C code with the proper C types for your array of clusters and the right function prototype. It will look something like this: /* Call Library source file */ #include "extcode.h" /* Typedefs */ typedef struct { int32_t Element; } TD2; typedef struct { int32_t dimSize; TD2 Cluster[1]; } TD1; typedef TD1 **TD1Hdl; void funcName(TD1Hdl arg1); void funcName(TD1Hdl arg1) { /* Insert code here */ } Obviously we don't do a good job of naming these types, so you should rename them first (ex: replace TD1 with MyArray and TD2 with MyStruct). Important: On 32-bit Windows you need to do one more thing to this code to make it work right in every case. Modify it like so: /* Call Library source file */ #include "extcode.h" /* Typedefs */ #if MSWin && ProcessorType != kX64 #pragma pack(push,1) #endif typedef struct { int32_t Element; } MyCluster; typedef struct { int32_t dimSize; MyCluster Cluster[1]; } MyArray; typedef MyArray **MyArrayHdl; #if MSWin && ProcessorType != kX64 #pragma pack(pop) #endif void funcName(MyArrayHdl arg1); void funcName(MyArrayHdl arg1) { /* Insert code here */ } The #if/endif and #pragma lines are the ones you need to add. This fixes alignment on 32-bit Windows because LabVIEW on that platform does not use the default alignment. If you don't do this then the C code will not interpret the data correctly in some cases. With that done, you just have to implement your code. Note that the function takes a MyArrayHdl (aka, a MyArray**). This is a "handle" to a LabVIEW array. LabVIEW arrays internally are structures containing an int32 dimension size (for each dimension) followed by an inline array of the elements. So, for instance, to sum all the elements in the example above you would write code like this: int32_t sum = 0; if(arg1) // empty arrays will have a NULL handle { for(int32_t i = 0; i < (*arg1)->dimSize; ++i) { sum += (*arg1)->Cluster[i].Element; } } That's all it takes to just read or modify the existing elements of the array of clusters. What about resizing? That's a bit trickier, but it's still possible. To resize the array you need to resize the handle, update the dimSize, and initialize any new elements (if you grew the array). When you resize the handle you have to calculate the size of the whole array in bytes. Here's the correct way to grow the array above by one element: MgErr err = mgNoErr; if( mgNoErr == (err = DSSetHSzClr(arg1, Offset(MyArray, Cluster) + sizeof(MyCluster)*numElems)) ) { (*arg1)->dimSize = numElements; // Initialize new elements } else { // error (probably mFullErr) } If you allow for an empty array to be passed in then you might get a NULL handle, which you can't resize. To allow for that, change your call library node by setting the Data Format of that parameter to "Pointers to Handles". This will change the type from MyArrayHdl to MyArrayHdl* (aka MyArray***). You would then work with it like this: MgErr err = mgNoErr; size_t arraySizeInBytes = Offset(MyArray, Cluster) + sizeof(MyCluster)*numElems; if(NULL != *arg1) { err = DSSetHSzClr(arg1, arraySizeInBytes); } else // empty array, must allocate { if( NULL != ( *arg1 = (MyArrayHdl)DSNewHClr(arraySizeInBytes) ) ) err = mFullErr; } if(mNoErr == err) { (**arg1)->dimSize = numElems; // Initialize new elements } else { // handle error } The last thing you have to do is to link your DLL to labviewv.lib (in the cintools directory of your LabVIEW installation, along with extcode.h). This gives you access to the DS* functions (and all the other functions in extcode.h). Make sure you use the labviewv.lib version. That's the one that's smart enough to make sure that it uses the correct versions of those functions even if you have multiple LabVIEW runtimes loaded in the same process. Now, obviously a lot of this is a bit tedious (much harder than using a simple C-style array), but it's not actually very difficult once you know how to do it. Don't be afraid to try it. It's easier than it looks, and it can make your LabVIEW/C interactions much more flexible. 4 Quote Link to comment
Adam Kemp Posted August 6, 2009 Report Share Posted August 6, 2009 Actually, I think that you can simplify my examples quite a bit and use the original prototype you gave: extern "C" short CCONV acq_board_info(short cnt, MyStruct_info *info); Configure the CLN as follows: return type: Numeric, Signed 16-bit Integer param 1: Numeric, Signed 16-bit Integer param 2: Adapt to Type, Data Format "Array Data Pointer". When I tried this LabVIEW didn't properly generate the C file (it put "void arg2[]" instead of "MyStruct_info arg2[]"), but I think it would pass the right data. You just have to make sure the type of the struct matches the cluster and be sure to use the #pragma statements as above for alignment. Quote Link to comment
Rolf Kalbermatter Posted August 7, 2009 Report Share Posted August 7, 2009 Actually, I think that you can simplify my examples quite a bit and use the original prototype you gave: extern "C" short CCONV acq_board_info(short cnt, MyStruct_info *info); Configure the CLN as follows: return type: Numeric, Signed 16-bit Integer param 1: Numeric, Signed 16-bit Integer param 2: Adapt to Type, Data Format "Array Data Pointer". When I tried this LabVIEW didn't properly generate the C file (it put "void arg2[]" instead of "MyStruct_info arg2[]"), but I think it would pass the right data. You just have to make sure the type of the struct matches the cluster and be sure to use the #pragma statements as above for alignment. Adam, you did miss in your previous post that the original problem was not about interfacing a DLL specifically made for LabVIEW, but about interfacing to an already existing DLL. In there you have normally no way that they use LabVIEW data handles! Your last option is however a good one. I was missing that possibility since the Pass: Array Data Pointer for Adapt to Type is a fairly new feature of the Call Library Node. It seems it was introduced with LabVIEW 8.2. And there is no pragma necessary (or even possible since the DLL already exists) but the structure is using filler bytes already to align everything to the element size boundaries itself. The void arg[] datatype generated by LabVIEW is not really wrong or anything as it is fully valid C code, it is just not as perfect as LabVIEW could possibly make it, but I guess when the addition of the Pass Array Data Pointer to the Adapt Datatype was done, that person did not feel like adding extra code to generate a perfect parameter list and instead took the shortcut of printing a generic void data pointer. Rolf Kalbermatter Quote Link to comment
c_w_k Posted August 7, 2009 Author Report Share Posted August 7, 2009 Rolf, I was able to get your way to work so I'm sticking with that until I learn LabVIEW a little more. On the byte swap and word swap I noticed that I neede to use a byte swap and a word swap with long data types but only needed a byte swap with the short data types......I'm a little confused on that since the byte swaps move 8 bytes and the word swaps move 16 bytes..... I've moved on to my next function in the dll and low and behold this one has a parameter for a struct.......and in this struct are two more structs :| but if I can figure out where to use the swap bytes and swap words I should be in good shape. For the struct that contains a struct I plan on simply creating a byte array that big enough and then sub-arraying the returned values. Thanks Chad Quote Link to comment
Adam Kemp Posted August 7, 2009 Report Share Posted August 7, 2009 Rolf, good point. I didn't notice that it was already padded correctly. Chad, if you're already having complications with byte swapping then I strongly suggest trying my approach from the second post (ignoring the pragma stuff). It will save a lot of time because it will allow you to just wire the cluster directly. What you might run into still is structs that contain pointers to other structs, and that's not easy to solve. Eventually it just becomes easier to create a wrapper DLL that works better with LabVIEW. You can also try the Import Shared Library feature. It's hit or miss since there are a lot of things C APIs can do that don't translate well to LabVIEW, but for a lot of simple interfaces (including the one in the first post here) it can do exactly the right thing automatically. Quote Link to comment
Rolf Kalbermatter Posted August 8, 2009 Report Share Posted August 8, 2009 Rolf, I was able to get your way to work so I'm sticking with that until I learn LabVIEW a little more. On the byte swap and word swap I noticed that I neede to use a byte swap and a word swap with long data types but only needed a byte swap with the short data types......I'm a little confused on that since the byte swaps move 8 bytes and the word swaps move 16 bytes..... I've moved on to my next function in the dll and low and behold this one has a parameter for a struct.......and in this struct are two more structs :| but if I can figure out where to use the swap bytes and swap words I should be in good shape. For the struct that contains a struct I plan on simply creating a byte array that big enough and then sub-arraying the returned values. Thanks Chad Well, Swap Words swaps the two 16 bit values in a 32bit value. It has no effect whatsoever on 16bit values (word) or 8bit values (byte). So you do not need to apply the Swap Bytes and Swap Words to each individual value but can instead put them simply in the cluster wire itself. They will swap where necessary but not more. Rolf Kalbermatter Quote Link to comment
c_w_k Posted August 13, 2009 Author Report Share Posted August 13, 2009 Adam, Now that I think i've got Rolf's way of doing to it to work for me, I've went back and tried setting everything up how you mentioned. I've configured the CLN so that it takes a short parameter and an "ADAPT to TYPE" parameter (also selected array data pointer). and for some reason it is crashing LabVIEW. I'm wondering if I need to handle the char[32] name and char[3] pad differently in my cluster...currently i've just got configured as LabVIEW strings... I will try posting some screen shots of what i'm seeing crash wise later tonight when I get home. Thanks for any advice on this -Chad Quote Link to comment
Rolf Kalbermatter Posted August 13, 2009 Report Share Posted August 13, 2009 Adam, Now that I think i've got Rolf's way of doing to it to work for me, I've went back and tried setting everything up how you mentioned. I've configured the CLN so that it takes a short parameter and an "ADAPT to TYPE" parameter (also selected array data pointer). and for some reason it is crashing LabVIEW. I'm wondering if I need to handle the char[32] name and char[3] pad differently in my cluster...currently i've just got configured as LabVIEW strings... I will try posting some screen shots of what i'm seeing crash wise later tonight when I get home. Thanks for any advice on this -Chad Yes you have to! Don't configure those as strings since LabVIEW strings are not only no C string pointer but also no fixed size strings. Leave them as the byte clusters as you did before and convert them to strings after you got the data from the DLL. You need to setup the clusters just the same as before but doing this with Adapt to Type and pass as C array pointer (only possible in LabVIEW 8.2 and above) will save you the hassle of typecasting/unflattening and byte and word swapping. Rolf Kalbermatter Quote Link to comment
c_w_k Posted August 17, 2009 Author Report Share Posted August 17, 2009 Rolf / Adam, I was able to get this to work.....I'm wondering if there is a better way of doing this......I've attached a screenshot of the code I used. Now I just need to clean up on the front end so that only the returned values appear.....I'm trying to some how hide the "name" cluster that has not yet been converted to a string so that only the converted cluster appears....I'll keep looking for a way to do this. thanks for your help Chad Quote Link to comment
Adam Kemp Posted August 17, 2009 Report Share Posted August 17, 2009 I'm assuming that the "name" field in the cluster is a sub-cluster containing 32 U8s. In that case you can use the Cluster to Array primitive to convert to a U8 array, and then the Byte Array To String primitive to convert it into a string. You would still need to have one internal cluster definition for talking to the DLL and then another cluster with a real string and no padding elements to expose to your VI's callers. Still, it's doable. Quote Link to comment
Rolf Kalbermatter Posted August 25, 2009 Report Share Posted August 25, 2009 Adams suggestion is correct and should work. He only omitted that the string name you will get will always be 32 characters long with zero characters after the end of the string. So after converting the name cluster into a byte array with Cluster to Byte Array, you should do a search for the first 0 byte and cut the array there and only after that convert it into a string. Also the pad cluster you can simply drop. It will most likely not contain any useful information but is just there to make sure the array elements align properly in memory. Rolf Kalbermatter Quote Link to comment
Adam Kemp Posted August 25, 2009 Report Share Posted August 25, 2009 Good point. Just to make sure Chad understands, though, you DO need the padding part in the cluster that you pass to the Call Library Node if you ever plan on passing an array of these things to the DLL. What you'll do is have two cluster types, one with the padding and the cluster of 32 U8s, and then another one without the padding and a String control instead of the U8 cluster. You'll just convert from one cluster type to the other one and return the one that works well with LabVIEW. That way the users of your API never see the ugly stuff that you needed just to call the C code. If you only ever pass one then the padding at the end is not important. Don't you just love low-level programming? Aren't you glad LabVIEW hides this stuff from you? Quote Link to comment
Rolf Kalbermatter Posted August 25, 2009 Report Share Posted August 25, 2009 You are of course correct. I meant he could drop the pad string from the user friendly cluster, but of course it should be present in the low level cluster. And well about your latest remark, I do love to dabble in that low level stuff too, but yes it is always nice to return back to LabVIEW high level programming and concentrate on the real problem instead of making the memory ends meet everywhere and watch out about correct memory allocation and deallocation and never to access an invalid buffer . Rolf Kalbermatter Quote Link to comment
Ralle Posted February 25, 2011 Report Share Posted February 25, 2011 Thanks Adam for your great TUT "interfacing a DLL specifically made for LabVIEW". Everything works fine, but If I try to change the new element without Initialising, the DLL throws an error. How can i initialize the new element? Can you give me short example for my application? I have an Array of Clusters with 2 Strings. Quote Link to comment
Adam Kemp Posted February 25, 2011 Report Share Posted February 25, 2011 How can i initialize the new element? I don't know what you mean by this. Can you show me the C code which is giving an error (I assume you probably mean crashing?). If you have an array of clusters of two strings then your data will look something like this in C (I assume you are including extcode.h, which comes with LabVIEW in the cintools directory): typedef struct { LStrHandle str1; LStrHandle str2;} ClusterWithTwoStrings;typedef struct { int32 cnt; ClusterWithTwoStrings elt[1];} ArrayOfClustersWithTwoStrings; Then your C function would look like this (assuming you set it up to be "adapt to type" and "handles by value"): void MyFunction(ArrayOfClustersWithTwoStrings** arrayHandle){ if(arrayHandle) { for(int32 i=0; i != (*arrayHandle)->cnt; ++i) { if((*arrayHandle)->elt[i].str1) ; // do something with this string if((*arrayHandle)->elt[i].str2) ; // do something with this string } }} Note the checks to make sure the array handle and the string handles are not NULL. In LabVIEW an empty array is a NULL handle, and an empty string is a NULL handle as well. You may be crashing because you are not checking for empty. So let's assume now that you want to actually create this array in your C code. That's a little more complicated. What you have to do now is pass in a pointer to the handle (just in case it's empty you need to allocate it and give it back to LabVIEW), so you set your call library up to be "adapt to type" and "pointers to handles". Now your C function looks something like this: void MyFunction(ArrayOfClustersWithTwoStrings*** arrayHandlePtr){ if(arrayHandlePtr) { if(*arrayHandlePtr) { // free any existing elements for(int32 i=0; i != (**arrayHandlePtr)->cnt; ++i) { DSDisposeHandle((**arrayHandlePtr)->elt[i].str1); // This function can handle NULL without an error DSDisposeHandle((**arrayHandlePtr)->elt[i].str2); (**arrayHandlePtr)->elt[i].str1 = NULL; (**arrayHandlePtr)->elt[i].str1 = NULL; } // Resize the array and clear any new elements DSSetHSzClr(*arrayHandlePtr, Offset(ArrayOfClustersWithTwoStrings, elt) + sizeof(ClusterWithTwoStrings)*numberOfElementsToAllocate); } else { // Allocate a new handle *arrayHandlePtr = (ArrayOfClustersWithTwoStrings**)DSNewHandle(Offset(ArrayOfClustersWithTwoStrings, elt) + sizeof(ClusterWithTwoStrings)*numberOfElementsToAllocate); } if(*arrayHandlePtr) { (**arrayHandlePtr)->cnt = numberOfElementsToAllocate; // Initialize new elements } else ; // error } else ; // error} (sorry about the tabbing; I can't figure out what this editor wants...) The important things to note here are: Arrays and strings must be allocated (or resized) using the LabVIEW memory manager functions (DSNewHandle, DSDisposeHandle, DSSetHandleSize, DSSetHSzClr, etc.) from extcode.h. Link against labviewv.lib to get access to those functions. Empty arrays or strings are NULL. After disposing a handle set it to NULL After allocating or resizing a handle for an array or string set the cnt field to the number of elements you allocated. When allocating space for an array or string you have to calculate the total number of bytes for that array or string. You do this by using Offset(structName, eltField) (to get the number of bytes for the cnt field plus any padding) plus the size of your element (ClusterWithTwoStrings for your cluster or uChar for a string) times the number of elements you want. Also note that you can convert an LStrHandle (a LabVIEW string) to a null-terminated C string using LToCStrN, and you can also convert from a C string to an LStrHandle using LStrPrintf. You can find documentation for these functions in the LabVIEW help. Also check out the "Calling Code Written in Text-Based Languages" section of the help. There's a lot of information in there. Quote Link to comment
Ralle Posted March 1, 2011 Report Share Posted March 1, 2011 This is the code: typedef struct { LStrHandle SerialNumber; LStrHandle ModelName; LStrHandle FullName; LStrHandle VendorName; LStrHandle DeviceClass; LStrHandle DeviceVersion; LStrHandle UserDefinedName; } CamInfoCluster;typedef struct { int32_t_Dilas dimSize; CamInfoCluster CamInfo[1]; } CamInfoArray;typedef CamInfoArray **CamInfoArrayHdl;//DT1Hdltypedef struct { LVBoolean status; int32_t_Dilas code; LStrHandle source; } ErrorCluster;void EnumerateCameras(CamInfoArrayHdl CamArray, ErrorCluster *LVError){ int numDevicesAvail; char* String; numDevicesAvail = 3; int deviceIndex; MgErr err = mgNoErr; if( mgNoErr == (err = DSSetHSzClr(CamArray, Offset(CamInfoArray, CamInfo) + sizeof(CamInfoCluster)*numDevicesAvail)) ) { (*CamArray)->dimSize = numDevicesAvail; //Initialize new elements????? (Example please) } else { FillErrorCluster(LVError, TRUE, mFullErr, "mFullErr"); return; } for ( deviceIndex = 0; deviceIndex < numDevicesAvail; ++deviceIndex ) { String = "Serial"; //SerialNumber (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt = strlen(String); NumericArrayResize(uB, 1, (UHandle*)(&((*(CamArray))->CamInfo[deviceIndex].SerialNumber)), 4 + (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt); strncpy((char*)(*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->str, String, (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt); } }void FillErrorCluster(ErrorCluster *LVError, LVBoolean bStatus, int iCode,char* cSource){ LVError->status = bStatus; LVError->code = iCode; (*(LVError->source))->cnt = strlen(cSource); NumericArrayResize(uB, 1, (UHandle*)(&(LVError->source)), 4 + (*(LVError->source))->cnt); strncpy((char*)(*(LVError->source))->str, cSource, (*(LVError->source))->cnt);} The incoming Array can be empty! LV Error: Possible reason(s): LabVIEW: An exception occurred within the external code called by a Call Library Function Node. The exception may have corrupted LabVIEW's memory. Save any work to a new location and restart LabVIEW Quote Link to comment
Rolf Kalbermatter Posted March 1, 2011 Report Share Posted March 1, 2011 This is the code: typedef struct { LStrHandle SerialNumber; LStrHandle ModelName; LStrHandle FullName; LStrHandle VendorName; LStrHandle DeviceClass; LStrHandle DeviceVersion; LStrHandle UserDefinedName; } CamInfoCluster;typedef struct { int32_t_Dilas dimSize; CamInfoCluster CamInfo[1]; } CamInfoArray;typedef CamInfoArray **CamInfoArrayHdl;//DT1Hdltypedef struct { LVBoolean status; int32_t_Dilas code; LStrHandle source; } ErrorCluster;void EnumerateCameras(CamInfoArrayHdl CamArray, ErrorCluster *LVError){ int numDevicesAvail; char* String; numDevicesAvail = 3; int deviceIndex; MgErr err = mgNoErr; if( mgNoErr == (err = DSSetHSzClr(CamArray, Offset(CamInfoArray, CamInfo) + sizeof(CamInfoCluster)*numDevicesAvail)) ) { (*CamArray)->dimSize = numDevicesAvail; //Initialize new elements????? (Example please) } else { FillErrorCluster(LVError, TRUE, mFullErr, "mFullErr"); return; } for ( deviceIndex = 0; deviceIndex < numDevicesAvail; ++deviceIndex ) { String = "Serial"; //SerialNumber (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt = strlen(String); NumericArrayResize(uB, 1, (UHandle*)(&((*(CamArray))->CamInfo[deviceIndex].SerialNumber)), 4 + (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt); strncpy((char*)(*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->str, String, (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt); } }void FillErrorCluster(ErrorCluster *LVError, LVBoolean bStatus, int iCode,char* cSource){ LVError->status = bStatus; LVError->code = iCode; (*(LVError->source))->cnt = strlen(cSource); NumericArrayResize(uB, 1, (UHandle*)(&(LVError->source)), 4 + (*(LVError->source))->cnt); strncpy((char*)(*(LVError->source))->str, cSource, (*(LVError->source))->cnt);} The incoming Array can be empty! LV Error: Possible reason(s): LabVIEW: An exception occurred within the external code called by a Call Library Function Node. The exception may have corrupted LabVIEW's memory. Save any work to a new location and restart LabVIEW There are some serious trouble and some minor nitpicks. First thing is your initialization of the string cnt value before you allocate the string handle with NumericArrayResize(). This can't work, since the cnt element is just another element inside the memory buffer of the handle. Therefore it does not yet exist. In fact the (*((*(CamArray))->CamInfo[deviceIndex].SerialNumber))->cnt = strlen(String); must crash since you dereference the (*(CamArray))->CamInfo[deviceIndex].SerialNumber value which is still a NULL handle from the previous DSSetHSzClr(). And if you hadn't used the function with Clr in it, the value would be not necessarily NULL but still as invalid a memory address as NULL is. So you can't initialize cnt before you allocated the string handle with NumericArrayResize() and in fact I make it a rule to always only initialize cnt AFTER I have filled in the data. The same potential error is done in the FillError function. Potential because the string may be not NULL if the VI that passed in the error cluster had put a non-empty string in there. Another issue although not fatal is that you do not need to account for the 4 bytes of the dim (for string its the cnt) integer when you use NumericArrayResize(). This function accounts for the dim integer already when allocating the necessary memory handle (but it doesn't fill in the dim/cnt value. You do need to account for the dim/cnt size when using the DSNewHandle(), DSNewHdlClr() and DSSetHandleSize() functions since this are low level memory manager functions that make no assumptions about the content of the handle. Last but not least strncpy() is a rather tricky function. It's semantics happen to work for this specific case, since a LabVIEW string does not need to be NULL terminated and strncpy() does not insist on adding that NULL character to the end if the count value happens to be equal or smaller than the actual characters in the source string. So while strncpy() actually works perfect for this use case with LabVIEW string handles I try to avoid this function at all costs since when used with normal C strings it often can result in unterminated strings in the memory buffer which can cause all kinds of buffer overrun errors. Call of MoveBlock() or memcpy() is much more clear in this respect since it does what one expects and nothing more or less. Quote Link to comment
charlie87 Posted September 19, 2013 Report Share Posted September 19, 2013 (edited) Hi, I am starting to work with array of clusters inside the C DLL. I followed your nice instructions which help me so much. However, I have problem with simple changing the integer value inside the cluster.  My cluster is defined: typedef struct { int32_t dimSize; double Item[1]; } DataArray;typedef DataArray **DataArrayPtr;typedef struct { LStrHandle Name; LStrHandle String; int8_t Type; DataArrayPtr Data; } Cluster;typedef struct { int32_t dimSize; Cluster Variable[1]; } Buffer;typedef Buffer **BufferPtr; I would like to change the value of the first cluster array element Type (int8_t). Lets see my test function: int myfunction(BufferPtr parameters) { Cluster var; var = (*parameters)->Variable[0]; var.Type = 15; return var.Type;} The myfunction return the value 15, however, I would expected that the value will be changed also inside the output cluster from the Call Library Function Node... But it does not... Edited September 19, 2013 by charlie87 Quote Link to comment
ned Posted September 19, 2013 Report Share Posted September 19, 2013 (edited) You have a misunderstanding about C. When you create "var" inside myfunction, you are allocating memory space for a new Cluster. The assignment to var, "var = (*parameters)->Variable[0];" copies the contents of the parameter into var. Then you modify the value of Type within that new copy of var and the original is left unchanged. It's doing exactly what's expected. Â Edit: note, however, that if you were to modify one of the strings within Var, it would be reflected in the original, because those are pointer types. The Type field is a value, not a pointer, so your function has two copies of it - one in the parameter, and another in var. Edited September 19, 2013 by ned 1 Quote Link to comment
charlie87 Posted September 19, 2013 Report Share Posted September 19, 2013 (edited) Thank you for your answer Ned. U r right, I am not very good in C programming. Stars * are my enemy forever. Also the second part of your answer is relevant. I was able to change the values inside the DataArray (pointer) in original, therefore I was confused when I cannot do the same.Maybe, you can help me with another issue. I am expected do change the cluster array BufferPtr, add new elements respectively. I am trying to follow the example posted on the first page of this topic:Â err = DSSetHSzClr(arg1, Offset(MyArray, Cluster) + sizeof(MyCluster)*numElems) Â I have read the specifications and some more examples, but I am not sure that I understand the meaning of the DSSetHSzClr. As I understood, the point is to increase the memory block pointed by the handle arg1. I am little bit confused, how to calculate the block size in my case. I suppose, that the Data which is part of the cluster can be variable sized as well as the strings Name and String. Do I need to calculate the size of the Buffer dynamically (count all items in DataArray and length of all strings) or not ? Edited September 19, 2013 by charlie87 Quote Link to comment
ned Posted September 19, 2013 Report Share Posted September 19, 2013 You're going to have to understand C pointers if you ever want this to work. The concept is simple: a pointer is a integer that contains a memory address. In a struct, a pointer takes up only the amount of space necessary to store that integer. You then need to allocate space somewhere else, initialize it with some value, and update the pointer to point at it. The size of the struct remains the same regardless of whether it contains a pointer to an empty string, or a pointer to a really long string, because the struct itself contains only the pointer. This should answer your question about whether you need to include the string lengths in calculating the buffer size. 1 Quote Link to comment
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.