Jump to content

Using the DLL files of an application compiled with C# with labview


Recommended Posts

2 hours ago, dadreamer said:

I see two main issues here.

  1. Too many events coming - your program just can't handle them all at once (you were already warned about that). How can you deal with it? You decide. I often use a boolean variable switch for that. You may consider something else.
  2. You want to have UninstallStandardCallback function to stop posting into LV, when the loop finishes or no matter what. Now your DLL seems to be trying to post even when the main VI finishes plus the event queue is full with unprocessed events. Try to implement that function on your own, it's not that complex.

-Boolean variable switch must be created inside the  dll file and it is necessary to send a value (1-0) to it from the labview, right?

Link to comment
1 hour ago, alvise said:

-Boolean variable switch must be created inside the  dll file and it is necessary to send a value (1-0) to it from the labview, right?

Yes, something like this:

extern "C" __declspec(dllexport) void __cdecl SetCbState(LVBoolean *state);
// ...
LVBoolean cbState = LVBooleanFalse;
// ...
extern "C" __declspec(dllexport) void __cdecl SetCbState(LVBoolean *state)
{
  cbState = *state;
}
// ...
extern "C" __declspec(dllexport) void __stdcall DataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, DWORD dwUser)
{
  if (cbState==LVBooleanTrue){
    // callback code
  }
}

 

Link to comment
Posted (edited)

 


#include "extcode.h"
#include "hosttype.h"
#include "HCNetSDK.h"




#define LibAPI(retval)       __declspec(dllexport) EXTERNC retval __cdecl
#define Callback(retval)     __declspec(dllexport) EXTERNC retval __stdcall

// Define LabVIEW specific datatypes to pass data as event
// This assumes that the LabVIEW event datatype is a cluster containing following elements in exactly that order!!!
// cluster
//   int32       contains the current live view handle 
//   uInt32      contains the dwDataType (NET_DVR_SYSHEAD, NET_DVR_STD_VIDEODATA, NET_DVR_STD_AUDIODATA, NET_DVR_PRIVATE_DATA,
//                                        or others as documented in the NET_DVR_SetStandardDataCallBack() function
//   array of uInt8     contains the actual byte stream data
#include "lv_prolog.h"
typedef struct
{
	int32_t size;
	uint8_t elm[1];
} 
LVByteArrayRec, * LVByteArrayPtr, ** LVByteArrayHdl;// ** LVByteArrayHdl;pointer to a pointer

typedef struct
{
	LONG realHandle;
	DWORD dataType;
	LVByteArrayHdl handle;
} LVEventData;
#include "lv_epilog.h"
extern "C" __declspec(dllexport) void __cdecl SetCbState(LVBoolean * state);
// where Standard Boolean type case is set: typedef uInt8 LVBoolean
LVBoolean cbState = LVBooleanFalse;
// always set to false on startup
extern "C" __declspec(dllexport) void __cdecl SetCbState(LVBoolean * state)
{
	cbState = *state;
}
// cbstate does a state reassessment.True or False
extern "C" __declspec(dllexport) void __stdcall DataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE * pBuffer, DWORD dwBufSize, DWORD dwUser)
{
	LVEventData eventData = { 0 };
	MgErr err = NumericArrayResize(uB, 1, (UHandle*)&(eventData.handle), dwBufSize);
	if (cbState == LVBooleanTrue && err)// send callback data if there is no error and the cbstatus is true.
	{

		LVUserEventRef userEvent = (LVUserEventRef)dwUser;
		MoveBlock(pBuffer, (*(eventData.handle))->elm, dwBufSize);
		(*(eventData.handle))->size = (int32_t)dwBufSize;
		eventData.realHandle = lRealHandle;
		eventData.dataType = dwDataType;
		PostLVUserEvent(userEvent, &eventData);
		DSDisposeHandle(eventData.handle);
	}
}
//If the above if condition does not occur, the LVUserEventRef here does not take a value.
typedef BOOL(__stdcall* Type_SetStandardDataCallBack)(LONG lRealHandle, void(CALLBACK* fStdDataCallBack) (LONG lRealHandle, DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize, DWORD dwUser), DWORD dwUser);
extern "C" __declspec(dllexport) BOOL __cdecl InstallStandardCallback(LONG lRealHandle, LVUserEventRef * refnum)
{
	HMODULE hDLL = LoadLibraryW(L"HCNetSDK.dll");
	if (hDLL)
	{
		Type_SetStandardDataCallBack installFunc = (Type_SetStandardDataCallBack)GetProcAddress(hDLL, "NET_DVR_SetStandardDataCallBack");
		if (installFunc)
		{
			return installFunc(lRealHandle, DataCallBack, (DWORD)(*refnum));
		}
		FreeLibrary(hDLL);
	}
	return FALSE;
}

