Solved

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

Posted on 2010-11-10
15
866 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

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

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Have you ever wanted to restrict the users input in a textbox to numbers, and while doing that make sure that they can't 'cheat' by pasting in non-numeric text? Of course you can do that with code you write yourself but it's tedious and error-prone …
Article by: Martin
Here are a few simple, working, games that you can use as-is or as the basis for your own games. Tic-Tac-Toe This is one of the simplest of all games.   The game allows for a choice of who goes first and keeps track of the number of wins for…
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
This lesson covers basic error handling code in Microsoft Excel using VBA. This is the first lesson in a 3-part series that uses code to loop through an Excel spreadsheet in VBA and then fix errors, taking advantage of error handling code. This l…

738 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