[Webinar] Streamline your web hosting managementRegister Today

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 899
  • Last Modified:

Calling a C++ Created DLL Function from VB6 with User Define Type

Have an API written in C++ that has the following function.

SD_GetPacket

Call specification:
int SD_GetPacket(char *buffer, unsigned short sizeofbuffer);

Return Values:
Return success=1, no data=0, fail=-error_no (-1=not enough space, others for future
expansion)

Discussion:
This function is called to return data from the API. This can be done by simply polling in a
timer or by calling the routine following reception of a message denoting data arrival.
Regardless of the method, SD_GetPacket should be called in a while loop structure so that
on each message or timer event all available data is processed until it returns a zero value.

Parameters:
char *buffer – buffer to receive the packet information received.
The data delivered is in the flowing format;
Description Type Offset Length
The stream the data was delivered from (i.e. 20-38) Word 0 2 bytes
A unique key for this packet used internally by SD Byte array 2 10
Length of the following payload (i.e. the packet) Word 12 2
Data (BettorData Packet), variable length Byte Array 14 <32767

If you were to define a suitable structure using C Language, the following would suit;
#define DDS_MAX_CHUNK 32766
#define UNIQUEIFIER_LEN 10
typedef struct
{
unsigned short stream; //SD Stream number
unsigned char key[UNIQUEIFIER_LEN]; //SD Unique Key
unsigned short elen; //Length of data payload following
unsigned char data[DDS_MAX_CHUNK]; //Data payload from BettorData
}sd_data_packet;
unsigned short sizeofbuffer – size of the memory block passed above. In C Language
using the structure above this is best specified using something similar to
sizeof(sd_data_packet).

Now trying to work out the best way to call this function from VB6, at present I am getting Bad DLL Calling Convention.

I've presently defined the function as follows:
Public Declare Function SD_GetPacket Lib "sdapi" (ByVal sBuffer, sBufferSize) As Integer

using variant datatypes, however this is obviously incorrect.
0
pnclick
Asked:
pnclick
  • 9
  • 6
2 Solutions
 
nffvrxqgrcfqvvcCommented:
Hi pnclick,
The first thing you will need to do is figure out if they DLL is supported under VB6 this means the DLL must use the __stdcall convention. Next we have to change the declerations of this API to match the C++ definition. You can forget about the structure for right now instead we can pass a large enough buffer so that the DLL fills it with the information. According to the documentation 65536 byte buffer should always be large enough to hold the data. Later we can copy the data into the structure. Lets start by getting the buffer filled first so we know everything is working up to that point.
 

Option Explicit

Private Const BUF_SIZE As Long = 65536
Private Declare Function SD_GetPacket Lib "sdapi" (ByVal Buffer As Long, ByVal sizeofbuffer As Integer) As Long

Public Sub SDPacket()
    Dim Buffer(BUF_SIZE - 1)    As Byte
    Dim Success                 As Long
    Success = SD_GetPacket(VarPtr(Buffer(0)), BUF_SIZE)
    Debug.Print Success
End Sub

Open in new window

0
 
nffvrxqgrcfqvvcCommented:
Ahh tricky... Now that I looked at this again the second parameter expects unsigned short which is 2 bytes. There is no such support for that data type in VB6 so what might need to be done here is pass (sizeofbuffer) a negative value so that it produces the MAX value of unsigned short and allocate your buffer accordingly.
 

Private Const BUF_SIZE As Integer = -1 'This should produce unsigned short(65535) on the DLL side.
Private Declare Function SD_GetPacket Lib "sdapi" (ByVal Buffer As Long, ByVal sizeofbuffer As Integer) As Long

Public Sub SDPacket()
    Dim Buffer(65536 - 1)    As Byte
    Dim Success              As Long
    Success = SD_GetPacket(VarPtr(Buffer(0)), BUF_SIZE)
    Debug.Print Success
End Sub

Open in new window

0
 
pnclickAuthor Commented:
Thanks egl1044

Much appreciated response,

A trick I currently have is the original logon call to the DLL has an issue with using the conversion of Integer values between C++ and VB. It uses a function as follows:

