02-10-2015 12:56 PM
Hello all,
This question probably has been asked, but I can't find the answer. So here's my question:
I have built a LabVIEW *.so in Linux and I want to call it from a "c" application. The LabVIEW *.so is returning a cluster of strings and I want to know how to call it from my c application (memory allocation?).
Here's the *.so source code, the function is named "testvi":
Here's my c application source code:
#include <stdio.h> #include <string.h> #include "testclusterofstrings.h" int main() { Cluster_Of_Strings clusterofstrings; Testvi(&clusterofstrings); printf("-------------\n"); printf("String_A: %s", (*clusterofstrings.String_A)->str); printf("-------------\n"); return 0; }
I'm getting the following output when calling my application:
LabVIEW caught fatal signal
13.0 - Received SIGSEGV
Reason: address not mapped to object
Attempt to reference address: 0x0x19f5c381
Segmentation fault (core dumped)
So, what is the proper way to do this?
Thanks,
Michel
Solved! Go to Solution.
02-11-2015 10:53 AM
Hello,
I've been working on this and here's what I came up with by looking at different post, and this is working.
So, I'd like to know if it the "clean" way to do it ????? I'm not fluent in c .... 😞
Please help, thanks.
#include <stdio.h> #include <string.h> #include "testclusterofstrings.h" char *LV_str_to_C_str(LStrHandle lv_str); int main() { Cluster_Of_Strings MeasInfo; int lengthOfString = 1024; MeasInfo.String_A = (LStrHandle)DSNewHandle(sizeof(int32) + lengthOfString * sizeof(uChar)); MeasInfo.String_B = (LStrHandle)DSNewHandle(sizeof(int32) + lengthOfString * sizeof(uChar)); memset(LStrBuf(*MeasInfo.String_A), '0', lengthOfString); memset(LStrBuf(*MeasInfo.String_B), '0', lengthOfString); Testvi(&MeasInfo); char *c_strA = LV_str_to_C_str(MeasInfo.String_A); char *c_strB = LV_str_to_C_str(MeasInfo.String_B); printf("-------------\n"); printf("String_A: %s\n", c_strA); printf("String_B: %s\n", c_strB); printf("-------------\n"); return 0; } char *LV_str_to_C_str(LStrHandle lv_str) { int i; char *c_str = (char*) malloc((*lv_str)->cnt*sizeof(uChar)+1); for (i=0; i<(*lv_str)->cnt; ++i) c_str[i] = (*lv_str)->str[i]; c_str[(*lv_str)->cnt]=0; return c_str; }
02-11-2015 11:07 AM
By the way, it is also working with this minimal code. So it looks like we only have to initialize the "MeasInfo" with the minimal size of LStrHandle two elements (int32 cnt, uChar str) is this right? Is this the way to initialize it?
#include <stdio.h> #include <string.h> #include "testclusterofstrings.h" char *LV_str_to_C_str(LStrHandle lv_str); int main() { Cluster_Of_Strings MeasInfo; MeasInfo.String_A = (LStrHandle)DSNewHandle(sizeof(int32) + sizeof(uChar)); MeasInfo.String_B = (LStrHandle)DSNewHandle(sizeof(int32) + sizeof(uChar)); Testvi(&MeasInfo); char *c_strA = LV_str_to_C_str(MeasInfo.String_A); char *c_strB = LV_str_to_C_str(MeasInfo.String_B); printf("-------------\n"); printf("String_A: %s\n", c_strA); printf("String_B: %s\n", c_strB); printf("size %d", (sizeof(int32) + sizeof(uChar))); printf("-------------\n"); return 0; } char *LV_str_to_C_str(LStrHandle lv_str) { int i; char *c_str = (char*) malloc((*lv_str)->cnt*sizeof(uChar)+1); for (i=0; i<(*lv_str)->cnt; ++i) c_str[i] = (*lv_str)->str[i]; c_str[(*lv_str)->cnt]=0; return c_str; }
02-11-2015 10:39 PM - edited 02-11-2015 10:43 PM
If its a labview-built dll and you're passing parameters by ref, I'm not too surprised you have to initialize it (although I would expect labview to be friendly enough to allocate the data structures for you). Maybe if you passed it a null pointer instead it would work? From your original post, maybe try this:
int main() {
Cluster_Of_Strings * clusterofstrings = NULL;
Testvi(clusterofstrings);
.... }
For some reason I remember reading that labview will interpret the null as a sign that it needs to allocate the structure, but I could be totally insane on that point.
If that doesn't work, then yes you'll have to allocate all the handles as appropriate. From <labview>\cintools\extcode.h you can see that a string is defined as follows:
typedef struct {
int32 cnt; /* number of bytes that follow */
uChar str[1]; /* cnt bytes */
} LStr, *LStrPtr, **LStrHandle;
Since you have size-0 arrays I think you really just need to call DSNewHClr(sizeof(int32)) which will allocate a handle with all 0s, and 0 is what you want. Final result would be...
int main() { Cluster_Of_Strings MeasInfo; MeasInfo.String_A = (LStrHandle)DSNewHClr(sizeof(int32)); MeasInfo.String_B = (LStrHandle)DSNewHClr(sizeof(int32)); Testvi(&MeasInfo); .... }
Oh, and for the string functions, make sure you look at the built-in functions first before you make your own.
02-12-2015 03:43 AM - edited 02-12-2015 03:49 AM
@smithd wrote:
If its a labview-built dll and you're passing parameters by ref, I'm not too surprised you have to initialize it (although I would expect labview to be friendly enough to allocate the data structures for you). Maybe if you passed it a null pointer instead it would work? From your original post, maybe try this:
int main() {
Cluster_Of_Strings * clusterofstrings = NULL;
Testvi(clusterofstrings);
.... }
For some reason I remember reading that labview will interpret the null as a sign that it needs to allocate the structure, but I could be totally insane on that point.
If that doesn't work, then yes you'll have to allocate all the handles as appropriate. From <labview>\cintools\extcode.h you can see that a string is defined as follows:
typedef struct {
int32 cnt; /* number of bytes that follow */
uChar str[1]; /* cnt bytes */
} LStr, *LStrPtr, **LStrHandle;
Since you have size-0 arrays I think you really just need to call DSNewHClr(sizeof(int32)) which will allocate a handle with all 0s, and 0 is what you want. Final result would be...
int main() { Cluster_Of_Strings MeasInfo; MeasInfo.String_A = (LStrHandle)DSNewHClr(sizeof(int32)); MeasInfo.String_B = (LStrHandle)DSNewHClr(sizeof(int32)); Testvi(&MeasInfo); .... }Oh, and for the string functions, make sure you look at the built-in functions first before you make your own.
Actually, the whole thing is both a little easier and more complicated at the same time. LabVIEW is fully managed with its data types but you have to follow that management contract when you interface to LabVIEW code from C.
First, the first attempt with allocating a string handle with sizeof(int32) + sizeof(uChar) bytes without initializing the length element is wrong. That length element could contain any value and cause LabVIEW to wrongly assume that the handle is already big enough to fill in its data and not do anything and then writing over the end of the allocated buffer.
Also initialisation of the structure with NULL is not going to work. This cluster has to be provided by the caller as it is a fixed size data area passed in as a pointer. However initialisation of the string handles inside the cluster with NULL should work fine, since LabVIEW considers NULL handles as the canonical zero length handle.
However after you have called the LabVIEW DLL function you are the owner of any memory that was allocated by that function and returned to you, just as you would be if you had allocated those handles yourself before the call. So proper etiquete is to also deallocate it and this is not optional but a requirement or you create memory leaks. It doesn't get noticed here since your test program terminates anyhow right after but it will bite you badly in a bigger application if you forget this.
The code could then look something like this:
int main() { Cluster_Of_Strings MeasInfo; MeasInfo.String_A = NULL; MeasInfo.String_B = NULL; Testvi(&MeasInfo); printf("-------------\n"); printf("String_A: %s\n", LV_str_to_C_str(MeasInfo.String_A)); printf("String_B: %s\n", LV_str_to_C_str(MeasInfo.String_B)); printf("size %d", (sizeof(int32) + sizeof(uChar))); printf("-------------\n");
if (MeasInfo.String_A)
DSDisposeHandle(MeasInfo.String_A);
if (MeasInfo.String_B)
DSDisposeHandle(MeasInfo.String_B);
return 0; }
// Returns the pointer to the string buffer in a LabVIEW string handle that has been made
// sure to be zero terminated.
char *LV_str_to_C_str(LStrHandle lv_str)
{
if (lv_str && !NumericArrayResize(uB, 1, (UHandle*)&lv_str, LStrLen(*lv_str) + 1))
{
LStrBuf(*lv_str)[LStrLen(*lv_str)] = 0;
return LStrBuf(*lv_str);
}
return NULL;
}
02-12-2015 08:17 AM
Hello,
It's always a pleasure to receive such a detailed answer. Good note about memory management, I was looking for that info also.
I have incorporated the modification in my code that you provided Rolf and it's working.
So, from what I understand is that when "LabVIEW see NULL handles" it will allocate the appropriate memory and return it to the calling application?
What would be the best resources (books, article, white paper...) to know how to work with the "LabVIEW memory manager" (other than the list of functions in the help)?
Thanks,
Michel
02-12-2015 09:22 AM
@rolfk wrote:
Actually, the whole thing is both a little easier and more complicated at the same time. LabVIEW is fully managed with its data types but you have to follow that management contract when you interface to LabVIEW code from C.
First, the first attempt with allocating a string handle with sizeof(int32) + sizeof(uChar) bytes without initializing the length element is wrong. That length element could contain any value and cause LabVIEW to wrongly assume that the handle is already big enough to fill in its data and not do anything and then writing over the end of the allocated buffer.
Also initialisation of the structure with NULL is not going to work. This cluster has to be provided by the caller as it is a fixed size data area passed in as a pointer. However initialisation of the string handles inside the cluster with NULL should work fine, since LabVIEW considers NULL handles as the canonical zero length handle.
Ah, that string null must be what I was thinking of. Since he was passing the cluster by ref I figured LabVIEW might interpret that in the same way as it does for strings, but maybe not.
Thats also the nice thing about using the DSNewHClr function, as it gives "sets" the size to 0.
So, from what I understand is that when "LabVIEW see NULL handles" it will allocate the appropriate memory and return it to the calling application?
From what rolf said, this only works with string handles and probably some of the other managed array types (since they all have that same format of length+data).
What would be the best resources (books, article, white paper...) to know how to work with the "LabVIEW memory manager" (other than the list of functions in the help)?
I don't personally know of anything besides searching through the forums. That having been said, I think if you can understand what rolf posted you understand enough to get by.
02-12-2015 02:03 PM
Well, when LabVIEW receives a handle and wants to write into it, it ALWAYS resizes the handle to the size needed. There might be some optimization at some places that that resizing only is done if the underlaying handle is actually smaller than what is needed. This is possible since the handle "knows" actually how big it is, independent of the length value in a string and array handle. This resizing is consistently written in such a way that a NULL handle will cause a call to DSNewHClr() while a non-null handle will cause a call to DSResizeHandle().
This applies to all string and array handles but not to any scalars or records(clusters). For the later normal C rules applies which means the memory has to be always allocated by the caller, either implicit on the stack (local variable) or explicit through a proper malloc()/DSNewPtr() call.
As to memory managemen of handles, whoever ends up receiving them is the manager and needs to make sure they are deallocated properly. This means when you leave your function and pass back a cluster containing handles you either leave the handles as they are, and your caller is then responsibe to manage them or you deallocate them AND make the handle a NULL value so the caller doesn't attempt to double free them later on. If you are the top level context receiving a handle (meaning it doesn't get passed further up to someone else to take care about) you always have to deallocate it. Otherwise you create a leak.
This are about the rules. There is no specific documentation that goes deeper into details and there really doesn't need to be. The rules are pretty logical in terms of normal C memory management. The only special case are the handles. Normally memory has always to be allocated by the caller. Anything else creates a VERY special case that needs to be explicitedly documented for that function and a function needs to be defined to deallocate the buffer once it is not needed anymore. This is important since you can't just specific that the buffer was malloc()d and needs to be free()d. The malloc() your library calls to create the buffer may reside in a different C runtime library than the free() your caller calls. So you need to provide a function that calls the matching free() in the same C runtime library as the malloc() that was used.
By using always LabVIEW memory manager functions for handles, this problem is almost completely solved. (There are some difficulties if you call a LabVIEW created shared library from a LabVIEW program where the shared library was created in a different version than the LabVIEW version you use to call the shared library.
02-12-2015 03:00 PM
For my understanding:
This is a comment from your first reply of this post:
"First, the first attempt with allocating a string handle with sizeof(int32) + sizeof(uChar) bytes without initializing the length element is wrong. That length element could contain any value and cause LabVIEW to wrongly assume that the handle is already big enough to fill in its data and not do anything and then writing over the end of the allocated buffer."
Then in the previous post you wrote:
"Well, when LabVIEW receives a handle and wants to write into it, it ALWAYS resizes the handle to the size needed."
So, does the first comment still valid?
02-12-2015 06:15 PM - edited 02-12-2015 06:36 PM
@Michel_Gauvin wrote:
For my understanding:
This is a comment from your first reply of this post:
"First, the first attempt with allocating a string handle with sizeof(int32) + sizeof(uChar) bytes without initializing the length element is wrong. That length element could contain any value and cause LabVIEW to wrongly assume that the handle is already big enough to fill in its data and not do anything and then writing over the end of the allocated buffer."
@Then in the previous post you wrote:
"Well, when LabVIEW receives a handle and wants to write into it, it ALWAYS resizes the handle to the size needed."
So, does the first comment still valid?
Absolutely.
DSNewHandle(sizeof(int32) + sizeof(uChar)) will allocate a handle that can store the int32 length and a single character.
Since you use DSNewHandle() and not DSNewHClr() the memory inside the handle will NOT be initialized and will contain whatever random bytes there were before, which will be most likely not zero bytes. So LabVIEW will possibly check that value and if it is bigger than what it needs it may not attempt to reallocate the handle for performance reasons. And clash, bum, everything goes haywire.
So the ALWAYS in my second comment is a little strong. But not knowing the internal implemenation of every function you have to play safe. This means never pass in unitialized handles but also never assume that a handle might not get rellocated which would invalidate any pointer into the handle as soon as you call a function that MIGHT modify the handle.