Trouble With Struct And Loading Dll Library


thuatnguyenbk

Recommended Posts

Hi, i have a problem with struct and dll file. I was loaded successfully when calling a function in dll file. But when i define another struct to use as a prototype for extern dll, and i faild. what wrong with me??? 

 

  • Here is spec of this function in dll:
Long BS_ReadLog( int handle, int startTime, int endTime, int* numOfLog, BSLogRecord* logRecord )
 
with BSLogRecord:
typedef struct { 
 
 unsigned char event; 
 unsigned char subEvent; 
 unsigned short tnaEvent; 
 time_t eventTime;    // 32 bits type 
unsigned userID; 
unsigned reserved2; 
} BSLogRecord;
 
 
  • Here's my code in DAQ:

class BSLogRecord

   local Event
   local SubEvent
   local tnaEvent
   local EventTime
   local UserID
   local Reserved
endclass
 
// Read log
extern("BS_SDK.dll","long BS_ReadLog(long, long, long, long[1], BSLogRecord[1])", "BSReadLog", "stdcall")
 
Error return: Invalid argument specified.: load Line 27 - Uncaught error in sequence load

 

Please hepl me, many thanks.

Link to comment
Share on other sites

Its important to understand that DAQFactory is not C or C++, and does not have the same datatypes.  Numbers are numbers, and strings are strings, and there is no correllary with C data types (except I suppose, scalar numbers are doubles).  Objects in DAQFactory are definitely NOT structures like a C++ or C structure, so while creating an object to represent a structure is a good idea, since that is what you would do in C++, its not appropriate in DAQFactory when trying to pass it to a C++ DLL.  They are apples and oranges.  Using objects to create structures in DAQFactory is fine, you just can't pass the object to anything else as is.  For one thing, there is no place for you to tell what the types are for each of the elements in the object.  You have a combination of 8, 16, and 32 bit values, and no where in the DAQFactory declaration do you or can you specify that.

 

So, to pass a structure to an external DLL, you instead need to tell the extern() function that you are going to pass an array of bytes where the array is the size of the structure.  This is tricky, because different compilers can sometimes do different packing.  In your case, its probably pretty constant across compilers and the first 3 members of the structure will be packed into one 4 byte unit.  If it was mixed, i.e. char, then long, then short or similar, then you have to worry about alignment.  The compiler would probably then make the char take up 4 bytes so it could align the long to a 4 byte interval from the beginning of the structure.  Anyhow, assuming your "unsigned" members are actually 32 bit, then BSLogRecord is 16 bytes long, so you should specify an array of 16 bytes in the prototype for extern().  

 

I should say here while I'm thinking of it that declaring variables simply as "unsigned" is poor practice and is going to mess things up if the code ever gets compiled into a 64 bit application.  By specifying just "unsigned" you are therefore working under the assumption that the compiler will interpret what's missing as a "long", but assuming anything with a computer is usually a bad idea, especially a compiler.  If the compiler is working in 64 bit mode (or 16 if you were using some old embedded processor), then it will likely assume you mean a 64 bit longlong, not a 32 bit long.

 

Moving on, to initialize BSLogRecord with values you'll need to create an array with 16 numbers, then use the From. functions to convert your numbers into byte arrays then insert the bytes in the appropriate spots in the array.  For example:

 

bsLogRecord[12] = transpose(from.uLong(userId),0)

 

You have to transpose because the From. functions return an array in the columns dimension, not rows.

 

Reading the value after calling your function is just the opposite, use the To. functions to take chunks of your 16 byte array and convert into numbers.  For example, to get userID you would do:

 

userId = to.uLong(bsLogRecord[12,15])

 

Not that you might need different forms of uLong and uWord to match the byte ordering, but assuming that your DLL was compiled for the same processor as DAQFactory, which it almost certainly is, you probably won't need the reverse versions of those functions.  They are more designed when you are connecting to a remote device over a serial or ethernet connection and that device is using a different processor, and possibly different byte / word ordering.

Link to comment
Share on other sites

This function is defined in dll:

Long BS_ReadLog(int handle, int startTime, int endTime, int* numOfLog, BSLogRecord* logRecord)

startTime and endTime are unix time, BSLogRecord is a struct of event, subEvent, tnaEvent, eventTime, userID and  reserved2

 

My code in DAQ:

global result

global SocketHandle

global numOfLog

 

global Event

global SubEvent
global tnaEvent
global EventTime
global UserID
global  reserved2

global bsLogRecord = transpose(from.uLong(EventSubEventtnaEventEventTimeUserID, reserved2),0)

 

// Load dll

extern("BS_SDK.dll","long BS_OpenSocket(string, long, long[1])", "CMAS_BSOpenSocket", "stdcall")

extern("BS_SDK.dll","long BS_ReadLog(long, long, long, long[1], long[6])", "CMAS_BSReadLog", "stdcall")

 

