Solved

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

Posted on 2010-11-10
15
837 Views
Last Modified: 2012-08-13
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
Comment
Question by:pnclick
  • 9
  • 6
15 Comments
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34101116
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
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34101380
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
 

Author Comment

by:pnclick
ID: 34120777
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
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34120929
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
 
LVL 29

Accepted Solution

by:
nffvrxqgrcfqvvc earned 500 total points
ID: 34121722
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
 

Author Comment

by:pnclick
ID: 34126246
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
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34126545
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
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34127362
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
 

Author Comment

by:pnclick
ID: 34135281
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
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34135646
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
 

Author Comment

by:pnclick
ID: 34235203
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
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34257313
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
 

Author Comment

by:pnclick
ID: 34278112
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
 
LVL 29

Assisted Solution

by:nffvrxqgrcfqvvc
nffvrxqgrcfqvvc earned 500 total points
ID: 34281979
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
 

Author Closing Comment

by:pnclick
ID: 34289425
Excellent quick responses to question.
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

I was working on a PowerPoint add-in the other day and a client asked me "can you implement a feature which processes a chart when it's pasted into a slide from another deck?". It got me wondering how to hook into built-in ribbon events in Office.
This article describes some techniques which will make your VBA or Visual Basic Classic code easier to understand and maintain, whether by you, your replacement, or another Experts-Exchange expert.
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…
Get people started with the utilization of class modules. Class modules can be a powerful tool in Microsoft Access. They allow you to create self-contained objects that encapsulate functionality. They can easily hide the complexity of a process from…

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

14 Experts available now in Live!

Get 1:1 Help Now