PlanetSquires Forums

Support Forums => Other Software and Code => Topic started by: Jon Hanson on March 01, 2007, 09:23:16 AM

Title: Some More COM Questions , variants again
Post by: Jon Hanson on March 01, 2007, 09:23:16 AM
I am trying as an exercise to get all the Com/ActiveX socket components I have used over the years working with FireFly. I am now on to SockSvrQ

in VB6 this is how you handle the data arrival event


Private Sub mobjSocksvrQ_DataArrival(ByVal SocketHandle As Long, Data As Variant)

'-----------------------------------
' receive data as a byte array
'-----------------------------------
Dim bytData() As Byte
       ReDim bytData(UBound(Data))
       bytData() = Data
 
'-----------------------------------
' OR
'
' receive data as a string
'-----------------------------------
dim sBuff as string  
       sBuff = StrConv(Data, vbFromUnicode)

End Sub


here is the FireFly genned code



' ********************************************************************************************
' Function name: DataArrival
' ********************************************************************************************
FUNCTION SocksvrQ_ISocksvrEvents1_DataArrival(BYVAL dwCookie AS DWORD, BYREF pdispparams AS DISPPARAMS) AS DWORD
  LOCAL pv AS VARIANT PTR, pvapi AS VARIANTAPITYPE PTR
  pv = pdispparams.VariantArgs : pvapi = pv
'  ==============================================================================
'  Parameters in DISPPARAMS are zero based and in reverse order
'  ==============================================================================
  LOCAL SocketHandle AS LONG
  SocketHandle = VARIANT#(@pv[1])
  Static prmData As Variant
  prmData = @pv[0]
'  ==============================================================================
'  *** Put your code here ***
'  ==============================================================================  

 Call OutputDebugString("data arrival event fired")
 Call OutputDebugString("Rx by FireFly: " & Str$(VariantVT(prmData)) )

  FUNCTION = 0  ' %S_OK
END FUNCTION


I've been hacking away for over a day now, trying to access the data in prmData (a variant).

Looking at the ocx with Jose Roca's TypeLib Browser it shows that the level of indirection for prmData is 1

and genns the following (excerpt only)

'  =======================================================================================
'  *** Data - VarType: VT_PTR - Resolved VarType: *%VT_VARIANT <VARIANT> - Attributes: &H1 -  [in] ***
  STATIC prmData AS VARIANT : prmData = @pv[0]
'  To return a value, use: prmData = <value> : @pvapi[0].vd.@pVariant = VARPTR(prmData)
'  =======================================================================================