int SD_Register(char *username, char *password, int winhandle, int message, unsigned
short *streamarray, unsigned char *substreamarray);

The last 2 paramaters indicate what data I am requesting back with the SD_GetPacket function above, unfortunately my declarations in relation to Unsigned Short Array (16 Bit) and Unsigned Char Array (8 Bit) is giving the DLL incorrect information, therefore nothing is coming back.

How would you declare the variables used in the DLL call.

At present, I've tried as follows:

Public Declare Function SD_Register Lib "sdapi" (ByVal sUser As String, ByVal sPassword As String, wHand As Integer, ByVal sMessage As Integer, ByRef arrStream() As Integer, ByRef arrSubStream() As Byte) As Integer

Public Sub Logon()
    Dim Success As Long
    
    Dim sUserString As String
    Dim sPasswordString As String
    
    Dim sByteStream(3) As Integer
    Dim sByteSubStream(3) As Byte

    sUserString = "XXXX"
    sPasswordString = "XXXX"
    
    sByteStream(0) = 20
    sByteStream(1) = 23
    sByteStream(2) = 24

    sByteSubStream(0) = 0
    sByteSubStream(1) = 1
    sByteSubStream(2) = 2
   
    Success = SD_Register(sUserString, sPasswordString, 0, 0, sByteStream(), sByteSubStream())
    
    Debug.Print Success
End Sub

Open in new window

0
Never miss a deadline with monday.com

The revolutionary project management tool is here!   Plan visually with a single glance and make sure your projects get done.

 
nffvrxqgrcfqvvcCommented:
Do you have the documentation that explains the purpose of the call and its parameters. Just looking at the example remember that an Int is actually 4 byte Long. The reason the call doesn't fail is because the DLL is using 4 byte data type but your decleration is using 2 byte data type for the paremeters (winhandle, message) since your passing 2 bytes the DLL has enough storage to hold the value but they should be declared using the 4 byte Long data type.

It would be best to show the documentation of each member to declare the the API correctly.
0
 
nffvrxqgrcfqvvcCommented:
This is how it might look, although having the documentation for each parameter would ensure this is correct, this should work based on the information you provided of the C++ function. I am showing two different ways you can pass the array information either one should work so you can choose one.
Private Declare Function SD_RegisterByLong Lib "sdapi" Alias "SD_Register" ( _
    ByVal username As String, _
    ByVal password As String, _
    ByVal winhandle As Long, _
    ByVal message As Long, _
    ByVal streamarray As Long, _
    ByVal substreamarray As Long) As Long
    
Private Declare Function SD_RegisterByRef Lib "sdapi" Alias "SD_Register" ( _
    ByVal username As String, _
    ByVal password As String, _
    ByVal winhandle As Long, _
    ByVal message As Long, _
    ByRef streamarray As Integer, _
    ByRef substreamarray As Byte) As Long

Private Sub Form_Load()

    Dim Success             As Long
    Dim streamArr(2)        As Integer
    Dim streamSubArr(2)     As Byte
    
    streamArr(0) = 20
    streamArr(1) = 23
    streamArr(2) = 24
    
    streamSubArr(0) = 0
    streamSubArr(1) = 1
    streamSubArr(2) = 2
    
     
    Success = SD_RegisterByLong("username", "password", 0, 0, VarPtr(streamArr(0)), VarPtr(streamSubArr(0)))
    
    Success = SD_RegisterByRef("username", "password", 0, 0, streamArr(0), streamSubArr(0))

Open in new window

0
 
pnclickAuthor Commented:
Thanks again egl1044,

The code provided fixed the first issue with the logon call. My code was giving the DLL strange values, however by using the code provided the log file is now showing a success connection, along with the correct streams required.

With the GetPacket function in the initial request, do I need to use a Byte to String conversion to see information, even though I am not currently using the structure, instead allocating the return to a buffer as suggested?

I do apologise for the continual questions, the C++ to VB6 conversion of data types has me a bit confused, but its good as I'm definately learning something new.
0
 
nffvrxqgrcfqvvcCommented:
unsigned short - The range is 0 through 65535 decimal.
short               - The range is –32768 through 32767 decimal.

I'll explain the reason why the GetPacket() function is tricky in VB6.

