Jump to content

the issue of calling dll passing arguments by pointer


Recommended Posts

hi,

I am in trouble now!

I have a dll , i want get same altered values from the parameters by passing pointers as arguments to a function.my function definition is as following.

========mydll.h========

##ifdef __cplusplus

extern "C" {

#endif

__declspec(dllexport) char *str (int *num,char **ch);

#ifdef __cplusplus

}

#endif

=========mydll.cpp======

#include "mydll.h"

char *str(int *num,char **ch)

{

char *pstr = "this is string!";

*num = 10;

*ch =pstr;

return *ch;

}

and then I create another project to test this dll ,it works correctly. but if I use labview to call the dll,it is incorrect.

in c++,call the function as follows:

int a = 0;

char *b ="";

char *rtnStr = str ( &a,&b);

after executing the "str" function,the return value and all arguments are:

a = 10;

b = this is string!

rtnStr = this is string!

but in labview, I wrap this dll by setting return value as string(c string pointer),setting parameters as numeric (pointer to value) and string(c string pointer).

I think my setting is correct!

but the result is incorrect!

a = 10

b =(unknowing char)

rtnStr = this is string!

Why "b" can not get the string as calling in C++? if I want to get the value by passing pointer. How should I do ?

Link to comment

Working with pointers in LV through DLLs can be a bit very confusing IMO. LV does not know how memory is allocated in the DLL, so it has no idea what to return when memory is allocated/deallocated. There are special memory functions in external.h, but I have never used them myself. The way I do it is to have certain rules that I know will work, for me at least.

Never return a dynamic value (array and similar with unknown size) in the return argument. Only return basic types like integers (could be pointer) or floats or nothing at all.

Allways allocate memory in LV when returning arrays and similar within the function body.

When you have lots of complex data types (arrays, clusters, objects) in the DLL, leave them there and return a pointer to LV. Preferably a pointer to a class. Use LV to call the methods via the pointer and return/send data only when needed through well defined get/set methods where all array sizes are defined in LV.

In your special case, I think what is wrong is that LV does not know the size of the return string, it doesn't know the size of anything really. You have to refactor so that both the return string and b have memory allocated in LV before the call, and this includes a wrapper that returns void or int or something.

Link to comment

QUOTE (bsvingen @ Apr 6 2009, 02:59 PM)

Working with pointers in LV through DLLs can be a bit very confusing IMO. LV does not know how memory is allocated in the DLL, so it has no idea what to return when memory is allocated/deallocated. There are special memory functions in external.h, but I have never used them myself. The way I do it is to have certain rules that I know will work, for me at least.

Never return a dynamic value (array and similar with unknown size) in the return argument. Only return basic types like integers (could be pointer) or floats or nothing at all.

Allways allocate memory in LV when returning arrays and similar within the function body.

When you have lots of complex data types (arrays, clusters, objects) in the DLL, leave them there and return a pointer to LV. Preferably a pointer to a class. Use LV to call the methods via the pointer and return/send data only when needed through well defined get/set methods where all array sizes are defined in LV.

In your special case, I think what is wrong is that LV does not know the size of the return string, it doesn't know the size of anything really. You have to refactor so that both the return string and b have memory allocated in LV before the call, and this includes a wrapper that returns void or int or something.

thanks very much!

It is not a good idea to use dlls returning pointer argument ,I think so too. because the dll is not mine, I only have the head file and dll, it seems that I must re-disposal it in C++. thanks again!

Link to comment

QUOTE (greatwall @ Apr 6 2009, 11:04 AM)

thanks very much!

It is not a good idea to use dlls returning pointer argument ,I think so too. because the dll is not mine, I only have the head file and dll, it seems that I must re-disposal it in C++. thanks again!

There are basically three possibilities. The first type of pointers are scalars passed by reference such as your first parameter (int* name) is. This is not a problem in LabVIEW. The second are arrays passed to the DLL such as a (char *buf). These have to get allocated by the caller in all cases and you can do that in LabVIEW also using for instance the Initialize Array function.

The third are arrays passed by reference (char **buf) both allocated by the caller or the DLL. Since LabVIEW doesn't have a pointer datatype it can not really represent such a datatype itself. And if the DLL allocates such a pointer things get even more complicated since LabVIEW has no way of knowing what type of heap manager allocated them but it has a very specific expectation of how memory blocks are allocated.

