views:

342

answers:

2

In VB6 I'm making a call to the Windows API DnsQuery.

Private Declare Function DnsQuery Lib "dnsapi" Alias "DnsQuery_A" ( _
   ByVal lpstrName As String, _
   ByVal wType As Integer, _
   ByVal Options As Long, _
   ByVal pServers As Long, _
   ppQueryResultsSet As Long, _
   ByVal pReserved As Long) As Long

Private Type VBDnsRecord
   pNext           As Long
   pName           As Long
   'Name            As String ' Commented out, see question
   wType           As Integer
   wDataLength     As Integer
   Flags           As Long
   dwTtl           As Long
   dwReserved      As Long
   ptrData         As Long
   Others(35)      As Byte
End Type

My declaration of the struct came from here. I presume Others(35) is to provide a big enough container when the actual struct that is returned is larger than expected (there are some variable-length types UNIONed in there). See the DNS_RECORD structure for more details.

So, I have 2 questions:

  1. Strings in VB are really, behind the scenes, double word pointers (4 bytes, aka a Long). For a bit I thought I could declare Name as a String since this would just place the pointer in there and work correctly (as when passing VB Strings into API calls). However, I'm guessing the application terminations I got are because it's a C-style string and not a VB-style string, and VB looks in the memory location just before the start of the string for a length value, and gets some random garbage and blows up. Is that a reasonable guess?

  2. My call to DnsQuery works when I use am returning a DNS RR type of DNS_PTR_DATA or DNS_A_DATA, but when I try DNS_TXT_DATA it's blowing up. Can someone else spot what I'm doing wrong? Look at Case DNS_TYPE_TEXT, and see my comments below.

    RetVal = DnsQuery(DnsName,
    QueryType, DNS_QUERY_BYPASS_CACHE,
    pServers, pDnsRecord, 0)
        If RetVal = 0 Then
           pNext = pDnsRecord
           Do While pNext <> 0
              CopyMemory DnsRecord, pNext, Len(DnsRecord)
              Select Case DnsRecord.wType
                 Case DNS_TYPE_A
                    Ptr = inet_ntoa(DnsRecord.ptrData)
                    TempString = String(lstrlen(Ptr), 0)
                    CopyMemory ByVal TempString, Ptr, Len(TempString)
                 Case DNS_TYPE_PTR, DNS_TYPE_NS, DNS_TYPE_CNAME,
    DNS_TYPE_DNAME, DNS_TYPE_MB,
    DNS_TYPE_MD, DNS_TYPE_MF,
    DNS_TYPE_MG, DNS_TYPE_MR
                    Ptr = DnsRecord.ptrData
                    TempString = String$(lstrlen(Ptr), 0)
                    CopyMemory ByVal TempString, Ptr, Len(TempString)
                 Case DNS_TYPE_TEXT, DNS_TYPE_HINFO, DNS_TYPE_ISDN,
    DNS_TYPE_TEXT, DNS_TYPE_X25
                    Dim TextData As Dns_Txt_Data
                    Ptr = DnsRecord.ptrData
                    CopyMemory VarPtr(TextData), Ptr, Len(TextData)
                    Stop
                 Case Else
                    TempString = "unhandled resource record type"
             End Select
             If Not FullRecord Then
                DnsLookup = "   " & TempString
                Exit Do
             Else
                DnsLookup = DnsLookup & " " & vbCrLf &
    DnsTypeNameFromCode(DnsRecord.wType)
    & " " & TempString
             End If
             pNext = DnsRecord.pNext
          Loop
    

    Now, when I put a breakpoint on the CopyMemory line and inspect the value of Ptr, I expect something in the millions or higher, indicating that it is a pointer, only I'm getting the value 1 (which explains why everything blows up when I try to copy from that memory location). This seems to indicate to me that instead of a pointer to the expected DNS_TXT_DATA struct, I'm getting the count of strings. When I examine Other(0) through Other(3), they all have values which make me suspect the next four bytes are a pointer. So what gives? Why is this struct just coming in "inline" but the others come in as pointers to the start of the struct?

I appreciate any help!

A: 

1) C strings and VB strings different and you can't just substitute one for another. You'll need to explicitly convert the C string to a VB string. In other words, yes, your guess is reasonable.

2) I took a quick look at the Microsoft docs, which say that DNS_TXT_DATA is a count followed by a pointer to a string (which is what you're seeing). DNS_PTR_DATA, on the other hand, is a point to the resulting record. So what you're describing seems to match the documentation.

jdigital
1) Aren't VB strings also null-terminated? So VB strings work ok as C strings, but not the other way around, right? and 2) Now I finally see it! I was thrown off by the call to inet_ntoa, which returns a (pointer to a) string. But it really is simply a value. Somehow I was getting that it was a pointer. I know exactly what to do now (but won't get back to it until Tuesday).
Emtucifor
I don't believe that VB strings are nul-terminated; also, they use wide chars. You should use conversion routines in both directions.
jdigital
If they use wide characters and the API functions don't, then CopyMemory wouldn't work correctly, but it does work. It seems you're answering more on theory than actual practice?
Emtucifor
Also, DNS_PTR_DATA does have a pointer inside of it, but DNS_A_DATA does not... so in fact, saying "DNS_PTR_DATA is a pointer to the resulting record" isn't really true. Anyway, between you and wqw, I figured out my mistake. Now I don't know who to award the answer to.
Emtucifor
Here's an article on converting between VB strings and C string: http://www.codeproject.com/KB/string/bstrsproject1.aspx. It explains that VB Strings are 16 bit unicode.
jdigital
However it appears that VB will sometimes perform automatic translation between ansi and unicode when making DLL calls.
jdigital
I'm marking yours as the answer since you were factually correct, though I would have liked to see some code like wqw offered. Also, I'll say the same as I did to wqw: it would have been nice for you to explicitly say "the UNIONed elements are directly embedded in the parent struct, there's no pointer involved at all here."
Emtucifor
+2  A: 

If you change Others in VBDnsRecord to pStringArray(0 To 8) As Long it will be easier to access the array ot string pointers like this

        ...
        ElseIf uRecord.wType = DNS_TYPE_TEXT Then
            For lIdx = 0 To uRecord.prt - 1
                sName = String(lstrlen(uRecord.pStringArray(lIdx)), 0)
                Call CopyMemory(ByVal sName, uRecord.pStringArray(lIdx), Len(sName))
                If LenB(Resolve) <> 0 Then
                    Resolve = Resolve & vbCrLf
                End If
                Resolve = Resolve & sName
            Next
        End If

Nine Longs are exactly 36 bytes so Len(VBDnsRecords) remains 64 bytes, exactly the size of the DNS_RECORD union in Platform SDK.

wqw
I went away and played with things on my own and eventually came up with... basically exactly what you did here (though I have to boo at the Hungarian notation on your data types). Between you and jdigital, I figured out my misconception. It would have been nice for one of you to explicitly say "the UNIONed elements are directly embedded in the parent struct, there's no pointer involved at all here."
Emtucifor
P.S., doesn't the struct change size depending on what's in it?
Emtucifor
No, the struct (union) is exactly 64 bytes. You are rightfully disgusted by the hungarian on the variables (data types?) but spending more time with Win32 API will get you out of boo...
wqw