To expand on the above, take a look at the following code:
Code:
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long)

Private Sub Example()

    Dim uOne As Test, uTwo As Test
    
    uOne.TestInt = 123
    uOne.TestSin = 123.456
    uOne.TestStr = "Hello"
    
    uTwo.TestInt = 123
    uTwo.TestSin = 123.456
    uTwo.TestStr = "Hello"

    Debug.Print AreEqual(uOne, uTwo)

End Sub

Private Function AreEqual(uOne As Test, uTwo As Test) As Boolean

    Dim sOne As String, sTwo As String
    
    sOne = String$(Len(uOne), Chr$(0))
    sTwo = String$(Len(uTwo), Chr$(0))
    Call CopyMemory(ByVal StrPtr(sOne), ByVal VarPtr(uOne), LenB(uOne))
    Call CopyMemory(ByVal StrPtr(sTwo), ByVal VarPtr(uTwo), LenB(uTwo))
    
    If sOne = sTwo Then AreEqual = True

End Function
If the type declaration is like this...
Code:
Private Type Test
    TestInt As Integer
    TestSin As Single
    TestStr As String
End Type
...the code fails, because the last parameter in the type is a pointer to a string. With a fixed length string...
Code:
Private Type Test
    TestInt As Integer
    TestSin As Single
    TestStr As String * 5
End Type
...it works fine. Note that this will also work with arrays in the Type, but only if they are fixed size. You could probably modify the function to take arbitrary UDTs (as long as they fit the "no pointers" criteria) by passing Variants to it and dereferencing the pointer in the Variant structure.