Jump to content

Nested DLLs


Recommended Posts

I need to implement a certain comms protocol into some software I will be creating. This has been provided for me as a DLL. The problem is that the DLL uses callbacks for the message processing functions, which I cannot easily do from LabVIEW. So my idea was to create a small wrapper DLL which implements the callback functions but sends the data to LabVIEW using PostLVEvent.  I will have DLL_A  (supplied by my customer), and DLL_A_Wrapper (created by me)

 

Now, something I do not know, can I call DLL_A functions directly from within LabVIEW or do I need to wrap all the DLL_A functions as well?

 

I suppose my question is more like, when LabVIEW loads my wrapper DLL, do two copies of DLL_A exist in memory?

Link to comment

I need to implement a certain comms protocol into some software I will be creating. This has been provided for me as a DLL. The problem is that the DLL uses callbacks for the message processing functions, which I cannot easily do from LabVIEW. So my idea was to create a small wrapper DLL which implements the callback functions but sends the data to LabVIEW using PostLVEvent.  I will have DLL_A  (supplied by my customer), and DLL_A_Wrapper (created by me)

 

Now, something I do not know, can I call DLL_A functions directly from within LabVIEW or do I need to wrap all the DLL_A functions as well?

 

I suppose my question is more like, when LabVIEW loads my wrapper DLL, do two copies of DLL_A exist in memory?

 

You will have to wrap the existing functions too so that you can call them.

 

A better strategy may be to create a separate DLL that just provides a call-back pointer that wraps and invokes the LabVIEW user event. You can then pass the opaque pointer to the existing DLL and get the data in an event structure. You wouldn't need to wrap all the existing functions for pass-through then.

Edited by ShaunR
Link to comment

I need to implement a certain comms protocol into some software I will be creating. This has been provided for me as a DLL. The problem is that the DLL uses callbacks for the message processing functions, which I cannot easily do from LabVIEW. So my idea was to create a small wrapper DLL which implements the callback functions but sends the data to LabVIEW using PostLVEvent.  I will have DLL_A  (supplied by my customer), and DLL_A_Wrapper (created by me)

 

Now, something I do not know, can I call DLL_A functions directly from within LabVIEW or do I need to wrap all the DLL_A functions as well?

 

I suppose my question is more like, when LabVIEW loads my wrapper DLL, do two copies of DLL_A exist in memory?

 

Actually as far as the functions are concerned that do not use any callback pointers you can also call the original DLL directly. A DLL, unless it does some nasty SxS (side by side) loading voodoo, does only get mapped once into every process space and any module (DLL) inside that process referencing a DLL with the same name will have the function pointers relinked to that single memory space.

 

While the method of Shaun to only implement the callback function in your external DLL will work too, it requires you to do a LoadLibrary() and GetProcAddress() call for that callback function. Personally I would tend to wrap the entire function needing a callback parameter into a second DLL.

Link to comment

Thanks guys! This is good news as I was not really very excited to wrap every single other function exported by the DLL.

 

I think I will wrap up the Initialise function (this is the one that requires the two callback pointers), and then implement the two callback functions as simple functions to PostLVEvent.

Link to comment

This is good news as I was not really very excited to wrap every single other function exported by the DLL.

 

Not so fast, friend ...

 

First, can you provide some representative examples of say, some of the more complex functions you intend to invoke from LabVIEW?

 

Also, it would help to know some basic timing diagrams and synchronization requirements between lv and the shared lib; e.g., are function callbacks spray-and-pray, or does the library routine invoking them expect synchronous return values or manipulations of arguments passed as pointers?

 

Ideally, provide a header file, or a good smattering of function declarations, anonymizing variable names into `a`, `b`, etc. if necessary.

 

Also, any cross-platform requirements? Is the DLL C calling convention? a CLR assembly? (obviously not, but `.dll` alone does not constrain how to proceed here)

 