image.png.7c3a55eeccd5ebb842d484e2dc0aa5a3.png

I guess you meant something like.By the way, it compiles without problems.

this.As far as I understand, I made comments :) 

Edited by alvise
Link to comment

Why did you leave eventData declaration and NumericArrayResize call outside the condition? Unnecessary work for the callback to do, if the flag is False.

There's also a mistake in your code. Compare with this:

2022-05-31_17-36-22.jpg.9a0720d0e8efe850a56ef440c80b67cd.jpg

Edited by dadreamer
Link to comment
1 hour ago, dadreamer said:

Why did you leave eventData declaration and NumericArrayResize call outside the condition? Unnecessary work for the callback to do, if the flag is False.

The eventData declaration is harmless. This is a stack variable space and gets allocated anyhow on function entry by reserving the according stack space. In the worst case it adds a single MOV rrx, sp+x call before the condition calculation, but it might do that even if you put it inside the if statement because of C compiler optimization to prevent CPU pipeline stalling.

The call to NumericArrayResize() is a different story. This can be a potentially relatively expensive call, so should indeed only be done when necessary.

Edited by Rolf Kalbermatter
Link to comment
Posted (edited)

The reason I left this ''

LVEventData eventData = { 0 };
	MgErr err = NumericArrayResize(uB, 1, (UHandle*)&(eventData.handle), dwBufSize);

''out  A simple mistake to define !err.Didn't realize I hadn't put the ('!'') sign.

  -Do you think I should create it as follows?

 

#include "extcode.h"
#include "hosttype.h"
#include "HCNetSDK.h"

#define LibAPI(retval)       __declspec(dllexport) EXTERNC retval __cdecl
#define Callback(retval)     __declspec(dllexport) EXTERNC retval __stdcall

// Define LabVIEW specific datatypes to pass data as event
// This assumes that the LabVIEW event datatype is a cluster containing following elements in exactly that order!!!
// cluster
//   int32       contains the current live view handle 
//   uInt32      contains the dwDataType (NET_DVR_SYSHEAD, NET_DVR_STD_VIDEODATA, NET_DVR_STD_AUDIODATA, NET_DVR_PRIVATE_DATA,
//                                        or others as documented in the NET_DVR_SetStandardDataCallBack() function
//   array of uInt8     contains the actual byte stream data
#include "lv_prolog.h"
typedef struct
{
	int32_t size;
	uint8_t elm[1];
} 
LVByteArrayRec, * LVByteArrayPtr, ** LVByteArrayHdl;// ** LVByteArrayHdl;pointer to a pointer

typedef struct
{
	LONG realHandle;
	DWORD dataType;
	LVByteArrayHdl handle;
} LVEventData;
#include "lv_epilog.h"
extern "C" __declspec(dllexport) void __cdecl SetCbState(LVBoolean * state);
// where Standard Boolean type case is set: typedef uInt8 LVBoolean
LVBoolean cbState = LVBooleanFalse;
// always set to false on startup
extern "C" __declspec(dllexport) void __cdecl SetCbState(LVBoolean * state)
{
	cbState = *state;
}
// cbstate does a state reassessment.True or False
extern "C" __declspec(dllexport) void __stdcall DataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE * pBuffer, DWORD dwBufSize, DWORD dwUser)
{
	LVEventData eventData = { 0 };
	MgErr err = NumericArrayResize(uB, 1, (UHandle*)&(eventData.handle), dwBufSize);
	if (cbState == LVBooleanTrue && !err)// send callback data if there is no error and the cbstatus is true.
	{
		LVEventData eventData = { 0 };
		MgErr err = NumericArrayResize(uB, 1, (UHandle*)&(eventData.handle), dwBufSize);
		LVUserEventRef userEvent = (LVUserEventRef)dwUser;
		MoveBlock(pBuffer, (*(eventData.handle))->elm, dwBufSize);
		(*(eventData.handle))->size = (int32_t)dwBufSize;
		eventData.realHandle = lRealHandle;
		eventData.dataType = dwDataType;
		PostLVUserEvent(userEvent, &eventData);
		DSDisposeHandle(eventData.handle);
	}
}
//If the above if condition does not occur, the LVUserEventRef here does not take a value.
typedef BOOL(__stdcall* Type_SetStandardDataCallBack)(LONG lRealHandle, void(CALLBACK* fStdDataCallBack) (LONG lRealHandle, DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize, DWORD dwUser), DWORD dwUser);
extern "C" __declspec(dllexport) BOOL __cdecl InstallStandardCallback(LONG lRealHandle, LVUserEventRef * refnum)
{
	HMODULE hDLL = LoadLibraryW(L"HCNetSDK.dll");
	if (hDLL)
	{
		Type_SetStandardDataCallBack installFunc = (Type_SetStandardDataCallBack)GetProcAddress(hDLL, "NET_DVR_SetStandardDataCallBack");
		if (installFunc)
		{
			return installFunc(lRealHandle, DataCallBack, (DWORD)(*refnum));
		}
		FreeLibrary(hDLL);
	}
	return FALSE;
}

 