The Integer data type in VB6 is 16 bit value (2 byte value) in the range –32768 through 32767 because ALL data types in VB6 are signed. The reason why you have to pass a negative (-1) for size of buffer is so the DLL side will read it as 65535. Now the most important part about allocating a large buffer and then passing it the max size is that if you tried to create the structure and pass the size of the structure using VB6 Len(structname) it would actually exceed the size of the unsigned integer in VB6. The strctures size is larger than 32767 bytes and if you attempted to use it as such you would get an overflow error. This is the reason why allocating a larger buffer and passing (-1) is the only way you can call this method.

You can define this structure and after the you call GetPacket() function and it fills that buffer with information you can use RtlMoveMemory to copy the Buffer data into that structure. Then you will be able to work with the structure.
0
 
nffvrxqgrcfqvvcCommented:
Here is an example of how you take the filled buffer and copy into the structure.
Option Explicit

Private Declare Sub RtlMoveMemory Lib "Kernel32" (lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)

Private Const DDS_MAX_CHUNK As Long = 32766
Private Const UNIQUEIFIER_LEN As Long = 10

Private Type data_packet
stream As Integer
bKey(UNIQUEIFIER_LEN - 1) As Byte
elen As Integer
bData(DDS_MAX_CHUNK - 1) As Byte
End Type

Dim dp      As data_packet

Private Sub Form_Load()
    Dim Buffer(65536 - 1)    As Byte
    Dim Success              As Long
    Success = SD_GetPacket(VarPtr(Buffer(0)), BUF_SIZE)
    If Success Then
        RtlMoveMemory dp, Buffer(0), LenB(dp)
        Debug.Print dp.stream
        Debug.Print dp.bKey
        Debug.Print dp.elen
        Debug.Print dp.bData
    End If
End Sub

Open in new window

0
 
pnclickAuthor Commented:
Thanks yet again egl1044,

Almost cooking with gas now, I'm receiving data consistantly and the information is being broken down in the the UDT. Only one remaining query which may be from the source of the DLL, who have indicated the information is NOT encrypted.

With the dp.Key and dp.bData variables, both of which are Byte arrays, when I display the information into a text box, I receive information such as thisn &6D, this is after using a convertion such as

txtInformation(0).Text = ByteArrayToString(dp.bData)

Without the ByteArrayToString function, the information displayed is ??? marks.

Any ideas on this one?
0
 
nffvrxqgrcfqvvcCommented:
I don't know the documentation for the information but unsigned char typically is a single byte value ranging from 0-255. You would expect this output if the data isn't represented as a string of printable characters but is outputed as byte values in that range. An example to give you a better idea is working with a binary file. The binary file is made up of a bunch of bytes that ultimatley forms the entire file that make sense to the loader or application opening/reading that file.  Take the same concept and and assume a binary file is being transmitted over the wire and the GetPacket() may pick up the packet as a sequence of bytes but doesn't actually have to be represented as printable string.

Unless you know for sure all this data is suppose to be valid readable characters then the output looks fine on the surface. Do you have more detail?
0
 
pnclickAuthor Commented:
Hi eql1044, sorry for the delay in getting back, been trying alot of testing to work out the issue myself, so I learn abit more from the experience.

With the dp.bdata byte array returning items such as n &6D, I realised in the documentation that the dp.stream variable outlines the format of the information contained in the dp.bdata field.

For example, if the Stream returns 28 then dp.bdata byte array contains the following information:

FIELD NAME     OFFSET     LENGTH     TYPE
Location            0               1                  Unsigned Char
RType                1               1                 Unsigned Char
CloseTime          2               6                Unsigned Byte ymdhms
RNumber            8              1                 Unsigned Byte
RCount               9              1                 Unsigned Byte
NPools               10             1                 Unsigned Byte
Status                11             1                 Unsigned Char
PType                 12             1                Unsigned Char
RDiv                                     2                Unsigned Int (For each member)
PId                                        1                Unsigned Byte
PValue                                 4                 Unsigned Long

And this exists for around 15 stream types.

So do I need to create a User Defined Type for each stream and then assign the dp.bdata byte array to the UDT?
0
 