You now have three people tuned into this thread who can potentially save you from less-obvious but even-more-insidious (because, they're not obvious) problems that potentially lie ahead beyond just function pointers.

  • Like 1
Link to comment

Hi Jack,

 

The library I am trying to integrate into my code is a "standard" TCP/IP based message protocol. The only function I need to call that requires the callbacks is the Init.

 

The Init, which is called only once at the start, defines which two other functions are used in the event of a message being received and an error being detected.

int Init(LinkType iLinkType, DataProcessingFunc pDataProcessFunc, ErrorProcessingFunc pErrorProcessFunc);

As far as I can tell, almost all the other functions in the library are used for configuring the communications or sending data, and all of these are "normal" type calls with no fancy prototypes or crazy structs being passed around. The library itself is cross-platform (I know for a fact it can be compiled to run under Linux), I have access to a "normal" 32-bit Windows DLL (not assembly). I am not sure of the calling convention but I think 

 

So my plan was just to wrap up the Init and implement a DataProcessingFunc and ErrorProcessFunc in my DLL, those last two would just pass data straight back to LabVIEW using PostLVEvent.

int Send(tyLinkID iLinkID,tyMessage message);

tyLinkID is just a plain integrer

typedef struct 
{
	tyMessageType		nMsgType;			
	DWORD					nAddress;			
	BYTE					*cType;				/
	WORD					nTypeLength;		
	BYTE					*cData;				
	WORD					nDataLength;		
} 
   tyMessage;

I plan on calling the Send function directly from my LabVIEW application.

 

Any problems that you can see from a cursory glance?

Link to comment

 

So my plan was just to wrap up the Init and implement a DataProcessingFunc and ErrorProcessFunc in my DLL, those last two would just pass data straight back to LabVIEW using PostLVEvent.

int Send(tyLinkID iLinkID,tyMessage message);

tyLinkID is just a plain integrer

typedef struct 
{
	tyMessageType		nMsgType;			
	DWORD					nAddress;			
	BYTE					*cType;				/
	WORD					nTypeLength;		
	BYTE					*cData;				
	WORD					nDataLength;		
} 
   tyMessage;

I plan on calling the Send function directly from my LabVIEW application.

 

Any problems that you can see from a cursory glance?

 

Don't!

 

Add a wrapper to you DLL that accepts the parameters as LabVIEW strings/byte buffers and build the struct in that wrapper.

 

cType and cData are pointers. If you try to call that directly from LabVIEW you end up with two different clusters for 32 Bit and 64 bit LabVIEW if you ever want to go that route, have to bother about struct element alignment yourself and need to do C pointer voodoo on your LabVIEW diagram. In C this is a lot more straightforward and you can let the compiler worry about most of those issues.

 

Jack certainly raised a few interesting points, from which most I simply assumed as given already, considering the question. The one nasty point could be if the caller of the callback function expects a meaningful return value from the callback, which Jack described as requiring a synchronous callback. PostLVUserEvent() will post the message into the user event queue and then return without waiting for the event to be handled. If your DLL expects from the callback to do some specific action such as clearing an error condition or whatever, you either have to build that into your callback before you return, making it really asynchronous to the actual handling of the event, or you need to add extra synchronization between your callback and the actual handling of your user event in a LabVIEW user event structure.

Link to comment

Don't!

 

cType and cData are pointers. If you try to call that directly from LabVIEW you end up with two different clusters for 32 Bit and 64 bit LabVIEW if you ever want to go that route, have to bother about struct element alignment yourself and need to do C pointer voodoo on your LabVIEW diagram. In C this is a lot more straightforward and you can let the compiler worry about most of those issues.

 

cData can be passed as an array of U8 and you avoid all that.

Edited by ShaunR
Link to comment

Rolf, this is a going to stay a 32-bit application for its entire lifetime. There is exactly zero percent chance this will change. So if ShaunR's method works that sounds like the winner to me. I do this kind of DLL interface stuff rarely, so am always struggling to remember the details of pointers and buffers etc.

 

I wan't to try and keep things as simple as possible, and having dealt with memory allocations in the DLL before I would like to try and avoid it if possible.

 

All I need is the data back in LabVIEW via a PostLVUserEvent, I don't think the caller of the callback every expects anything meaningful in return.

 

Now, I have not actually dug any deeper, I have some sample C code that shows the library in use (although here it uses a .lib rather than the DLL, but I expect the function handling to be the same). At some point I am going to need to go through this.

Link to comment

I wan't to try and keep things as simple as possible, and having dealt with memory allocations in the DLL before I would like to try and avoid it if possible.

 

It's unavoidable, unfortunately.

 

Consider this race condition --

 

Let's say your wrapper library uses `PostLVUserEvent()` inside the function callback that the main library calls, in order to send the structure represented by `struct tyMessage` (let's ignore alignment and padding for now; do a little hand-wavy thing knowing it's a surmountable prob).

 

What happens on the LabVIEW side? Let's say this goes into a Register For Events queue and is handled by an event structure. This means, the handling of the event is asynchronous from the callback from the main library.

 

Feel free to pause the tape now and consider the problem here; press play once you think you have an answer ...

 

 

.... that's right -- race condition, where if you lose the race, the kernel is very likely going to throw an Access Violation (if you're lucky) or you're going to be accessing valid, but undefined memory space.

 

