Jump to content

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


Recommended Posts

15 hours ago, alvise said:

Do I understand correctly what you are saying? I still need to create a .dll in C++ to use the labview code you shared here.

No! My example tries to replicate your posted Preview code. In there is only the exception callback which is used for error reporting to the user application and which I dutifully ignored to make the example more simple. Instead I did try to implement proper error handling by using the return value of each API call and requesting the last error information if that return value indicates an error. This should be more than enough for simple example code, most example code out there simply ignores any and all errors, which I find a very bad practise as when something doesn't work you have no idea where it starts to fail.

The SDK documentation seems to indicate that this exception callback is optional so it should not affect the operation of the sample.

Quote

I need to compile the code below (after adapting the part added as C# code to C/C++ language) and create the .dll file, right?

That code does a number of things and some are completely wrong.

1) Forget about trying to write to that PS file in the callback. If you really wanted to have such a file you could simply just call NET_DVR_SaveRealData_V30() on the handle returned from NET_DVR_RealPlay() or NET_DVR_RealPlay_V30() and be done with it without having to bother about setting your own callback.

2) PostLVUserEvent() expects a valid native LabVIEW memory buffer to be passed to and that needs to match the event data type EXACTLY! For a LabVIEW array this is a LabVIEW array data handle.

3) The dwDataType parameter of the callback function is important in order to determine what type of data is in the buffer. You need that when trying to interpret the contents of the buffer, so you need to pass that to LabVIEW as well somehow.

4) Rather than storing the EventRefnum in a global to be referenced from the callback function I would instead pass that information through the user data pointer when installing the callback.

LVUserEventRef *pUE;

void SendEvent(LVUserEventRef *rwer)
{
  pUE = rwer;
}

5) This code stores the address where LabVIEW placed the refnum when passing it to the SendEvent() function. That address is almost 100% certainly used for something else by the time your callback function comes around to try to use it. You need to store the refnum instead, not the reference to it!.

But this probably saved you from nasty crashes because PostLVUserEvent() does verify that the user event refnum is valid before doing anything. If it had tried to process the passed in data according to the user event data type your code for sure would have crashed immediately!!!

This is a simple example of how a proper callback implementation would look like:

#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;

typedef struct
{
	LONG realHandle;
	DWORD dataType;
	LVByteArrayHdl handle;
} LVEventData;
#include "lv_epilog.h"

Callback(void) DataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, DWORD dwUser)
{
	LVEventData eventData = {0};
	MgErr err = NumericArrayResize(uB, 1, (UHandle*)&(eventData.handle), dwBufSize);
	if (!err)
	{
		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);
	}
}

typedef BOOL(__stdcall *Type_SetStandardDataCallBack)(LONG lRealHandle, fStdDataCallBack cbStdDataCallBack, DWORD dwUser);

LibAPI(BOOL) InstallStandardCallback(LONG lRealHandle, LVUserEventRef *refnum)
{
	HANDLE 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;
}

Compile this with your favorite C/C++ compiler. Do not pass .Net, C# or anything like that. They are a detour that can only complicate the matter even more for you but not solve any problems for you, even if you are afraid of C/C++ like the devil is afraid of holy water.

The InstallStandardCallback() function is a convenience function. It tries to dynamically load the HCNetSDK.dll to avoid problems when this DLL containing the callback implementation is loaded before LabVIEW loaded the HCNetSDK.dll itself. Instead you could do a LoadLibrary("<yourDLLName>") call on this DLL in LabVIEW instead, then a GetProcAddress(hDLL, "DataCallBack") and then pass the according pointer as a pointer sized integer in LabVIEW to the Call Library Node that you created to call the NET_DVR_SetStandardDataCallBack() function. In that case you will also need to Typecast the LabVIEW user event refnum into an uInt32 integer and pass that as third parameter to the NET_DVR_SetStandardDataCallBack() function which has this parameter configured as uInt32 Numeric. You can not use pass Native Data Type here, since LabVIEW insists on passing a reference to the refnum in that case and then you start to get the same problem as is explained in point 5) above.

And before you cry victory, consider another very important point: The SDK library will push data down your callback no matter if you process them in LabVIEW or not. If your user event loop serving that user event is not ready or can't keep up with that data stream, the user event queue in LabVIEW will get stuffed with event data messages with potentially huge byte array data in them and after some time it will simply post a threaded Out Of memory dialog that gives you exactly one option, to abort everything and lose whatever work you have not yet stored. So beware!!!!

Quote

There is probably nothing as difficult as putting the pieces together in this confusion.

You are trying to bite off chunks from a cake, that is in many areas far beyond your knowledge.