// Open socket, a handle will returned to SocketHandle

result = CMAS_BSOpenSocket("192.168.0.70", 1471, @SocketHandle)

?result

 

// Read log of handle, number of log record and log record are returned to numOfLog and bsLogRecord

result = CMAS_BSReadLog(SocketHandle, 1392033600, 1392120000, @numOfLog, @bsLogRecord)

 

?result
 
Function readlog can't excuted. Could you help me. Thanks you so much.
Link to comment
Share on other sites

OK, there are a number of things wrong.  First, you have to treat the structure as simply an array of bytes.  Not an array of longs, and not a structure in DAQFactory.  So your prototype should be:

 

uchar[16]  

 

not 

 

long[6]

 

unless you are sure that your compiler is 4 byte aligning all the parameters in the structure (which I doubt).  I suppose technically, you could do

 

unsigned long[4]

 

That will likely work with event, subevent and tnaEvent packed into the first long because the other 3 are unsigned longs.  4 unsigned longs is the same as 16 bytes.  In fact, that might be easier for you provided the byte ordering is the same (which is likely). 

 

So you get:

 

extern("BS_SDK.dll","long BS_ReadLog(long, long, long, long[1], unsigned long[4])", "CMAS_BSReadLog", "stdcall")

 

Then, just initialize bsLogRecord as:

 

global bsLogRecord = fill(0,4)

 

Don't do your transpose(from.uLong()).  Its incorrect because you listed 6 parameters.  And unnecessary.

 

After that you called the function correctly.  When it returns, if successful, bsLogRecord[1] should hold EventTime, [2] should have UserID, and [0] will have Event, SubEvent and tnaEvent packed into it.  Use from.uLong(bsLogRecord[0]) to unpack it.

 

private packed = from.uLong(bsLogRecord[0])

Event = packed[0][0]

SubEvent = packed[0][1]

tnaEvent = to.uShort(packed[0][2,3])

 

 

The result is something like this:

 

global result
global SocketHandle
global numOfLog
 
global Event
global SubEvent
global tnaEvent
global EventTime
global UserID
global  reserved2
global bsLogRecord = fill(0,4)
 
// Load dll
extern("BS_SDK.dll","long BS_OpenSocket(string, long, long[1])", "CMAS_BSOpenSocket", "stdcall")
extern("BS_SDK.dll","long BS_ReadLog(long, long, long, long[1], long[4])", "CMAS_BSReadLog", "stdcall")
 
// Open socket, a handle will returned to SocketHandle
result = CMAS_BSOpenSocket("192.168.0.70", 1471, @SocketHandle)
?result
 
// Read log of handle, number of log record and log record are returned to numOfLog and bsLogRecord
result = CMAS_BSReadLog(SocketHandle, 1392033600, 1392120000, @numOfLog, @bsLogRecord)
 
?result
 

private packed = from.uLong(bsLogRecord[0])

Event = packed[0][0]

SubEvent = packed[0][1]

tnaEvent = to.uShort(packed[0][2,3])

 
EventTime = bsLogRecord[1]
UserID = bsLogRecord[2]
Link to comment
Share on other sites

  • 1 month later...

Having some trouble calling a DLL. So tried doing something simple like call the windows message box and not getting that to work. What am I doing wrong?

 

function from user32.dll

int WINAPI MessageBox(  _In_opt_  HWND hWnd,  _In_opt_  LPCTSTR lpText,  _In_opt_  LPCTSTR lpCaption,  _In_      UINT uType);

my code

extern ("user32.dll", "ulong MessageBox(long, string, string, ulong)", "MBox", "stdcall")
global x = MBox(null, "Text", "Caption", 0)

Link to comment
Share on other sites

I'm pretty sure I talked about this somewhere else here, but then again, there is a lot of info on the forum.  Most all Windows functions, at least those that take strings, have two versions, one for Unicode, one for ANSI.  So, even though you put: messageBox() in your code, the compiler uses build flags to determine whether to compile that as MessageBoxW or MessageBoxA.  This is determined by one of the Windows header files (winuser.h I'd imagine).  MessageBox() alone does not actually exist in User32.dll.

 

On that note, I strongly recommend to anyone doing DLL stuff to use the dependency walker tool, as this tool makes it very clear both what the actual names of the exposed functions are, as well as what sort of other DLL dependencies the DLL you are using needs.  You can download this tool from the web.  Its free and very small.

 

So, if you just change your code to be MessageBoxA instead of MessageBox it will work.  Note that DAQFactory is NOT unicode, so you never want to use the wide character versions of Windows functions.

Link to comment
Share on other sites

Thanks. Yes MessageBoxA works. Will attempt to move ahead from here. Knowing not to try to use any unicode is helpful.

 

Is there a list somewhere that matches up C or C++ variable types with DAQFactory equivalents? Felt like I was guessing some on that part of the function declaration.