Because, my best guess at how the library works is this:

 

```

two malloc's to represent the non-fixed width data in struct tyMessage

invoke your callback, based on function pointer you had passed in Init()

on return of function, perform two free's on those two buffers, having expected the callback to eagerly make its own copy, or otherwise perform all processing before not needing it anymore

```

 

So the other option is to instead of using Register for Events, use a Register Event Callback instead. Inside, you could `MoveBlock()` into a preallocated LStrHandle, but this the least performant, least maintainable, least reliable, and most moving parts. That callback would then need to work as a Flaky Delegate in some manner to broker that information back to your application.

 

The final option (my recommendation, considering information so far) is to create two LStrHandle's (or some other more sensical structure, based on whatever cType and cData actually represent) within your callback, use `PostLVUserEvent()` to get those back into LabVIEW, then destroy the handles, allowing your callback to return control to the main library.

 

In precisely the same vein as our callback here making its own copies, whatever queueing mechanism within LabVIEW uses to put UHandles into an Event Queue, the LabVIEW Memory Manager ensures that a "copy" is made, such that the original entity calling `PostLVUserEvent()` is able to immediately deallocate all resources it just sent -- conceptually, at least, this maps to the mental model.

 

`DSDisposeHandle()` is woefully lacking in documentation how/whether the Memory Manager reference counts the underlying buffer pointed to by a UPtr, but anecdotally this is how I observe it behaves. Said another way -- even though your callback is responsible for the initial allocation (ref count == 1), `PostLVUserEvent()` then allows the enqueuer to make another claim on that underlying buffer (ref count == 2), then your callback calling `DSDisposeHandle()` will atomically* decrease the count (ref count == 1) without the Memory Manager actually `free()`ing the buffer. Later, in the Event Structure, once that data has been accessed via the left-hand Event Data node and the frame finishes executing, the data goes out of scope, at which point ref count drops to 0 and an actual `free()` (or at least, a conceptual release of that resource) is done.

 

Clear as mud?

Link to comment

cData can be passed as an array of U8 and you avoid all that.

 

 

Doo, if the function protototype really looks like:

int Send(tyLinkID iLinkID,tyMessage message);

then the message struct is indeed passed by value and the function prototype effectively would be mostly equivalent to 

int Send(tyLinkID iLinkID, tyMessageType nMsgType, DWORD nAddress, BYTE *cType, WORD nTypeLength, BYTE *cData, WORD nDataLength);

For this particular type it should be ok to configure the CLN like this, but there is an undefined behaviour for other possible data structs where the element alignment would cause the elements to be aligned on other than the natural address boundary.

 

For instance a struct like this:

typedef struct
{
     char byte1;
     char byte2;
     char byte3;
     char *string;
};

it's not sure if the C compiler would pack the first 3 bytes into a single function parameter or not, making the binary function interface potentially different between different C compilers!

 

As to UHandle reference counting. As far as I'm aware of, LabVIEW does not do reference counting on handles. The reason being that simple reference counting isn't really enough for LabVIEW optimizations but there also needs to be some way of marking if a handle is stomped on (modified). Doing this kind of optimization during compilation (LabVIEW diagram to DFIR and DFIR optimization) delivers much better results than trying to optimize handle reuse at runtime.

 

Generally LabVIEW C functions do not take ownership of a handle passed into it, unless specifically documented to do so. From anything I can see, PostLVUserEvent() is no execption to this. LabVIEW does simply create a deep copy of the entire data structure.

Link to comment

OK experts C experts :-)

 