9 hours ago, dadreamer said:

 

  1. You want to have UninstallStandardCallback function to stop posting into LV, when the loop finishes or no matter what. Now your DLL seems to be trying to post even when the main VI finishes plus the event queue is full with unprocessed events. Try to implement that function on your own, it's not that complex.

It is necessary to create this in the dll code again, right?

 

Edited by alvise
Link to comment

I would do it like this:

First it is important to move the NumericArrayResize() call into the same condition level as the DSDisposeHandle() call. Your code currently leaks a handle every time the callback is called while the cbState boolean is on LVBooleanFalse, which is VERY bad. The fact that it does unnecessary work is not perfect bad a very minor problem in comparison.

// cbstate does a state reassessment.True or False
extern "C" __declspec(dllexport) void __stdcall DataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE * pBuffer, DWORD dwBufSize, DWORD dwUser)
{
    if (cbState == LVBooleanTrue)
    {
        LVEventData eventData = { 0 };
        MgErr err = NumericArrayResize(uB, 1, (UHandle*)&(eventData.handle), dwBufSize);
        if (!err)// send callback data if there is no error and the cbstatus is true.
        {
            LVEventData eventData = { 0 };
            MgErr err = NumericArrayResize(uB, 1, (UHandle*)&(eventData.handle), dwBufSize);
            LVUserEventRef userEvent = (LVUserEventRef)dwUser;
            MoveBlock(pBuffer, (*(eventData.handle))->elm, dwBufSize);
            (*(eventData.handle))->size = (int32_t)dwBufSize;
            eventData.realHandle = lRealHandle;
            eventData.dataType = dwDataType;
            PostLVUserEvent(userEvent, &eventData);
            DSDisposeHandle(eventData.handle);
        }
    }
}

//If the above if condition does not occur, the LVUserEventRef here does not take a value.
typedef BOOL(__stdcall* Type_SetStandardDataCallBack)(LONG lRealHandle, void(CALLBACK* fStdDataCallBack) (LONG lRealHandle, DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize, DWORD dwUser), DWORD dwUser);
extern "C" __declspec(dllexport) BOOL __cdecl InstallStandardCallback(LONG lRealHandle, LVUserEventRef * refnum)
{
    HMODULE hDLL = LoadLibraryW(L"HCNetSDK.dll");
    if (hDLL)
    {
        Type_SetStandardDataCallBack installFunc = (Type_SetStandardDataCallBack)GetProcAddress(hDLL, "NET_DVR_SetStandardDataCallBack");
        if (installFunc)
        {
            if (refnum && *refnum) 
                return installFunc(lRealHandle, DataCallBack, (DWORD)(*refnum));
            else
                return installFunc(lRealHandle, NULL, 0));
        }
        FreeLibrary(hDLL);
    }
    return FALSE;
}

Then write an Uninstall Callback.vi that is essentially the same as the Install Callback.vi but don't pass in any user event refnum. Simply pass a NotARefnum to the second parameter of the Call Library Node.

Edited by Rolf Kalbermatter
Link to comment