If you can change the DLL (even if you have the source code that is not always an option) you can adapt the functions to play with compatible LabVIEW datatypes or use the LabVIEW memory manager functions from extcode.h to create functions that accept directly LabVIEW native datatypes. You would then configure the corresponding parameter in the Call Library Node to Adapt to Type. Doing this last option requires both a very good understanding of C in general as well as about the use of those LabVIEW memory manager functions.

If changing the DLL is no option you can always create an intermediate DLL that does translate between whatever the original DLL requires and what works best for the LabVIEW interface. Here too you have the choice to either use LabVIEW compatible C datatypes or use the LabVIEW memory manager fucntions to deal directly with the native LabVIEW data buffers.

Rolf Kalbermatter

Link to comment
  • 3 weeks later...

QUOTE (greatwall @ Apr 5 2009, 08:39 PM)

=========mydll.cpp======

#include "mydll.h"

char *str(int *num,char **ch)

{

char *pstr = "this is string!";

*num = 10;

*ch =pstr;

return *ch;

}

There are a couple of issues really.

The statement

char **ch

declares a pointer to a pointer, so it is wanting to manipulate the reference you have to the string, I expect labview would have a few issues with that as it may cause loss of data because you are losing a reference to whatever that wire held. I really don't know how it would interact with labview, but I'd guess it might not be nice.

The return of (*ch) is pointless, you have already modified the callers data in the line (*ch = pstr;)

Now probably the biggest problem.

char *pstr = "this is string!";

declares a pointer that (potentially, compiler dependant) points into the data section of the DLL, this will possibly be marked read only by the OS and any code down the line that tries to alter that value would fail if so with an access violation.

It is far too compiler dependant to use.

It is probably this last factor that is causing issues, you would have to allocate a new chunk of memory and initialise it with the value. Additionally if the DLL moves out of memory then your memory reference would then be invalid.

Link to comment
  • 3 weeks later...

I agree with the other posters on general good style for calling DLLs from LabView with non-simple types: allocate the memory in LabView, pass a pointer to the LV-allocated buffer to the DLL as an argument (usually along with a length) and allow the DLL to modify the buffer that was passed in. So a C function that converted a lower-case string to upper case might have prototype:

int ConvertToUpperCase (char *string, int length)

where the return value is used for error handling.

Some DLLs return a pointer to data in the return value. It's not ideal but sometimes you have to work with what your are given. There is no way to access this directly using the Call Library Function, but there is a workaround that is described in this post

http://forums.lavag.org/How-to-get-data-fr...aded#entry38166

which links to this example

http://zone.ni.com/devzone/cda/epd/p/id/3672