My goal is to write my wrapper DLL with as least fuss as possible, as my C skills are not great.

 

I have access to the .lib files, it seems to me that life would be easier if I statically linked the additional libraries as then I do not need to mess about with loadlibrary, GetProcAddress etc etc. Is this sensible?

 

Note the code I am going to interface to is quite old, and in all liklihood is not going to change.

Link to comment

OK experts C experts :-)

 

My goal is to write my wrapper DLL with as least fuss as possible, as my C skills are not great.

 

I have access to the .lib files, it seems to me that life would be easier if I statically linked the additional libraries as then I do not need to mess about with loadlibrary, GetProcAddress etc etc. Is this sensible?

 

Note the code I am going to interface to is quite old, and in all liklihood is not going to change.

 

There isn't a really compelling reason not to use static import libraries other than that you won't be able to load the wrapper DLL without the secondary DLL being available too in a Windows searchable location. With LoadLibrary() you can implement your own runtime failure when the DLL can't be found (or add custom directories to attempt to load your secondary DLL from) while with the static import library LabVIEW will simply bark at you that it could not load the wrapper DLL despite that you can clearly see it on disk at the expected location.

Link to comment

OK experts C experts :-)

 

My goal is to write my wrapper DLL with as least fuss as possible, as my C skills are not great.

 

I have access to the .lib files, it seems to me that life would be easier if I statically linked the additional libraries as then I do not need to mess about with loadlibrary, GetProcAddress etc etc. Is this sensible?

 

Note the code I am going to interface to is quite old, and in all liklihood is not going to change.

 

The one outstanding reason to use dynamic loading is you can replace the dependencies without recompiling your DLL (as long as the interface doesn't change). So you can update your driver DLL with a newer version just by overwriting the old one (either manually or with an installer). You will find this a must if you ever piggyback off of NI binaries which change with each release/update. With static bindings it is forever set in stone for that *.lib version until you decide to recompile with the new lib..

Link to comment

Shaun, that is in line with the conclusions I reached from my research, so thanks for also confirming it.

 

The code I am interfacing with is not going to change. However if it t does we will just have to rebuild my DLL.


It's unavoidable, unfortunately.

 

Consider this race condition --

 

Let's say your wrapper library uses `PostLVUserEvent()` inside the function callback that the main library calls, in order to send the structure represented by `struct tyMessage` (let's ignore alignment and padding for now; do a little hand-wavy thing knowing it's a surmountable prob).

 

What happens on the LabVIEW side? Let's say this goes into a Register For Events queue and is handled by an event structure. This means, the handling of the event is asynchronous from the callback from the main library.

 

Feel free to pause the tape now and consider the problem here; press play once you think you have an answer ...

 

 

.... that's right -- race condition, where if you lose the race, the kernel is very likely going to throw an Access Violation (if you're lucky) or you're going to be accessing valid, but undefined memory space.

 

Because, my best guess at how the library works is this:

 

```

two malloc's to represent the non-fixed width data in struct tyMessage

invoke your callback, based on function pointer you had passed in Init()

on return of function, perform two free's on those two buffers, having expected the callback to eagerly make its own copy, or otherwise perform all processing before not needing it anymore

```

 

So the other option is to instead of using Register for Events, use a Register Event Callback instead. Inside, you could `MoveBlock()` into a preallocated LStrHandle, but this the least performant, least maintainable, least reliable, and most moving parts. That callback would then need to work as a Flaky Delegate in some manner to broker that information back to your application.

 

The final option (my recommendation, considering information so far) is to create two LStrHandle's (or some other more sensical structure, based on whatever cType and cData actually represent) within your callback, use `PostLVUserEvent()` to get those back into LabVIEW, then destroy the handles, allowing your callback to return control to the main library.

 

In precisely the same vein as our callback here making its own copies, whatever queueing mechanism within LabVIEW uses to put UHandles into an Event Queue, the LabVIEW Memory Manager ensures that a "copy" is made, such that the original entity calling `PostLVUserEvent()` is able to immediately deallocate all resources it just sent -- conceptually, at least, this maps to the mental model.

 