- Callback functions are complicated even for very seasoned C programmers.

- Memory management is complicated, also for pretty experienced C programmers.

- LabVIEW uses a very specific memory management that is required to allow it to optimize its operations. This memory management contract is not any more complicated than what a .Net library would use, but it is different. And no LabVIEW is not the problem here to not follow .Net. LabVIEW was invented in 1986 and its current memory management system was created somewhere around 1990, give or take a year. .Net was first introduced in 2003 so there is no way the LabVIEW developers could have anticipated what Microsoft will invent about 15 years later.

LabVIEW shields you away from these difficulties almost completely but only if you stay within LabVIEW. Once you start interfacing to external code you have to consider and understand all these things fairly intimately, and in addition to that you have to understand the memory management requirements of the library you are interfacing to, which may or may not be well designed, but never designed to be compatible with LabVIEW, unless developed by a LabVIEW developer such as the IMAQ Vision library is.

- And last but not least: Image handling is complicated, and video handling even more so, even if you stay with LabVIEW ready made libraries.

If I had access to a HikVision camera, even one somewhere officially published on the internet as I do not want to hack anyones private door bell or security camera, I could do more testing, but I'm for obvious reasons not feeling inclined to pay 150 Euro or more, just to buy such a camera to do these tests.

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

And before you cry victory, consider another very important point: The SDK library will push data down your callback no matter if you process them in LabVIEW or not. If your user event loop serving that user event is not ready or can't keep up with that data stream, the user event queue in LabVIEW will get stuffed with event data messages with potentially huge byte array data in them and after some time it will simply post a threaded Out Of memory dialog that gives you exactly one option, to abort everything and lose whatever work you have not yet stored. So beware!!!!

There is a nasty effect, when this is happening. You may notice, that the image on your panel indicator starts to lag behind what is going on before the camera. Easy to verify with moving the camera or the objects in front of it. Event Inspector window is of help here as well, showing that the events are being accumulated and not being processed in time. When no need to view or process each and every frame of the data, I usually introduce a boolean flag, that could allow or deny posting from the DLL to LabVIEW. Most of the time the flag is False and the callback is idling. When I need the data to process, I set the flag to True, grab some frames and set it to False right after. Usually there's no more than a few cameras in the system and this works well.

Link to comment

Thanks for your comprehensive explanations.

Quote

 

No! My example tries to replicate your posted Preview code. In there is only the exception callback which is used for error reporting to the user application and which I dutifully ignored to make the example more simple. Instead I did try to implement proper error handling by using the return value of each API call and requesting the last error information if that return value indicates an error. This should be more than enough for simple example code, most example code out there simply ignores any and all errors, which I find a very bad practise as when something doesn't work you have no idea where it starts to fail.

The SDK documentation seems to indicate that this exception callback is optional so it should not affect the operation of the sample.

 

 

First of all, I would like to go over the sample code you shared, if we are sure that this code will not work, I would like to move on to dll compilation in the next step.
When I tested the code you shared, I recaptured the wire values with the images below. And I took the photos as below. Something is not right here. Do you think there is a problem?

image.png.43261157ff937533a738bf8b72e69dc1.png

image.png.c568870099acb118cbc54ce5ab47deea.png

If you say skip this example, I will skip this example. I will move on to the next steps you wrote.

Edited by alvise
Link to comment
Quote

The 'NET_DVR_RealPlay' parameter is found in very few places. There is not much information about this parameter.

It's documented in the SDK help file although I did not find it in the menu tree. But when you are looking at NET_DRV_RealPlay_v30() there is a reference to it at the end of that page.

It's probably the original RealPlay function in V1.0 of the SDK, then they added V30 and later V40 of these functions and most likely each older version calls in fact the latest V40 with the unused values set to NULL or some default.

"Not executed" in your probe window means that the code in that frame was not executed. It's as simple as that!

And if it was not executed, you have probably not pressed the "start" button (after creating that probe).

Then the probe [5] indicates that you apparently did execute the "start" frame and the output of the NotARefnum function is correctly False, since NOT being NotARefnum means that it was a valid refnum and therefore we can try to retrieve its native window handle to pass to the RealPlay function. That native window handle should then be something else than 0.

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

I tested different numbers, nothing changed.

image.png.8f2a388f55df443466dc0647664b66ca.png

That shows nothing! What is the value of the FP.NativeWindow indicator? It should be something else than 0.

You can change the value of the hPlayWnd control in that cluster to whatever you would like, it should be overwritten by the Bundle by Name node above with the handle value retrieved from the FP.NativeWindow property.

You really should take some of the LabVIEW tutorials first. You try to play with guns and canons but haven't really understood how to operate the basics of using the matches.