nffvrxqgrcfqvvcCommented:
Yup exactly. Create all the structures given by the documentation and then based on the stream value use RtlMoveMemory to copy that specific data into the correct structure based on the value.
0
 
pnclickAuthor Commented:
I'll test this using the simplest (or shortest number of elements in the type declaration), and also the most frequently received.

One of the streams, (Stream 20) has 2 elements in the declaration:

Field Name     OffSet     Length     Type
Status Flag        0              2          Bit Flags
PacketSeq         2              2          Unsigned Int

Status Flag bits are as follows:

Bits      Status Flag             Note
0          System Status        1 = ok, 0 = problem
1           N_Online                1 = ok, 0 = No Recent Data
2           A_Online                1 = ok, 0 = No Recent Data
3           T_Online                 1 = ok, 0 = No Recent Data
4           U_Online                1 = ok, 0 = No Recent Data
5           NZ_Online              1 = ok, 0 = No Recent Data
6..13     Reserved
14-15    SysID                      ID of the system tranmitting the packet.

The code I have at present is as follows, however the code isn't quite returning what I need.

 
Option Explicit

Private Const BUF_SIZE As Integer = -1 'This should produce unsigned short(65535) on the DLL side.

Private Declare Function SD_GetPacket Lib "sdapi" (ByVal Buffer As Long, ByVal sizeofbuffer As Integer) As Long

Private Declare Sub RtlMoveMemory Lib "Kernel32" (lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
Private Declare Function SD_RegisterByLong Lib "sdapi" Alias "SD_Register" ( _
    ByVal username As String, _
    ByVal password As String, _
    ByVal winhandle As Long, _
    ByVal message As Long, _
    ByVal streamarray As Long, _
    ByVal substreamarray As Long) As Long
    
Private Const DDS_MAX_CHUNK As Long = 32766
Private Const UNIQUEIFIER_LEN As Long = 10
    
Private Type DataPacket
    stream As Integer
    bKey(UNIQUEIFIER_LEN - 1) As Byte
    elen As Integer
    bData(DDS_MAX_CHUNK - 1) As Byte
End Type

Private Type HeartBeat
    SysStatus As Long
    PacketSequence As Integer
End Type

Dim dp As DataPacket
Dim sysHeartBeat As HeartBeat

Private Sub cmdTestGet_Click()
    Dim Buffer(65536 - 1) As Byte
    Dim Success As Long
    Dim I As Long
    Dim sDescription As String
    Dim sString As String
    
    Dim x As Long
        
    Success = SD_GetPacket(VarPtr(Buffer(0)), BUF_SIZE)
    
    Do While Success <> 0
        x = UBound(Buffer)
        
        RtlMoveMemory dp, Buffer(0), LenB(dp)
        
        Debug.Print dp.stream
        Debug.Print dp.bKey
        Debug.Print dp.elen
        Debug.Print dp.bData
        
        Select Case dp.stream
            Case Is = 20 ' System Heart Beat
                sDescription = "System Heart Beat"
                
                RtlMoveMemory sysHeartBeat, dp.bData(0), dp.elen
                
                Debug.Print sysHeartBeat.PacketSequence
                Debug.Print sysHeartBeat.SysStatus
        End Select
        
        Success = SD_GetPacket(VarPtr(Buffer(0)), BUF_SIZE)
    Loop
End Sub

Open in new window

0
 
nffvrxqgrcfqvvcCommented:
Looking at the offset and length the structure is using 2 byte offsets so it appears you may need to be using Integer.
Private Type HeartBeat
    StatusFlag As Integer ' <-- 2 bytes instead of four
    PacketSequence As Integer 
End Type

@ Line 59:
RtlMoveMemory sysHeartBeat, dp.bData(0), Len(sysHeartBeat)

Open in new window

0
 
pnclickAuthor Commented:
Excellent quick responses to question.
0

Featured Post

The new generation of project management tools

With monday.com’s project management tool, you can see what everyone on your team is working in a single glance. Its intuitive dashboards are customizable, so you can create systems that work for you.

  • 9
  • 6
Tackle projects and never again get stuck behind a technical roadblock.
Join Now