`DSDisposeHandle()` is woefully lacking in documentation how/whether the Memory Manager reference counts the underlying buffer pointed to by a UPtr, but anecdotally this is how I observe it behaves. Said another way -- even though your callback is responsible for the initial allocation (ref count == 1), `PostLVUserEvent()` then allows the enqueuer to make another claim on that underlying buffer (ref count == 2), then your callback calling `DSDisposeHandle()` will atomically* decrease the count (ref count == 1) without the Memory Manager actually `free()`ing the buffer. Later, in the Event Structure, once that data has been accessed via the left-hand Event Data node and the frame finishes executing, the data goes out of scope, at which point ref count drops to 0 and an actual `free()` (or at least, a conceptual release of that resource) is done.

 

Clear as mud?

 

 

Jack, I have gone through some sample code where the two callbacks are implemented.

 

In the Receive callback the function calls malloc/memcpy on the data it is passed in.

 

My plan for my wrapper is to allocate some memory for LabVIEW to use, once in my Init function. Then I will copy the data structure into that memory and PostLVEvent using my copy.

Link to comment

Good luck here! Report back if/when you run into trouble.

 

BTW, I'd tend to lean toward static linking here; it better matches the risk/reward profile in the circumstances you lay out.

 

In terms of performance, static versus dynamic linking is utterly negligible in virtually all application domains -- it's a mostly a matter of your deployment/distribution/upgrade scenarios required by the application.

Link to comment

This is actually simple task. All the proposed solution may get you to the finish line, but it all seems quite complicated.

 

You need to figure out for yourself, if is the callback prototypes have some user parameter (void * user_param) that allow you to pass the EventRefNum, the PostLVEvent can use. If not, you need to declare it (the EventRefNum) as a variable in one of your *.c files, so it is a DLL global and write a function, that sets the DLL variable to the correct RefNum value of user event you created in labview code.

 

I wouldn't dive into doing some memory copies into memory preallocated by LabVIEW. Just make a LabVIEW "aware" data in your DLL code. It is the cleanest solution. You do not need to dispose any handles, just make a new one for the data, show it into PostLVEvent and harvest it in event structure with native LabVIEW code. LabVIEW will take care of all cleaning up (you do not need to dispose the handle you made).

 

If you are not familiar with making new handles, here is code to help you out!

 

Bellow is a callback function to pass logging data into LabVIEW (exactly like you need). Important variables are: "s" (the data you wanna pass to LV) and "NewInfo" (The LabVIEW aware copy)

If you need to allocate some crazy stuff for your data (the structure of the data is complex as a woman), here is some code to guide you. It deals with Array (handle) of clusters of 2 strings (handle). 

static void avlog_cb(void *avcl, int level, const char * szFmt, va_list varg)
{
	static int print_prefix = 1;
	char s[LINE_SZ];

	if (level <= g_ErrInfo.GetErrorLevel()) //post or not to post the log?
	{
		MgErr Err = mgNoErr;

		g_FcTb.FUNC_av_log_format_line.getPtr()(avcl, level, szFmt, varg, s, LINE_SZ, &print_prefix); //need to use coz of vsprintf is enemy with ffmpeg timeformat

		//length = vsprintf_s(s, n - 1, szFmt, varg); vsprintf is not compatible with some ffmpeg log formats

		if (g_ErrInfo.GetEventRef()) //if the UserEvent is selected for logging
		{
			LStrHandle NewInfo = nullptr;
			NewInfo = (LStrHandle)(DSNewHandle(Offset(LStr, str) + sizeof(uChar))); //make a new handle
			assert(NewInfo != nullptr);
			(*NewInfo)->cnt = 0;
			Err = LStrPrintf(NewInfo, (CStr)"LOG_DLL;%d;%s;%s", level, (avcl != nullptr ? (*(AVClass **)avcl)->class_name : ""), s); //print into handle
			assert(Err == mgNoErr);
			Err = PostLVUserEvent(g_ErrInfo.GetEventRef(), &NewInfo); //post to the log
			assert(Err == mgNoErr);
			Err = DSDisposeHandle(NewInfo);
			assert(Err == mgNoErr);
		}
		else //else output to Dbg window
		{
			Err = DbgPrintf("LOG_DLL;%d;%s;%s", level, (avcl != nullptr ? (*(AVClass **)avcl)->class_name : ""), s);
			assert(Err == mgNoErr);
		}
	}
}