Edited by Rolf Kalbermatter
Link to comment
2 hours ago, alvise said:

image.png.727fd2ed4727a69b350d224d900cb8dc.png

Since it is overwritten with bundle, no matter what I enter there, nothing will change, you're right, sometimes I forget what I know in the mess :)

Well, this is clearly the problem. Somehow the FP.NativeWindow does not return a valid handle. There are three possible reasons.

1) NI disabled that node in later LabVIEW versions but I have no idea why they would have done that. It is an undocumented property of course, but that doesn't mean that it should suddenly stop to work.

2) The other is that the front panel of the Empty.vi is not open. But looking at the scrollbars inside the subpanel, it definitely looks like the panel is loaded. Also the Insert VI method for the subpanel did obviously not return an error either. So I'm a bit at a loss. The FP.NativeWindow property simply returns non-0 value on my system.

3) There is an error in on the Start vi. but you of course obscured that part of the VI, so we can't say if that is the reason. lUserID being 0 looks slightly suspicious but of course it could be a valid value since the documentation for the NET_DVR_Login_V30() function states that this is 0 or higher on success, and -1 indicates an error.

 

And I take issue with your comment about the example code I provided being a mess. 😆

Edited by Rolf Kalbermatter
Link to comment

I'm not talking about the complexity of the code you wrote, there are too many details, I'm just talking about the confusion in my head while trying to wrap up the general situation. This situation gets a little confusing. Your code is pretty clean :)

image.png.da2c9ac062ea859d651e80be3adacabd.png

I'm running the start.vi code directly as you shared and I'm actively using the 32-bit domain.

Also I am using LV2018.I cannot find FP.NativeWindow in VI property node.I'm getting the thought that this might be the problem.

Link to comment
5 hours ago, dadreamer said:

There is a nasty effect, when this is happening. You may notice, that the image on your panel indicator starts to lag behind what is going on before the camera. Easy to verify with moving the camera or the objects in front of it. Event Inspector window is of help here as well, showing that the events are being accumulated and not being processed in time. When no need to view or process each and every frame of the data, I usually introduce a boolean flag, that could allow or deny posting from the DLL to LabVIEW. Most of the time the flag is False and the callback is idling. When I need the data to process, I set the flag to True, grab some frames and set it to False right after. Usually there's no more than a few cameras in the system and this works well.

This is good practice.

Depending on what version of LV I'm using, I also usually use the flush event queue with either a time (1 sec) or number of events (10) and raise a warning (dropping frames) if it is exceeded. This makes sure you don't run out of memory.

Link to comment
26 minutes ago, dadreamer said:

Add SuperSecretPrivateSpecialStuff=True line into your LabVIEW.ini and you will find it. 😉

Ok I tried that and saw labview but my attempt to see video stream fails again. That's not the problem here.

 

2 hours ago, Rolf Kalbermatter said:

3) There is an error in on the Start vi. but you of course obscured that part of the VI, so we can't say if that is the reason. lUserID being 0 looks slightly suspicious but of course it could be a valid value since the documentation for the NET_DVR_Login_V30() function states that this is 0 or higher on success, and -1 indicates an error.

Looking at the SDK user manual I read the same thing. This shouldn't be a problem.

Link to comment
3 hours ago, alvise said:

Ok I tried that and saw labview but my attempt to see video stream fails again. That's not the problem here.

Sigh! That ini file setting has absolutely nothing to do with your video streams. It unhides some VI server properties and methods that NI considers not for end user consumption, either because they are highly special, or not fully tested and only meant for internal use. Also it clutters your VI server property and method hierarchy in a way, that it gets very difficult to find anything. With that setting you should be able to see the FP.NativeWindow property in the property list (And your property node for that property should get a dirty (poopy) brown color to indicate that this is an non-public property that you should not expose to end users if you work at NI).

Quote

Looking at the SDK user manual I read the same thing. This shouldn't be a problem.

Yes but that is not helpful information. But did you also check that the Start.vi has no error in?

Link to comment

I guess there is no error in start.vi because according to the NET_DVR_RealPlay connection datasheet, it is possible that it takes a value of 0.

Logging into the device returns the product serial number and everything correct.

image.png.520484a7c0bf70a766abf7b0293b772c.png

image.png.063a43db4063c9a0822fbff614be1971.png

No errors occur here.

The Session output here sometimes gives a value of 0 and sometimes 1 every time the start is pressed.

image.png.30a2bc153c2a036e39b09c7fbd9c3463.png

image.png.e1d68874c56e57adfa46ed1248981c70.png

Link to comment
2 hours ago, alvise said:

I guess there is no error in start.vi because according to the NET_DVR_RealPlay connection datasheet, it is possible that it takes a value of 0.

 

There is really no need to guess, you know!

There is an error in cluster on EVERY VI (and an error out).

You can simply look at the front panel after the VI was invoked. If error in has an error that is pretty obvious, but that would mean that all the functions in that VI do nothing, which could explain why FP.NativeWindow returns 0 as window handle. And after the VI has finished, error out will show an error if error in had an error of course, but also if any function inside the VI encountered any error (that the VI was able to notice).

Link to comment
32 minutes ago, alvise said:

If I understand you correctly, if you are talking about error outputs, there is no error here.

If you say give up, let me compile the code you shared in the next step and continue.

I"m not telling you anything about what to do here. It's strange that FP.NativeWindow seems to return 0 here, it shouldn't and also doesn't on my computer, but that is LabVIEW 2013 in an old Windows 7 VmWare installation (my experimental workhorse). I could try on a new LabVIEW version but really have to do some other, real work too.

The problem with that code I showed you is that even if you can compile it and it works as expected, the real trouble for you will only just have started. The data you will receive in that callback event is NOT any standard image data, it is not even decoded. That means you will still have to decode the H264 or some other compressed data packages that you will receive. And honestly that would be even for me quite a task to solve.

Basically, despite all that tinkering so far you still wouldn't be really much further than what you could have gotten with the RTSP examples posted on the NI knowledge articles. Well not sure about how the camera wants the login to be solved, that could be a bit of a challenge but after that it is pretty smooth sailing except that the data packages you receive that way are still H264 encoded, respectively G711, G722, G723 or similarly encoded audio packages. But that is not different to what you will get with this callback!

Edited by Rolf Kalbermatter
Link to comment

No, I don't want you to waste any more time, if you can tell me what to do, that would be enough.
Since there is no other alternative, I have to compile the code and continue.

Quote

The problem with that code I showed you is that even if you can compile it and it works as expected, the real trouble for you will only just have started. The data you will receive in that callback event is NOT any standard image data, it is not even decoded. That means you will still have to decode the H264 or some other compressed data packages that you will receive. And honestly that would be even for me quite a task to solve. 