Link to comment
Share on other sites

So does that mean it makes no difference if you use Long vs. uLong for example, or Short vs. Long, or Long vs. Float because in DAQFactory they are all just numbers?

 

Am I understanding the manual correctly? If the external C function expects a pointer, you need to use an array notation for the DAQFactory prototype, and then when you call the function use an @ before the variable name.

 

What happens if the DLL function you are calling returns a pointer to varying length array of elements? If you made your prototype myarray[10] and the pointer pointed to an array of 15 elements would DAQFactory array only contain the first 10? If you made your prototype myarray[10] and the pointer only pointed to an array of 5 elements would DAQFactory array contain 5 or 10 elements? If 10, would be in the last 5 elements?

Link to comment
Share on other sites

It makes all the difference because in order to call the C function DAQFactory has to convert its numbers into the appropriate type, and the only way it knows the appropriate type is based on what you specify in the prototype.  If you specified short and the value was actually long, the stack would be corrupted because DAQFactory would only put 2 bytes for the short, and the function would expect 4.

 

As for pointers, yes, you need to use array notation in the prototype.  That's how DAQFactory knows that its a pointer.  Even if its a pointer to a single value, you have to at least put [1].  DAQFactory will then create the appropriately sized memory block, pass the pointer to it to the function, then on return, convert the memory block back into the DAQFactory value.  If you make the prototype myarray[10] and the function puts 15 elements there, you would have memory corruption and DAQFactory would likely crash, though maybe not immediately.  This is no different if you did it in C and is called a buffer overrun.  Functions should never return more data than the caller expects, otherwise the caller has no idea how much memory to allocate.  In cases where C functions return a variable amount of data, where the caller has no idea how much data there is until the function returns, the C function typically will take a pointer to a pointer and allocate the memory itself and return the allocated length.  This, by the way, is why the C standard template library has replaced sprintf() with snprintf() as a safer form.  With sprintf() you have to be sure to allocate enough memory to cover whatever sprintf() does, and if the specifier has a bug (for example you do %f and think its going to be 8 characters, but its a double so does 16), you could get a buffer overrun.  With snprintf() you also pass the length of your buffer, and the snprintf() function makes sure not to write past the end.  

 

If you make it [10] and the function only returns 5 values, I'm not completely sure what would happen.  I'd have to look at the code.  I'm sure it won't crash, because DAQFactory allocated memory for 10 values and the function only changed the first 5 values.  I suppose it depends on what DAQFactory defaults the memory too.  It will either be the original value of the array in DAQFactory, if the values exist, or I would guess 0.  Again, this really isn't any different than C.  If you pass a pointer to memory allocated for 10 elements, and the function only changes 5, its up to you to know this act appropriately.

 

Link to comment
Share on other sites

Ok, thanks for your reply. I think I'm getting a better idea of how DAQFactory is working. I wasn't sure what it would do with arrays since in other situations you never bother with dimensioning the number of elements, but in this case dimensioning is required and once dimensioned to a certain length that will be the number of elements. So if afterwards you append a record to the array it will be added after the dimensioned length.

 

When I asked "So does that mean it makes no difference if you use Long vs. uLong for example, or Short vs. Long, or Long vs. Float because in DAQFactory they are all just numbers?" I was wondering about the return values from an external function call. Based on your reply I'm assuming it does matter because DAQFactory will use the specified type as a basis for transformation into the DAQFactory number format which is always an 8 byte floating point value.

Link to comment
Share on other sites

Since it does make a difference what size the variables are in a DLL and that the DAQFactory declarations must match, I'm wondering how to know what variable declarations to use for handle variables and int variables since they might be different on 32 vs. 64 bit versions of Windows and possibly vary based on the compiler used to create a DLL. Will DAQFactory figure out what size to use for int's and handles and pointers? If not, will calling the DLL with the wrong declarations cause memory problems?

Link to comment
Share on other sites

Same applies for return values.  DAQFactory needs to know what type is being returned by the function so it knows how to convert it.

 

"handle" and "int" are not supported in the prototype. That's why we have long, short, byte instead.  On 32 bit compilers, int is the same as long, but as you pointed out, on 64 bit compilations it could be 64 bit (depends on compiler).  For this reason I don't like to use int even in my C++ code.  Its not portable because it doesn't have a fixed meaning.  It is your responsibility when you see "int" in the header file for the DLL to determine what it means.  Most likely its "long".  You have to determine the meaning for "handle" too, because handle is not a real data type.  Its an alias and defined somewhere in the header chain.  Usually handle is an unsigned long, but that varies depending on the API / DLL.

 

As for 64 bit DLLs, it is unlikely that DAQFactory can load a 64 bit DLL since its a 32 bit app.  You'd likely have to create some sort of 32 bit wrapper DLL.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.