Over here is a nice simple example from NI engineer. Makes a thread that sends simple structure to labview. Notice, that he doesn't use malloc or new to make a data copy, since PostLVUserEvent makes a deep copy of the data structure.

 

http://forums.ni.com/t5/LabWindows-CVI/using-PostLVUserEvent-function-from-Labview-dll/td-p/2510908

typedef struct
{
	LStrHandle key;
	LStrHandle value;
} DictElement, **DictElementHdl;

typedef struct DictionaryArr
{
	int32_t dimsize;
	DictElement Arr[1];
} **DictionaryArrHdl;

void PrintChar2LVStrHandle(const char *charstr, LStrHandle *StrHandleP, bool forcenew)
{
	MgErr error = mgNoErr;
	std::string temp = "";
	if (charstr)
	{
		temp.assign(charstr);
	}
	if ((!IsHandleEmpty(StrHandleP)) && !forcenew)
	{
		error = DSDisposeHandle(*StrHandleP);
		if (error != mgNoErr)
			throw(WrpExcUser("PrintChar2LVStrHandle()", error));
	}
	*StrHandleP = (LStrHandle)(DSNewHandle(Offset(LStr, str) + sizeof(uChar)));
	if (*StrHandleP == nullptr)
		throw(WrpExcUser("Could not allocate string handle", CUSTOMERROR));
	//(**StrHandleP)->cnt = 0;
	error = LStrPrintf(*StrHandleP, (CStr)"%s", temp.c_str());
	if (error != mgNoErr)
		throw(WrpExcUser("Could not print to string handle", error));
}

void FFmpegDict2LVDict(DictionaryArrHdl *LVDictArr, AVDictionary *FFDictArr) //if there are no entries to copy, this function just makes an ampty LVarray
{
	MgErr error = mgNoErr;
	AVDictionaryEntry *DictE = nullptr;
	int count = g_FcTb.FUNC_av_dict_count.getPtr()(FFDictArr); //get entries count
	int i = 0;

	if (LVDictArr)
	{
		if (*LVDictArr)
		{
			for (i = 0; i < (**LVDictArr)->dimsize; ++i)
			{
				error = DSDisposeHandle((**LVDictArr)->Arr[i].key);//dispose disposable handles here
				(**LVDictArr)->Arr[i].key = nullptr;//set them to nullptr
				if (error != mgNoErr)
					throw(WrpExcUser("Error disposing LV dictionary entry handle", error));
				error = DSDisposeHandle((**LVDictArr)->Arr[i].value);//and here
				(**LVDictArr)->Arr[i].value = nullptr;
				if (error != mgNoErr)
					throw(WrpExcUser("Error disposing LV dictionary entry handle", error));
			}
			error = DSSetHSzClr(*LVDictArr, Offset(DictionaryArr, Arr) + sizeof(DictElement)*count);//set correct size
			if (error != mgNoErr)
				throw(WrpExcUser("Error scaling video decoder info handle", error));
		}
		else //the array is empty ?? should not happen, means fucntion returns not used dictionary options, but none supplied
		{
			*LVDictArr = (DictionaryArrHdl)DSNewHClr(Offset(DictionaryArr, Arr) + sizeof(DictElement)*count); //make a new handle
			if (*LVDictArr == nullptr)
				throw(WrpExcUser("Error creating dictionary LV array handle", CUSTOMERROR));
		}
		//at this point the array is ready for the dict entries to be copied
		i = 0;
		while (DictE = g_FcTb.FUNC_av_dict_get.getPtr()(FFDictArr, "", DictE, AV_DICT_IGNORE_SUFFIX))
		{
			// iterate over all entries in FFDictArr
			PrintChar2LVStrHandle(DictE->key, &(**LVDictArr)->Arr[i].key, true); //copy key string
			PrintChar2LVStrHandle(DictE->value, &(**LVDictArr)->Arr[i].value, true); //copy value string
		}
		//at this point all the keys and values are in the LV array
		(**LVDictArr)->dimsize = count; //set the LV array size
		g_FcTb.FUNC_av_dict_free.getPtr()(&FFDictArr);
		//FF dict entry and FF dict arr should now be nullptr
	}
	else
		throw(WrpExcUser("Empty array handle pointer", INVALIDPTR));
	//first dispose handles in existing LVarray
	//resize the LVarray to correct size
	//while dict_get fill the array
	//set correct array size
	//do not free the dictionary, it is done by av_dict_free
}
Edited by bublina
Link to comment