maybe I can find the conversion dll for the SDK itself. So I have to deal with it somehow.

 

  HMODULE hDLL = LoadLibraryW(L"HCNetSDK.dll");
    if (hDLL)
    {
        Type_SetStandardDataCallBack installFunc = (Type_SetStandardDataCallBack)GetProcAddress(hDLL, "NET_DVR_SetStandardDataCallBack");
        if (installFunc)

Since "HANDLE" is written here (7 argument of type "HANDLE" is incompatible with parameter of type "HMODULE" hikvision-labview
  there was a problem. I changed it to HMODULE, a bug was resolved.

 

 

typedef BOOL(__stdcall* Type_SetStandardDataCallBack)(LONG lRealHandle,fStdDataCallBack cbStdDataCallBack, DWORD dwUser);

LibAPI(BOOL) InstallStandardCallback(LONG lRealHandle, LVUserEventRef* refnum)

 

,
Error (active)    E0020    identifier "fStdDataCallBack" is undefined    hikvision-labview callback - x86-Debug    D:\HIKVISION\hikvision-labview callback\hikvision-labview callback.cpp    46 

I'm getting this error but what I don't understand is that there are no tokens here  

image.png.489fa76fc0b269c2b0ac5180147e9713.png

hikvision-labview callback.cpp

Edited by alvise
Link to comment

The HMODULE and HANDLE should be the same, but then I compile in C. You probably compile in C++ and there the compiler is usually a lot more picky. It also likely depends on the Windows SDK that comes with your compiler. Newer versions tend to be a lot more strict about defining data types, especially when using C++ mode.

As to the definition for the callback pointer, I was assuming that that is somewhere in the HCNetSDK.h file but didn't check. I compiled it all without that header file. If it is not in there you simply need to copy the definition from the SDK documentation into the file just before that function.

Again this should really not spook you out at all. It shows that you are basically not understanding what callbacks are nor how their C syntax is and that you are in for lots and lots of painful lessons even before you can start to wonder about how to make sense of the gibberish that your callback event will report to you. Using that callback will NOT make life for you easier than it has been so far! Rather the opposite! Problems are just starting to pile on you from here on. As far as decoding H264 data goes, you will be on your own from here on. I have no time to deal with that too, as it is going to be a much more involved task than this exercise so far has been.

Edited by Rolf Kalbermatter
Link to comment

Maybe there's something I'm missing. I tried to debug Rolf's example a bit. It appears that I'm getting zero HWND for the subpanel VI as well! Could it be that NI changed the way VI's are inserted into the main VI? WinSpy++ doesn't even show the window of "Empty.vi" anywhere in the windows hierarchy. I'm using LabVIEW 2019 32-bit right now. Seems to need more testing on different LV versions.

Moreover Get Last Error.vi was reporting an error in NET_DVR_GetErrorMsg configuration. This function has the following prototype:

char* NET_DVR_GetErrorMsg(
  LONG   *pErrorNo
);

But in the CLFN settings pErrorNo was passed as value, not as pointer to value. After the fix it started to work as intended.

Link to comment
1 hour ago, dadreamer said:

Maybe there's something I'm missing. I tried to debug Rolf's example a bit. It appears that I'm getting zero HWND for the subpanel VI as well! Could it be that NI changed the way VI's are inserted into the main VI? WinSpy++ doesn't even show the window of "Empty.vi" anywhere in the windows hierarchy. I'm using LabVIEW 2019 32-bit right now. Seems to need more testing on different LV versions.

Moreover Get Last Error.vi was reporting an error in NET_DVR_GetErrorMsg configuration. This function has the following prototype:

char* NET_DVR_GetErrorMsg(
  LONG   *pErrorNo
);

But in the CLFN settings pErrorNo was passed as value, not as pointer to value. After the fix it started to work as intended.

I was wondering about that too. But then the scrollbars in the image he posted seem to indicate that that VI is actually properly inserted and the Insert VI method doesn't seem to return an error either. With the limited information that he tends to give and the limited LabVIEW knowledge he seems to have, it is all very difficult to debug remotely though. And it is not my job to do really.

Edit: I'll be damned! A VI inserted into a Subpanel does not have a window handle at all. I thought I had tested that but somehow got apparently misled in some ways. LabVIEW seems to handle that all internally without using any Windows support for that. So back to the drawing board to make that not a Subpanel window but instead using real Windows Child window functionality. I don't like to use the main VIs front panel as the drawing canvas as the library would draw all over the front panel and fighting LabVIEWs control and indicator redraws.

As to the NET_DVR_GetErrorMessage() call I overlooked that one. Good catch and totally unexpected! It seems that the GetLastError() call is redundant when calling this function as GetErrorMessage() is not just a function to translate an error code but really a full replacement for GetLastError(). Highly unusual to say the least but you get that for reading the documentation not to the last letter. 😆

It's hard to debug such a software without having any hardware to test with, so the whole library that I posted is in fact a dry exercise that never has run in any way as there is nothing it can really run with on my system. Same about the Callback code. I tested that it compiles (with my old but trusted VS2005 installation) but I can not test that it runs properly. Well I could but that would require to write even more C code to create a test harness that would simulate the Hikvision SDK functionality. I like to tinker with this kind of problems but everything has its limits when it is just a hack job in my free time.😀

Attached is a revisited version of the library with the error Vi fixed and it does not use a SubPanel for now but simply lets the Empty.vi stand on its own for the moment. Quick and dirty but we can worry about getting that properly embedded in the main VI after it has proven to work like this.

HKNetSDK Interface.zip

Edited by Rolf Kalbermatter
  • Haha 1
Link to comment

I don't quite understand what you mean here.

Quote

Edit: I'll be damned! A VI inserted into a Subpanel does not have a window handle at all. I thought I had tested that but somehow got apparently misled in some ways. LabVIEW seems to handle that all internally without using any Windows support for that. So back to the drawing board to make that not a Subpanel window but instead using real Windows Child window functionality. I don't like to use the main VIs front panel as the drawing canvas as the library would draw all over the front panel and fighting LabVIEWs control and indicator redraws.

 

Link to comment
29 minutes ago, alvise said:

I don't quite understand what you mean here.

That wasn't intended for you but dadreamer. So don't worry. 😀

22 minutes ago, alvise said:

I managed to get the image .It's exciting to be this close after a winding road :)

Well, that's good to know. So the whole idea did work after all. I hope that is what you need.

If you also need to have the data saved somewhere, look for the according function mentioned earlier. The callback event really won't help you but just make you feel like all the problems so far have been peanuts in comparison. Believe me!

Link to comment

for the dadreamer I took on what he said :) and created an example using System.windows.form and picturebox and it works too.I do not know if it is the right way.
When I test the sample you sent, sometimes random numbers appear on the screen.What could be the reason for this?

The camera I'm using right now has 2 channel video. I think there is a need for a callback when trying to read both channels at the same time.

What I need is to use this image with IMAQdx afterwards.

Really thanks for everything.

Edited by alvise
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.