But you do realize that once you got the callback reliably posting binary data to your event loop, all the problems up to now are pretty much peanuts in comparison? That bytestream is compressed, with a format that depends on the parameter dwDataType and some information in the data stream, likely not in every package but at specific moments that you have to detect based on the context of earlier packages (which you hopefully haven't suppressed with your inhibit boolean).

And that compression is H264 or maybe MPEG4 or similar for the video data and G722 or such for the Audio packages, but the documentation also talks about other possible formats, most likely depending on the camera model and/or its settings. Going to decode H264 or MPEG4 in LabVIEW itself is going to be a no-no. It may technically be possible but I would not consider being able to write a video decompressor in C and even less in LabVIEW. the math and programming needed for that is simply to complicated. Which leaves only one option: finding an external DLL and interfacing to it and believe me, interfacing to a video decompressor is no easy feat. Your exercises so far would seem almost trivial in comparison, and you are already swimming with your nose barely at the surface.

Edited by Rolf Kalbermatter
Link to comment

I've currently compiled and run the code you suggested last time. Since the true flag is not sent in the dll file, I get the following output when run.

 

EventLog.txt

 

 

image.png.0f822d19cf38f1d56aa92a43fcfc9c7f.png

Why was the "Datacallback" function in the picture above created, is it necessary to get information using that function?


Now I'm trying to figure out how can I send the true-falsa flag.
If I'm not mistaken, I had to create something like below, but now the labview is crashing.

 

Preview CAM-2.vi

 

Link to comment
10 minutes ago, alvise said:

Why was the "Datacallback" function in the picture above created, is it necessary to get information using that function?

Because you exported it. Leave it as is.

11 minutes ago, alvise said:

Now I'm trying to figure out how can I send the true-falsa flag.

Take a boolean button with Switch when released mechanical action and create a new event frame "Value Change" with that button inside. Put a CLFN with SetCbState call, add one parameter in its settings with Adapt to Type -> Handles by Values data type and format. Wire your button to the CLFN. Now you're controlling the callback event flow.

Link to comment
27 minutes ago, alvise said:

Why was the "Datacallback" function in the picture above created, is it necessary to get information using that function?

First the DataCallback() wasn't "created" but "exported. It needs to be present in the DLL so you can pass its address to the according SDK function. But it does not need to be exported from the DLL for that. As long as the Calling convention is configured correctly it will work without exporting it.

And the thing that causes the linker to add that function to the DLL export table is your

extern "C" __declspec(dllexport)

 in front of the DataCallback declaration. It is unnecessary and only helps to confuse you.

Simply defining is like this should be enough:

void CALLBACK DataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE * pBuffer, DWORD dwBufSize, DWORD dwUser)
{
     ...
}

 

Quote

Now I'm trying to figure out how can I send the true-falsa flag.
If I'm not mistaken, I had to create something like below, but now the labview is crashing.

Your nose really is barely above the water level. Take a break, and a deep breath!

You simply need to create a VI that calls the SetCbState() function.

And ohhhhhhhhhhhh my God!!!!!! You defined the event data cluster totally wrong. That "handle" is not an integer value. It is a full and complete LabVIEW byte array!! It shouldn't cause your crash .. at least not immediately! But since you don't tell LabVIEW that it is a handle it can't manage its memory. Meaning with every message that is sent to your event loop, you simply leak that handle each time. That will drive your application into out of memory in no time, just as the incorrect handling of the cbState boolean did. You not only were leaking handles when the cbState boolean was false but with EVERY single callback!!!!!!!!

Edited by Rolf Kalbermatter
Link to comment
16 minutes ago, Rolf Kalbermatter said:

And ohhhhhhhhhhhh my God!!!!!! You defined the event data cluster totally wrong. That "handle" is not an integer value. It is a full and complete LabVIEW byte array!! It shouldn't cause your crash .. at least not immediately! But since you don't tell LabVIEW that it is a handle it can't manage its memory. Meaning with every message that is sent to your event loop, you simply leak that handle each time. That will drive your application into out of memory in no time, just as the incorrect handling of the cbState boolean did. You not only were leaking handles when the cbState boolean was false but with EVERY single callback!!!!!!!!

This explains why he was seeing unstable behaviour from run to run. This is what happens, when two advisers are trying to help simultaneously. 😃 I often prefer a slightly different way to transfer data from the callback. I post a pointer to data and in LabVIEW I read it out with MoveBlock (because I like to see a minimalistic external code and do most of the coding in LV instead).

By the way, I kind of think posting an U32/U64 number might work, if DSDisposeHandle would be moved to the LV Event frame from the callback code and in that frame MoveBlock + DSDisposeHandle would be called.

Edited by dadreamer
Link to comment
21 minutes ago, dadreamer said:

This explains why he was seeing unstable behaviour from run to run. This is what happens, when two advisers are trying to help simultaneously. 😃 I often prefer a slightly different way to transfer data from the callback. I post a pointer to data and in LabVIEW I read it out with MoveBlock (because I like to see a minimalistic external code and do most of the coding in LV instead).

By the way, I kind of think posting an U32/U64 number might work, if DSDisposeHandle would be moved to the LV Event frame from the callback code and in that frame MoveBlock + DSDisposeHandle would be called.

Yes it would work, if you make sure to call the DSDisposeHandle function on EVERY event, without inhibiting its call through some intermediate error cluster handling AND without having other programming errors AND without trying to single step through the code AND, AND, AND.

I prefer to make it as stable as possible rather than as unmaintainable as possible. 😆 Your approach is fine if you get it fully debugged and then slap a password on the VI to prevent the uninhibited from peaking into your VI and mess with its inner workings. Giving the average LabVIEW user even the possibility to tinker with any pointers, is a sure way to make them create a mess.

You mean to say it is their fault if they do that? Sure it is, but many do not have the knowledge to understand that it is. 😀

And if you want to make it 32-bit and 64-bit compatible you have to choose between two uglies that way:

1) define the handle explicitly as U64 on the C side and typecast it to that to pass it to LabVIEW. And treat it as U64 on the LabVIEW side just as any pointer sized integer.

2) create conditional compile code with the event cluster definition. This would be über-ugly.

Edited by Rolf Kalbermatter
Link to comment
30 minutes ago, alvise said:

Right now I made a change as you said. I get the following output.

Is it normal to get an output like the one below? Because sometimes I get Realhandle data. Sometimes I don't.

I have no idea what you mean. It doesn't look wrong to me. The lRealHandle is always 0, which according to the SDK documentation is a valid session handle and should match the value that you pass to InstallCallback after having started the camera stream.

The dwDataType is 1 in the first package which indicates that it is a SysHead package with some initial information about the stream as documented in some of the SDK C samples. dwDataType = 2 seems not to be documented in the SKD help for the StandardDataCallback. But it is for the RealDataCallback which states that it is simply stream data, so not likely meant to be decoded yourself but simply streamed to a file as .ps file as in the SDK sample. And there we are back at the start. To get such a .ps file you could also simply have called NET_DVR_SaveRealData() and forget about all the callback hassles.

Edited by Rolf Kalbermatter
Link to comment
Posted (edited)

By the way, isn't it necessary to use this function and this function? Because I'm currently using this function.

 

55 minutes ago, dadreamer said:

Now correct the cluster contents as proposed and test again.

                   Which cluster do I need to fix?
are you talking about this?image.png.3b1310a8abbe684bf742fd1489600e00.png

 

 

39 minutes ago, Rolf Kalbermatter said:

I have no idea what you mean. It doesn't look wrong to me. The lRealHandle is always 0, which according to the SDK documentation is a valid session handle and should match the value that you pass to InstallCallback after having started the camera stream.

The dwDataType is 1 in the first package which indicates that it is a SysHead package with some initial information about the stream as documented in some of the SDK C samples. dwDataType = 2 seems not to be documented in the SKD help for the StandardDataCallback. But it is for the RealDataCallback which states that it is simply stream data, so not likely meant to be decoded yourself but simply streamed to a file as .ps file as in the SDK sample. And there we are back and the start. To get such a .ps file you could also simply have called NET_DVR_SaveRealData() and forget about all the callback hassles.

What I don't understand is why sometimes Real Handle information is read in "Event Log" and sometimes not. I made 2 different log records below.no real handle info.txthas real handle info.txt

-As I understand "callback" is only required for "Standard Data Callback", it cannot be used to read required video data.
But I'm reviewing the function here it's talking about streaming video data.

 

 

Edited by alvise
Link to comment

Is your cluster elements order correct? Open Context Help window, then select Wiring Tool and hover it over the pink wire. In the Context Help you'll see order of the cluster elements.

Edited by dadreamer
Link to comment
1 hour ago, alvise said:

I made the correction as you said, and I am getting the following log records. Currently, no Realhandle data is visible.

EventLog-15.txt 35.15 kB · 1 download

I have absolutely no idea what you mean when you say that no RealHandle data is visible. As far as that log looks, no event is ever sent. This could be happening if your cbState boolean was never set to true.

Edited by Rolf Kalbermatter
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.