I wouldn't dive into doing some memory copies into memory preallocated by LabVIEW. Just make a LabVIEW "aware" data in your DLL code. It is the cleanest solution. You do not need to dispose any handles, just make a new one for the data, show it into PostLVEvent and harvest it in event structure with native LabVIEW code. LabVIEW will take care of all cleaning up (you do not need to dispose the handle you made).

 

I directly contest these statements. Provide an executable test case here that indicates what you say is true.

 

Am I missing something here? In your example you allocate a handle each time you send an event but never deallocate it. And right below that you say that it's not necessary to copy the original data since PostLVEvent will create its own copy of the data!

 

*edit: "what RolfK said"

Link to comment

 

Over here is a nice simple example from NI engineer. Makes a thread that sends simple structure to labview. Notice, that he doesn't use malloc or new to make a data copy, since PostLVUserEvent makes a copy of the top level data structure.

 

http://forums.ni.com/t5/LabWindows-CVI/using-PostLVUserEvent-function-from-Labview-dll/td-p/2510908

 

 

OK, having downloaded that example; the data posted is allocated on the stack, not on the heap. Major difference. That's why that particular example you cite has no memory leak.

 

But creating a new DSHandle without freeing it -- definitely a memory leak.

 

Now, ideally, `LVPostUserEvent()` would have an additional (boolean) parameter that indicates "take ownership of this data" -- to avoid caller bookkeeping and provide vastly superior performance by not bring the Memory Manager into the mix making a new allocation -- but the LVRT is back in caveman ages, so we are relegated to caveman practices.

*edit: And a couple minutes more code review on that example: alignment brittleness, turns CPU into a space heater, assumes receiver is capable of pacing publisher (I sincerely doubt this is true; my hunch is that a slow memory leak would eventually crash LabVIEW, sicne publisher is not throttled in time and recv is not throttled in space; that said, no "substantial" work is done in event handler case, which *might* mean this contrived example itself would not crash, but that certainly doesn't extrapolate to reader-application-domain-du-jour). It's not a horrible example, just misleading and kinda bad. And another race condition on labview side in registration.

Link to comment

 

Am I missing something here? In your example you allocate a handle each time you send an event but never deallocate it. And right below that you say that it's not necessary to copy the original data since PostLVEvent will create its own copy of the data!

 

 

 

You are not missing anything. I posted a code, that leaks memory  :frusty: , it is fixed now. To be perfectly honest, I thought, that you really do not need to clean that up, since it will get a record in the global memory table and LabVIEW will clean it up. Somehow. Magically.

*edit: And a couple minutes more code review on that example: alignment brittleness, turns CPU into a space heater, assumes receiver is capable of pacing publisher (I sincerely doubt this is true; my hunch is that a slow memory leak would eventually crash LabVIEW, sicne publisher is not throttled in time and recv is not throttled in space; that said, no "substantial" work is done in event handler case, which *might* mean this contrived example itself would not crash, but that certainly doesn't extrapolate to reader-application-domain-du-jour). It's not a horrible example, just misleading and kinda bad. And another race condition on labview side in registration.

 

I don't understand the space time thingy. Does it still apply after I fixed the posted code?

Edited by bublina
Link to comment

To be perfectly honest, I thought, that you really do not need to clean that up, since it will get a record in the global memory table and LabVIEW will clean it up.

 

 

To be fair, your expectation here is utterly reasonable and desirable. It's the C<->LabVIEW FFI is utterly unreasonable and undesirable, and virtually unchanged since the 1900's.

Link to comment

To be fair, your expectation here is utterly reasonable and desirable. It's the C<->LabVIEW FFI is utterly unreasonable and undesirable, and virtually unchanged since the 1900's.

 

Jack, can you please reply to the post #23? After rereading your suggestion, I see it is the same as mine and I just didn't understand the text after first glance.

 

The main problem with the C<->LabVIEW is, that it is undocumented, completely.

 

Also, is my presumption:

that if I allocate a handle, wire it out of the DLL node into a indicator terminal, it will get garbage collected,

correct?

Edited by bublina
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

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