indicating that the parameter is a pointer to a variant (well that's how I interpret it)

the VariantVT function returns 16396 ie VT_BYREF OR VT_VARIANT

Try as I might I cannot access the data (which in this case is a simple string from an echo server). Any suggestions?

regards
Title: Some More COM Questions , variants again
Post by: Jose Roca on March 01, 2007, 05:05:52 PM
Try the following:


DIM vByteArray(0) AS BYTE
DIM buffer AS STRING
DIM nBytes AS LONG

' Convert the variant in an array of bytes
vByteArray() = prmData
' Convert the array of bytes to string
IF UBOUND(vByteArray) > -1 THEN
  nBytes = UBOUND(vByteArray) - LBOUND(vByteArray)
  IF nBytes > 0 THEN
     buffer = PEEK$(VARPTR(vByteArray(0)), nBytes)
     MSGBOX buffer
  END IF
END IF
Title: Some More COM Questions , variants again
Post by: Jon Hanson on March 02, 2007, 03:31:01 AM
Thanks for the reply, I tried the code and didn't see the message box so I added the following



 ' Convert the variant in an array of bytes
 vByteArray() = prmData
 ' Convert the array of bytes to string
 
 Call OutputDebugString("lbound: " & Str$(LBound(vByteArray)) )
 Call OutputDebugString("ubound: " & Str$(UBound(vByteArray)) )


which resulted in the following output


[4496] lbound:  0
[4496] ubound: -1


the compiler is quite happy with

vByteArray() = prmData


but it doesn't seem to do what one would expect

regards
Title: Some More COM Questions , variants again
Post by: Jon Hanson on March 02, 2007, 03:40:47 AM
after looking at the code I tried preallocating 64 bytes to the byte array as a test and added the following code

 'Dim vByteArray(0) As Byte
 Dim vByteArray(0 To 64) As Byte
 Dim buffer As String
 Dim nBytes As Long
 
 Call OutputDebugString("before lbound: " & Str$(LBound(vByteArray)) )
 Call OutputDebugString("before ubound: " & Str$(UBound(vByteArray)) )

 ' Convert the variant in an array of bytes
 vByteArray() = prmData
 ' Convert the array of bytes to string
 
 Call OutputDebugString("after lbound: " & Str$(LBound(vByteArray)) )
 Call OutputDebugString("after ubound: " & Str$(UBound(vByteArray)) )


which gave

[1580] before lbound:  0
[1580] before ubound:  64
[1580] after lbound:  0
[1580] after ubound: -1


so the assignment seems to result in an empty byte array
Title: Some More COM Questions , variants again
Post by: Jose Roca on March 02, 2007, 03:55:51 AM
Must be because is a VT_BYREF array. Probably we will have to use the SafeArray functions with the pointer returned by @pvapi[0].vd.@pVariant, but I don't have SockSvrQ installed to do tests.
Title: Some More COM Questions , variants again
Post by: Jon Hanson on March 02, 2007, 10:56:01 AM
Jose

after your last post I did some digging, just for everyone's information
here are the definitions of the DispParams type and the VARIANTAPITYPE type


TYPE DispParams
 VariantArgs AS VARIANT PTR
 NamedDispId AS LONG    PTR
 CountArgs   AS DWORD
 CountNamed  AS DWORD
END TYPE

' =============================================================================================
' VARIANTDATA UNION
' =============================================================================================
 UNION VARIANTDATAUNION
   bVal AS BYTE            ' VT_UI1
   iVal AS INTEGER         ' VT_I2
   lVal AS LONG            ' VT_I4
   fltVal AS SINGLE        ' VT_R4
   dblVal AS DOUBLE        ' VT_R8
   boolVal AS INTEGER      ' VT_BOOL
   scode AS LONG           ' VT_ERROR
   cyVal AS LONG           ' VT_CY
   date AS DOUBLE          ' VT_DATE
   bstrVal AS LONG         ' VT_BSTR
   punkVal AS DWORD        ' VT_UNKNOWN
   pdispVal AS DWORD       ' VT_DISPATCH
   parray AS DWORD         ' VT_ARRAY|*
   pbVal AS BYTE PTR       ' VT_BYREF|VT_UI1
   piVal AS INTEGER PTR    ' VT_BYREF|VT_I2
   plVal AS LONG PTR       ' VT_BYREF|VT_I4
   pfltVal AS SINGLE PTR   ' VT_BYREF|VT_R4
   pdblVal AS DOUBLE PTR   ' VT_BYREF|VT_R8
   pboolVal AS INTEGER PTR ' VT_BYREF|VT_BOOL
   pscode AS LONG PTR      ' VT_BYREF|VT_ERROR
   pcyVal AS LONG PTR      ' VT_BYREF|VT_CY
   pdate AS DOUBLE PTR     ' VT_BYREF|VT_DATE
   pbstrVal AS LONG PTR    ' VT_BYREF|VT_BSTR
   ppunkVal AS DWORD PTR   ' VT_BYREF|VT_UNKNOWN
   ppdispVal AS DWORD PTR  ' VT_BYREF|VT_DISPATCH
   psArray AS DWORD PTR    ' VT_ARRAY|*
   pVariant AS DWORD PTR   ' VT_BYREF|VT_VARIANT
   pByRef AS DWORD         ' Generic ByRef
 END UNION
' =============================================================================================

' =============================================================================================
' VARIANTAPI structure
' =============================================================================================
 TYPE VARIANTAPITYPE
   vt         AS WORD  'VARTYPE
   wReserved1 AS WORD
   wReserved2 AS WORD
   wReserved3 AS WORD
   vd         AS VARIANTDATAUNION
 END TYPE
' =============================================================================================


I looked on Jose's site at http://com.it-berater.org/other_wrappers.htm

and used some code from the TB_WHTTP v.1.0 (Microsoft Windows HTTP Services) wrapper example

I was assuming that the data in prmData was a safearray underneath

Here is the code from the FireFly event handler im my test code



' ********************************************************************************************
' Function name: DataArrival
' ********************************************************************************************
FUNCTION SocksvrQ_ISocksvrEvents1_DataArrival(BYVAL dwCookie AS DWORD, BYREF pdispparams AS DISPPARAMS) AS DWORD
  LOCAL pv AS VARIANT PTR, pvapi AS VARIANTAPITYPE PTR
  pv = pdispparams.VariantArgs : pvapi = pv
'  ==============================================================================
'  Parameters in DISPPARAMS are zero based and in reverse order
'  ==============================================================================
  LOCAL SocketHandle AS LONG
  SocketHandle = VARIANT#(@pv[1])
  Static prmData As Variant
  prmData = @pv[0]
'  ==============================================================================
'  *** Put your code here ***
'  ==============================================================================  

  LOCAL hr AS LONG
  LOCAL nLBound AS LONG
  LOCAL nUBound AS LONG
  LOCAL nBytes AS LONG
  LOCAL pvData AS DWORD
  LOCAL buffer AS STRING
  Local nDims As Long  
 
  Call OutputDebugString("var type: " & Str$(@pvapi.vt))

  Call OutputDebugString("@pointer is: " & Str$(@pvapi[0].vd.@pVariant) )
  Call OutputDebugString("pointer is: " & Str$(@pvapi[0].vd.pVariant) )
 
  If @pvapi[0].vd.pVariant Then
     ' Number of dimensions of the array (must be 1)

     Call OutputDebugString("before get dim")
     nDims = SafeArrayGetDim(@pvapi[0].vd.pVariant)
     Call OutputDebugString("after get dim: " & Str$(nDims))
     
  End If

  FUNCTION = 0  ' %S_OK
END FUNCTION
' ********************************************************************************************



This is the output from the code when receiving a return msg from an echo server


[3932] var type:  16396
[3932] @pointer is:  8209
[3932] pointer is:  10552040
[3932] before get dim
[3932] after get dim:  8209


the var type is 16396 ie VT_BYREF | VT_VARIANT
the Dimension of what I think is an array comes back as 8209.
In Jose's wrapper code it should be 1, so I think I am dealing with something else.

If I change the line

     nDims = SafeArrayGetDim(@pvapi[0].vd.pVariant)

to

     nDims = SafeArrayGetDim(@pvapi[0].vd.@pVariant)

I get a GPF

when I apply the rest of the wrapper code to extract a string , I get  GPF's.

I fully realise my knowledge of and ability to handle pointers is shaky, so I could be close to success or I could be far away, but I had fun doing the digging.
Title: Some More COM Questions , variants again
Post by: Jose Roca on March 03, 2007, 10:11:33 PM
This should work. Similar code works in the test that I have done with the PostData parameter of the BeforeNavigate2 event of the WebBrowser control.


  LOCAL hr AS LONG
  LOCAL pvar AS VARIANTAPI PTR
  LOCAL pvar2 AS VARIANTAPI PTR
  LOCAL pvData AS DWORD
  LOCAL plLBound AS LONG
  LOCAL plUBound AS LONG
  LOCAL nLen AS LONG
  LOCAL strData AS STRING
 
  pvar = @pvapi[0].vd.pVariant
  pvar2 = @pvar.vd.psarray
  hr = SafeArrayAccessData(@pvar2.vd.parray, pvData)
  IF hr = %S_OK THEN
     SafeArrayGetLBound @pvar2.vd.parray, 1, plLBound
     SafeArrayGetUBound @pvar2.vd.parray, 1, plUBound
     nLen = plUBound - plLBound + 1
     strData = SPACE$(nLen)
     MoveMemory BYVAL STRPTR(strData), BYVAL pvData, nLen
     SafeArrayUnaccessData @pvar2.vd.parray
     OutputDebugString BYCOPY strData
  END IF


P.S. This is true if HEX$(@pvar.vd.@psarray) returns 4011 (VT_BYREF OR VT_UI1). If it is another kind of data, e.g. an array of variants, we will have to use the SafeArrayGetElement function.

All this crazyness was perpetrated by the designers of VB, that were also put in charge of designing the M$ implementation of COM. Because VB always uses Unicode, that can't be used to pass binary data, and also don't have pointers to deal with a buffer, they decided to solve the problem using safearrays and implementing functions in the runtime to deal with conversions. But if the compiler has no native support for safearrays, it is a nightmare.
Title: Some More COM Questions , variants again
Post by: Jon Hanson on March 05, 2007, 06:33:05 AM
Jose

the new code doesn't work for SockSvrQ.

HEX$(@pvar.vd.@psarray) returned a very large hex no (32 bit aaddress maybe?)

the following code

  Call OutputDebugString("is it 4011: " & Hex$(@pvapi[0].vd.@psarray)) ' this 0x2011 ie VT_ARRAY | VT_UT1 (byte)

returns 0x2011 which is VT_ARRAY | VT_UT1

I found a post of yours on PB forums ie http://www.powerbasic.com/support/forums/Forum6/HTML/003523.html

where you use the SafeArrayGetElement function

looking at that, I tested the folowing code as a start


  Call OutputDebugString("Dim of array: " & Hex$(SafeArrayGetDim(@pvapi[0].vd.parray)))


which returns 0x2011 which is a data type description not the num of dimensions. I'm just going around in circles here, so I decided it was time to ask for some more guidance.
Title: Some More COM Questions , variants again
Post by: Jose Roca on March 05, 2007, 06:46:54 AM
If it is 2011 then it is not a VT_BYREF variant and needs a level less of indirection. Try the following:


  pvar = @pvapi[0].vd.pVariant
  hr = SafeArrayAccessData(@pvar.vd.parray, pvData)
  IF hr = %S_OK THEN
     SafeArrayGetLBound @pvar.vd.parray, 1, plLBound
     SafeArrayGetUBound @pvar.vd.parray, 1, plUBound
     nLen = plUBound - plLBound + 1
     strData = SPACE$(nLen)
     MoveMemory BYVAL STRPTR(strData), BYVAL pvData, nLen
     SafeArrayUnaccessData @pvar.vd.parray
     OutputDebugString BYCOPY strData
  END IF
Title: Some More COM Questions , variants again
Post by: Jon Hanson on March 05, 2007, 07:00:41 AM
The Latest code works perfectly.
Thanks for coming to my rescue once again
Title: Some More COM Questions , variants again
Post by: Jose Roca on March 05, 2007, 07:06:20 AM
Notice that SafeArrayAccessData returns a pointer to the data. This allows a fast copy with MoveMemory. Being a byte array, if we used SafeArrayGetElement we will be retrieving the data byte by byte. SafeArrayGetElement is better used if it is an array of variants.
Title: Re: Some More COM Questions , variants again
Post by: Jose Roca on March 21, 2007, 12:44:00 AM
Instead of:


hr = SafeArrayAccessData(@pvar.vd.parray, pvData)
IF hr = %S_OK THEN
   SafeArrayGetLBound @pvar.vd.parray, 1, plLBound
   SafeArrayGetUBound @pvar.vd.parray, 1, plUBound
   nLen = plUBound - plLBound + 1
   strData = SPACE$(nLen)
   MoveMemory BYVAL STRPTR(strData), BYVAL pvData, nLen
   SafeArrayUnaccessData @pvar.vd.parray
   OutputDebugString BYCOPY strData
END IF


We can call the API function BstrFromVector:


pvar = @pvapi[0].vd.pVariant
BstrFromVector @pvar.vd.parray, strData


This is my translation of the BstrFromVector function:


DECLARE FUNCTION BstrFromVector LIB "OLEAUT32.DLL" ALIAS "BstrFromVector" ( _
   BYVAL psa AS DWORD _                          ' [in] SAFEARRAY * psa
, BYREF pbstr AS STRING _                       ' [out] BSTR * pbstr
   ) AS LONG                                     ' HRESULT

Title: Re: Some More COM Questions , variants again
Post by: TechSupport on March 21, 2007, 09:16:39 AM
What in the world would we do without Jose? That man is 'the man' when it comes to working with COM in PowerBASIC. Thanks Jose!
:)

Title: Re: Some More COM Questions , variants again
Post by: Jon Hanson on March 23, 2007, 07:28:40 AM
Thanks Jose

I finally had time to try your new code (work has been a bit hectic). Works perfectly.
We have been deploying stuff remotely to a gsm operator in Cameroon, so now at least I can spell your name correctly from my keyboard (é is Alt + 0233 on the numeric keyboard)

Regards