The "secret trick" (thanks, Rolf!) that you do not need to write your own DLL to do this; the LabView dev env and runtime export a "MoveBlock" function that you can use to copy data from an area of memory that is private to the DLL to an area of memory allocated by LabView. (Since the DLL is loaded by LabView, it isn't an access violation to READ the data, but chaos will result if LabView tries to MODIFY the data.) You return a pointer from your function and then use MoveBlock to copy that into an area of LV-allocated memory, which LabView can then copy/move/delete as it wishes. In the function above, you'd have to call MoveBlock multiple times, once for each pointer dereference required. (Yuck!!!) As others have pointed out, plenty can go wrong if the internal data changes while you are doing this (for example, it is device driver). In the example below, I throw a mutex around the calls to prevent me from inadvertently calling the library with the same instance in a different VI.

Something that isn't clear is what data type to return pointers. Lately I have been using the Call Library Function's "unsigned pointer-sized integer" to handle pointers and size_t integers. On my platform this always returns a UInt64. In fact the function I am calling works with size_t ints set to either "pointer sized" or Uint32s. This would not be the case if LabView were actually passing U32s or U64s at my request---any parameters following the first goof would be corrupted. LabView's documentation is pretty sparse on this, but my guess is that since wires size needs to be defined at compile time, the compiler always allocates 64 bits of storage for "pointer sized integers" on the diagram, but inside the call library function it passes either 32 or 64 bits as appropriate. I don't think this "pointer-sized integer" is foolproof because it requires LabView and the DLL to agree on what that is, but it seems safer than blindly assuming a U32.

If you have source code for the DLL, you can wrap it with your own code and define fixed-size types.

post-15310-1242314904.png?width=400

Link to comment

QUOTE (Rob Calhoun @ May 14 2009, 10:29 AM)

Something that isn't clear is what data type to return pointers. Lately I have been using the Call Library Function's "unsigned pointer-sized integer" to handle pointers and size_t integers. On my platform this always returns a UInt64. In fact the function I am calling works with size_t ints set to either "pointer sized" or Uint32s. This would not be the case if LabView were actually passing U32s or U64s at my request---any parameters following the first goof would be corrupted. LabView's documentation is pretty sparse on this, but my guess is that since wires size needs to be defined at compile time, the compiler always allocates 64 bits of storage for "pointer sized integers" on the diagram, but inside the call library function it passes either 32 or 64 bits as appropriate. I don't think this "pointer-sized integer" is foolproof because it requires LabView and the DLL to agree on what that is, but it seems safer than blindly assuming a U32.

Your assumption is mostly right. The reason it is a 64 Bit integer is not exactly because the wire needs to be a known data size at compile time since a 64 bit platform is considered a different platform and a recompile would be necessary anyhow if you go from 32 Bit to 64 Bit or vice versa.

But imagine you passing this pointer to a subVI. To make this work LabVIEW would either have to create a new pointer size datatype or as it does now, always use a 64 Bit (unsigned) integer. The pointer size datatype would be cool as LabVIEW could treat it as separate type incompatible to anything else and you could in such a way avoid a user wiring pointers to integers and vice versa. But it is also a very strict notion and primitives such as add or minus would either need to be updated to accept such a datatype or you could not do pointer arithmetics (offsets) on them. And you would need to find a new wire colour, and change many other functions to support this type too, and, and, and.....

Since any sane VI library developer will avoid bothering his/her library user to bother about such things by having such pointers on the top level API, the fact that the pointer sized parameter can be wired with integers is not such a big deal. The person doing the Call Library Node VIs hopefully knows what she is doing and the rest don't have to worry about that ;) .

As it is now the Call Library Node will pick for such parameters the correct 32 Bit part or the entire 64 bit part of the integer based on which platform it is running. There is no ambuguity that makes this not foolproof, since you either work with 32 Bit LabVIEW and a 32 bit DLL or 64 Bit LabVIEW and a 64 Bit DLL. Mix and match between these two is not possible and a 64 Bit DLL will always use 64 Bit pointers and the same applies of course for 32 Bit DLLs.

Of course if your DLL does some nasty tricks with WOW64 and passes those none-native pointers to the caller you would need to adapt for that by NOT using the pointer sized parameter types but explicitedly selected integer sizes. But anything that is declared as pointer in the prototype, and looks, smells and sounds like a pointer will use the platform pointer size that LabVIEW is also using when you select pointer sized integer.

Rolf Kalbermatter

QUOTE (stevea1973 @ Apr 26 2009, 08:01 PM)

There are a couple of issues really.

The statement

char **ch

declares a pointer to a pointer, so it is wanting to manipulate the reference you have to the string, I expect labview would have a few issues with that as it may cause loss of data because you are losing a reference to whatever that wire held. I really don't know how it would interact with labview, but I'd guess it might not be nice.

You actually can do that by defining it as an (pointer sized) integer passed by reference.

BUT!!!!!! You need to be very careful about what you do then.

First you can not access the data in that pointer directly from within LabVIEW. You either need specific functions in the DLL to return information from that pointer or need to us the unofficially well documented MoveBlock() calls to copy the information out of the pointer into LabVIEW data buffers.

Second and this is important: You need in most cases to call the according function to deallocate this pointer after you are finished with it. Otherwise you create a memory leak. I say in most cases since it depends what the function really returns. If it is just the pointer to some statically allocated buffer you better don't try to deallocate it. The same applies if it returns just a copy of the pointer that was passed in as parameter and therefore got in fact allocated by the caller, here your LabVIEW diagram.

In all other cases however the library will either have to export a deallocation function (most cases) or document exactly which platform API has to be used to deallocate the resource. A free() will not cut it here since that can depend on the C runtime the application or DLL was compiled with so that the application and DLL can in fact use different memory manager libraries, and pointers from either part have no meaning for the C runtime memory manager calls of the other. (Obviously they are allocated in the same process space so can be accessed from both parts but functions like realloc() and free() might crash badly if applied to pointers coming from another subsytem/DLL).

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.