Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all 1487 articles
Browse latest View live

Markov Chains The weather

$
0
0
This application uses a 2X2 transition matrix to make predictions by using a Markov chain. For exemplification, the values from the transition matrix represent the transition probabilities between two states found in a sequence of observations (ex. s=RSRRSRRSRRRRSRRRSSSRRSRRRS). These two states are: Sunny and Rainy, or R and S. Based on the initial probability vector, the application calculates how the weather may be on a number of days (steps). Note that a transition matrix can be obtained from a series of observations by using the DPD algorithm. Note that Markov Chains - The weather is desidned in Visual Basic 6.0 (VB6), thus, the VB6 IDE is needed.


Download: Markov Chains The weather


Name:  Markov Chains - The weather.jpg
Views: 43
Size:  38.6 KB
Attached Images
 

Simple sequence alignment in VB6

$
0
0
This highly responsive VB6 application is an implementation of the global sequence alignment algorithm. It allows the modification of the alignment parameters (match, mismatch, gap), and it shows the pairwise alignment as well as the score matrix in real time. The purpose of it is to seeks the optimal alignment between two text sequences. Why is this optimal alignment so important? To find similarities between two words, a manual comparison is required. For words between 4 and 8 letters, such a task can be performed by any of us without the use of a computer. However, what if the objects of comparison are entire phrases/sequences/files? Then the number of possibilities for different matches increases exponentially and nobody can do it by paper computing (to my knowledge). Computational solutions for these problems exist, such as different implementations of sequence alignment algorithms. Here, the implementation of global sequence alignment is shown in detail. Note that the implementation si designed in Visual Basic 6.0.


Download: Simple sequence alignment in VB6


Name:  TextAlignDNA (2).jpg
Views: 32
Size:  57.8 KB

Name:  TextAlignDNA (1).jpg
Views: 31
Size:  56.7 KB
Attached Images
  

Visual Sequence Alignment in VB6

$
0
0
This highly visual and responsive VB6 application is an implementation of the global sequence alignment algorithm. It allows the modification of the alignment parameters (match, mismatch, gap), and it shows the pairwise alignment as well as the score matrix in real time. Moreover, it shows the score matrix values as a heatmap and the traceback path of the current alignment. Many predefined experiments are available to the user for certain observations related to the global sequence alignment algorithm. Also, changes to either of the two sequences lead to a real-time pairwise alignment. The purpose of the algorithm is to seeks the optimal alignment between two text sequences. Why is this optimal alignment so important? To find similarities between two words, a manual comparison is required. For words between 4 and 8 letters, such a task can be performed by any of us without the use of a computer. However, what if the objects of comparison are entire phrases/sequences/files? Then the number of possibilities for different matches increases exponentially and nobody can do it by paper computing (to my knowledge). Computational solutions for these problems exist, such as different implementations of sequence alignment algorithms. Here, the implementation of global sequence alignment is shown in detail. Note that the implementation si designed in Visual Basic 6.0. Note: This VB6 application has a child in javascript that can be opened directly in the browser here.


Download: Visual Sequence Alignment in VB6


Name:  AlignDNA in VB6 (2).jpg
Views: 47
Size:  43.5 KB

Name:  AlignDNA in VB6 (1).jpg
Views: 43
Size:  45.5 KB

Name:  AlignDNA in VB6 (3).jpg
Views: 43
Size:  39.6 KB
Attached Images
   

Markov Chains step-by-step algorithms in VBA

$
0
0
This repository includes the ".bas" implementations for Markov Chains that accompany the book entitled: Markov Chains: From Theory to Implementation and Experimentation. These ".bas" files can be used in various VBA Excel applications. This repository also includes an EXCEL file that supports VBA. This file is called "MarkovChainsApp.xlsm" and incorporates all VB ".bas" files with some experimental approaches and textual explanations in regard to these algorithms.

Download: Markov Chains step-by-step algorithms in VBA


Name:  MC.jpg
Views: 36
Size:  32.1 KB
Attached Images
 

[VB6] Undocumented API: SHLimitInputEditWithFlags - Easy input filtering

$
0
0
Name:  inputlim.jpg
Views: 155
Size:  81.0 KB
UNDOCUMENTED API: SHLimitInputEditWithFlags
Easily apply category filters, paste handling, and automated tooltips to an edit control.

Microsoft being Microsoft, they only begrudgingly documented a function called SHLimitInputEdit for the DOJ settlement, and did so poorly. This is a weird function; it takes an edit hwnd, and an object that implements IShellFolder and IItemNameLimits. The former doesn't even matter (unless it's been implemented in newer versions of Windows; I haven't checked). When you implement IItemNameLimits, you get a single call to GetValidCharacters, where you can supply a string of either included or excluded characters (only 1 can be used, so if you specify any excluded characters, included becomes null). It's an odd way of doing things.

But it turns out, that's a front end for an actually much more interesting and useful but completely undocumented, SHLimitInputEditWithFlags, an API Geoff Chappell found as exported at ordinal #754 in shell32.dll (it's still ordinal only in Windows 10, even though it's been kicking around since Windows XP).

This function allows a wide variety of options for limits and a tooltip that pops up upon bad input. Instead of just being able to specify an exact string, you can use CT_TYPE1 categories, which in addition to the standard upper, lower, digits... has some handy options like categories for hexadecimal, punctuation, or control characters. It also implements custom categories; binary, octal, and ASCII a-z/A-Z. It also provides control over a tooltip that pops up when you attempt to enter a bad character-- you can have no tooltip, or specify the title, message, and icon (a TTI_* default icon or custom hIcon), and set alignment, width, and timeout (including timing out immediately if a valid input is received). It also handles pasting in several different ways; filtering in the valid chars, pasting until the 1st invalid char, or canceling the paste. If the paste is modified, it puts what was pasted on the clipboard (optionally). The pasting options and automatic control over the tooltip is what really makes this worthwhile over just manually checking KeyPress events or WM_CHAR messages.

Requirements
-No dependencies.
-Function present on Windows XP through at least Windows 10 (I haven't checked 11).

Details

Code:

Public Declare Function SHLimitInputEditWithFlags Lib "shell32" Alias "#754" (ByVal hwndEdit As Long, pil As LIMITINPUTSTRUCT) As Long
SHLimitInputEditWithFlags takes two arguments, an hWnd for an edit control, and an (until this post) undocumented structure. Here's the members and a description:

Code:

Public Type LIMITINPUTSTRUCT
    cbSize As Long      'Size of structure. Must set.
    dwMask As LI_Mask    'LIM_* values.
    dwFlags As LI_Flags  'LIF_* values.
    hInst As Long        'App.hInstance or loaded module hInstance.
    pszFilter As Long    'String via StrPtr, LICF_* category, LPSTR_TEXTCALLBACK to set via LIN_GETDISPINFO, or resource id in .hInst.
    pszTitle As Long    'Optional. String via StrPtr, LPSTR_TEXTCALLBACK to set via LIN_GETDISPINFO, or resource id in .hInst.
    pszMessage As Long  'Ignore if tooltip disabled. String via StrPtr, LPSTR_TEXTCALLBACK to set via LIN_GETDISPINFO, or resource id in .hInst.
    hIcon As Long        'See TTM_SETTITLE. Can be TTI_* default icon, hIcon, or I_ICONCALLBACK to set via LIN_GETDISPINFO.
    hwndNotify As Long  'Window to send notifications to. Must specify if any callbacks used or bad character notifications enabled.
    iTimeout As Long    'Timeout in milliseconds. Defaults to 10000 if not set.
    cxTipWidth As Long  'Tooltip width. Default 500px.
End Type

dwMask is just a list of which of the remaining members should be used:
Code:

'Values for LIMITINPUTSTRUCT.dwMask
Public Enum LI_Mask
    LIM_FLAGS = &H1      'dwFlags used
    LIM_FILTER = &H2    'pszFilter used
    LIM_HINST = &H8      'hinst contains valid data. Generally must be set.
    LIM_TITLE = &H10    'pszTitle used. Tooltip title.
    LIM_MESSAGE = &H20  'pszMessage used. Tooltip main message.
    LIM_ICON = &H40      'hicon used. Can use default icons e.g. IDI_HAND. Loaded from .hInst.
    LIM_NOTIFY = &H80    'hwndNotify used. NOTE: Must be set to receive notifications. Automatic finding of parent broken.
    LIM_TIMEOUT = &H100  'iTimeout used. Default timeout=10000.
    LIM_TIPWIDTH = &H200 'cxTipWidth used. Default 500px.
End Enum

Now we'll get into the core of it with the flags for dwFlags:

Code:

'Values for LIMITINPUTSTRUCT.dwFlags
Public Enum LI_Flags
    LIF_INCLUDEFILTER = &H0    'Default: pszFilter specifies what to include.
    LIF_EXCLUDEFILTER = &H1    'pszFilter specifies what to exclude.
    LIF_CATEGORYFILTER = &H2    'pszFilter uses LICF_* categories, not a string of chars.

    LIF_WARNINGBELOW = &H0      'Default: Tooltip below.
    LIF_WARNINGABOVE = &H4      'Tooltip above.
    LIF_WARNINGCENTERED = &H8  'Tooltip centered.
    LIF_WARNINGOFF = &H10      'Disable tooltip.

    LIF_FORCEUPPERCASE = &H20  'Makes chars uppercase.
    LIF_FORCELOWERCASE = &H40  'Makes chars lowercase. (This and forceupper mutually exclusive)

    LIF_MESSAGEBEEP = &H0      'Default: System default beep played.
    LIF_SILENT = &H80          'No beep.

    LIF_NOTIFYONBADCHAR = &H100 'Send WM_NOTIFY LIN_NOTIFYBADCHAR. NOTE: Must set LIM_NOTIFY flag and .hwndNotify member.
    LIF_HIDETIPONVALID = &H200  'Timeout tooltip early if valid char entered.

    LIF_PASTESKIP = &H0        'Default: Paste any allowed characters, skip disallowed.
    LIF_PASTESTOP = &H400      'Paste until first disallowed character encountered.
    LIF_PASTECANCEL = &H800    'Cancel paste entirely if any disallowed character.

    LIF_KEEPCLIPBOARD = &H1000  'If not set, modifies clipboard to what was pasted after paste flags executed.
End Enum

If you do not use the LIF_CATEGORYFILTER flag, the .pszFilter member must be set to StrPtr(value) where value is a non-delimited string of which characters to allow (by default) or disallow (if LIF_EXCLUDEFILTER flag is included). If you do use the flag, the following categories are valid:
Code:

'Filters support CT_TYPE1 categories:
Public Const LICF_UPPER = &H1
Public Const LICF_LOWER = &H2
Public Const LICF_DIGIT = &H4
Public Const LICF_SPACE = &H8
Public Const LICF_PUNCT = &H10  'Punctuation
Public Const LICF_CNTRL = &H20  'Control characters
Public Const LICF_BLANK = &H40
Public Const LICF_XDIGIT = &H80  'Hexadecimal values, 0-9 and A-F.
Public Const LICF_ALPHA = &H100  'Any CT_TYPE1 linguistic character. Includes non-Latin alphabets.
'Custom categories
Public Const LICF_BINARYDIGIT = &H10000
Public Const LICF_OCTALDIGIT = &H20000 'Base 8; 0-7.
Public Const LICF_ATOZUPPER = &H100000 'ASCII A to Z
Public Const LICF_ATOZLOWER = &H200000 'ASCII a to z
Public Const LICF_ATOZ = (LICF_ATOZUPPER Or LICF_ATOZLOWER)

From there, you're all set to apply basic input limits to an edit control. Remember, if you don't want a tooltip you don't need to set the title, message, and icon, but in that case you must include the LIF_WARNINGOFF flag, or the function will fail. If you are going to have a tooltip, you must as a minimum specify the message.

Advanced

There's a couple flags for advanced options. LIF_NOTIFYONBADCHAR will send hWnd specified by the .hwndNotify member a LIN_BADCHAR notification code in a WM_NOTIFY message. You must subclass the specified hWnd to receive the message (on Windows 10, it will not automatically send them to the parent, but directly to the provided hWnd. That automatic behavior may work on earlier versions, but manually specifying it works on all). From there it has it's own NM structure to copy:

Code:

Public Type NMLIBADCHAR
    hdr As NMHDR
    wParam As Long 'WM_CHAR wParam (Char code)
    lParam As Long 'WM_CHAR lParam (see MSDN for details)
End Type

That gives you the WM_CHAR message.

There's also special handling for WM_PASTE operations built in. The default behavior is to paste whatever characters from the clipboard are allowed, then set the contents of the clipboard to the filtered result. You can change that behavior to only pasting up until the first disallowed character with the LIF_PASTESTOP flag, or to cancel the paste entirely with LIF_PASTECANCEL.

Callbacks

I didn't implement this option in the demo because I don't see a lot of utility for it, but you can specify LPSTR_TEXTCALLBACK for the text fields, and I_ICONCALLBACK for the icon field, and the control will send a LIN_GETDISPINFO message for the tooltip text and LIN_GETFILTERINFO for the filter. I'm not going to detail it, but it works exactly like LVN_GETDISPINFO callbacks for the ListView control, and there's plenty of documentation for that. The constants and structure are included in the Demo if you did want to explore this.

Sample Project

The demo pictured at the top of this post implements a wide array of features, including subclassing for the bad character notifications, but also includes a simple 'Set to numbers only' to show how simple calls to this function can be:
Code:

Dim tli As LIMITINPUTSTRUCT
tli.cbSize = Len(tli)
tli.dwMask = LIM_FILTER Or LIM_FLAGS
tli.dwFlags = LIF_CATEGORYFILTER Or LIF_WARNINGOFF
tli.pszFilter = LICF_DIGIT

SHLimitInputEditWithFlags Text1.hWnd, tli

That's all you need to do to have a textbox take only numbers, with no tooltip.


And that's it! Enjoy this undocumented treasure from the Windows API.

IMPORTANT: This is an undocumented, internal API, with all the issues that involves. There may be small variations in functionality between Windows versions, stability is not guaranteed, and it may be removed at any time from future versions, or have it's ordinal changed.
Attached Images
 
Attached Files

Spectral Forecast equation for signals (VB6)

$
0
0
Intro
This project uses my own mathematical model published in the Chaos journal. The model is called Spectral Forecast. The Spectral Forecast equation is a part of the Spectral Forecast model. The Spectral Forecast equation was initially used on matrices and can be used on other multidimensional mathematical objects. Here, a new utility is demonstrated for signals by using the equation on vectors of the same size.

Spectral Forecast equation (VB6 app 1.0)
Spectral Forecast equation (VB6 app 1.0) - is a demo application designed in Visual Basic 6.0, that is able to mix two signals in arbitrary proportions. Different cases can be seen, with two different waveform signals that are combined depending on the value of a so-called distance d. This distance d is defined from zero to the maximum value found above the two vectors that represent these signals. Note that the implementation of Spectral Forecast equation (VB6 app 1.0) has an issue with the autoredraw setting in the case of Form1 (a VB6 specific issue). Thus, certain real-time processing delays can be observed. However, the version 2.0 that can be found here does not pose a problem with autoredraw.

Download: Spectral Forecast equation (VB6)

Name:  sf3.jpg
Views: 15
Size:  40.1 KB

Name:  sf4.jpg
Views: 16
Size:  22.2 KB
Attached Images
  

Mix two signals by using Spectral Forecast in VB6

$
0
0
This project uses my own mathematical model published in the journal Chaos. The model is called Spectral Forecast. The Spectral Forecast equation is a part of the Spectral Forecast model and it was initially used on matrices. It can also be used on other multidimensional mathematical objects. Here, a new utility is demonstrated for signals by using the equation on vectors of the same size. Spectral Forecast equation for signals (VB6 app 2.0) - is a demo application designed in Visual Basic 6.0, that is able to mix two signals in arbitrary proportions. Different cases can be seen, with two different waveform signals that are combined depending on the value of a so-called distance d. This distance d is defined from zero to the maximum value found above the two vectors that represent these signals.

Download: Mix two signals by using Spectral Forecast in VB6

Name:  sf6.jpg
Views: 15
Size:  37.9 KB

Name:  sf5.jpg
Views: 15
Size:  26.4 KB

Name:  sf4.jpg
Views: 15
Size:  22.2 KB
Attached Images
   

Liquidity planner: new WebView2 demo application

$
0
0
Like last year's "Vacation Planner" (https://www.vbforums.com/showthread....light=webview2), the "Liquidity Planner" is a small but complete VB6 application whose GUI is entirely based on the Microsoft Edge WebView2 runtime. The glue between the application and WebView2 is once again Olaf Schmidt's RC6 (vbrichclient.com).

Name:  LiqPlanS.jpg
Views: 46
Size:  36.8 KB

A few (german) explanations about the software, incl. a demo video can be found at http://www.ww-a.de/liqplaner.html

VB6-Sources: liqplaner_sources.zip
Attached Images
 
Attached Files

Small Collection of RC6 Helper Methods

$
0
0
In case they are of use to anyone else, I'm posting a few small RC6 helper methods that I use quite frequently alongside my RC6 apps. Nothing earth shaking here, but these methods can help reduce some lines of code and some are useful for things like caching recordsets and collections.

I'll likely add more to this thread as I create them, and I'd also be happy to see any methods you've created in the comments.

Notes:

Rc6CollectionHash - Takes an RC6 cCollection object or cCollection.Content byte array and returns a Hash (SHA256 Lowercase Hex string by default, but Uppercase Hex and ByteArray results are possible via the optional p_HashAlgorithm and p_HashFormat parameters).

Rc6RecordsetHash - Takes an RC6 cRecordset object and returns a Hash (SHA256 Lowercase Hex string by default, but Uppercase Hex and ByteArray results are possible via the optional p_HashAlgorithm and p_HashFormat parameters). Note that the cRecordset object must have been create via an SQL SELECT statement.

LZMADeCompInplace - Takes a byte array that was previously compressed via an LZMAComp method, and swaps it out for decompressed data in-place.

LZMADeCompReturn - Takes a byte array that was previously compressed via an LZMAComp method, and returns the decompressed data as a byte array.

LZMACompInplace - Takes a byte array of data and compresses it using the LZMA algorithm, swapping out the decompressed data for the compressed data in-place.

LZMACompReturn - Takes a byte array of data and compresses it using the LZMA algorithm, returning the compressed data as a byte array.


Code:

Code:

Option Explicit

Public Enum e_HashAlgorithm
  hashalgo_SHA256
  hashalgo_SHA1
  hashalgo_SHA384
  hashalgo_SHA512
  hashalgo_MD5
End Enum

Public Enum e_HashFormat
  hashformat_HexLowerCase
  hashformat_HexUpperCase
  hashformat_ByteArray
End Enum

Public Function Rc6CollectionHash(p_CollectionOrContentBytes As Variant, _
                                  Optional ByVal p_HashAlgorithm As e_HashAlgorithm = hashalgo_SHA256, _
                                  Optional ByVal p_HashFormat As e_HashFormat = hashformat_HexLowerCase) As Variant
  ' Returns a Hash string/byte-array (dependent on the p_HashFormat parameter value)
  ' Defaults to returning a SHA256 lower-case hex string
 
  Const c_SepSize As Long = 12  ' This is the length of the unique sequence that separates RC6 collection items
 
  Dim la_Content() As Byte  ' cCollection content
  Dim la_ZeroMem(65) As Byte ' An empty array for zeroing out unique separator sequences
  Dim la_Sep() As Byte ' The unique separator sequence
  Dim l_HashAsHex As Boolean ' When true, we will return the hash as a Hex string. When false, a Byte Array will be returned
  Dim l_Ubound As Long
  Dim ii As Long
  Dim jj As Long
 
  If IsObject(p_CollectionOrContentBytes) Then
      ' We have a cCollection object, so get the content from the object
      la_Content = p_CollectionOrContentBytes.Content
 
  Else
      If VarType(p_CollectionOrContentBytes) = vbByte Or vbArray Then
        ' We have a byte array (presumanly cCollection content)
        la_Content = p_CollectionOrContentBytes
      Else
        ' We have junk, raise an error
        Err.Raise 5, , "Byte array or cCollection class required."
      End If
  End If
 
  ReDim la_Sep(c_SepSize - 1)
 
  ' The unique separator is stored at the end of the collection content
  ' So we will get it from there
  l_Ubound = UBound(la_Content) - (c_SepSize - 1)
  New_c.MemCopy VarPtr(la_Sep(0)), VarPtr(la_Content(l_Ubound)), c_SepSize
 
  ' Loop through the collection to find the unique identifier
  ' Zero out all unique identifiers so that Collections with exact matching key/value content
  ' will always return the same hash (since the unique separators have been removed).
  For ii = 0 To UBound(la_Content) - 67
      For jj = 0 To c_SepSize - 1
        If la_Content(ii + jj) <> la_Sep(jj) Then
            ' This is not a unique separator, so exit the loop
            Exit For
        End If
      Next jj
     
      If jj = c_SepSize Then
        ' The previous loop ran until the end, so we have found a unique separator.
        ' Zero it out so that it won't be part of our hash calculation
        New_c.MemCopy VarPtr(la_Content(ii)), VarPtr(la_ZeroMem(0)), c_SepSize + 4
       
        ii = ii + c_SepSize + 3 ' Jump over the separator + 4 bytes (we use +3 because we will i+1 at the Next loop point)
      End If
  Next ii
     
  ' Zero out record keeping stuff from the end of the content
  ' that can change between otherwise identical key/value content collections
  ' So that we always generate the same hash for the same key/value content
  New_c.MemCopy VarPtr(la_Content(UBound(la_Content) - 66)), VarPtr(la_ZeroMem(0)), 66
 
  ' Hash the key/value content
  l_HashAsHex = (p_HashFormat <> hashformat_ByteArray)
 
  Select Case p_HashAlgorithm
  Case hashalgo_SHA256
      Rc6CollectionHash = New_c.Crypt.SHA256(la_Content, l_HashAsHex)
     
  Case hashalgo_SHA1
      Rc6CollectionHash = New_c.Crypt.SHA1(la_Content, l_HashAsHex)
 
  Case hashalgo_SHA384
      Rc6CollectionHash = New_c.Crypt.SHA384(la_Content, l_HashAsHex)
 
  Case hashalgo_SHA512
      Rc6CollectionHash = New_c.Crypt.SHA512(la_Content, l_HashAsHex)
 
  Case hashalgo_MD5
      Rc6CollectionHash = New_c.Crypt.MD5(la_Content, l_HashAsHex)
 
  Case Else
      Err.Raise 5, , "Unknown hash type: " & p_HashAlgorithm
  End Select
 
  If p_HashFormat = hashformat_HexUpperCase Then
      Rc6CollectionHash = UCase$(Rc6CollectionHash)
  End If
End Function

Public Function Rc6RecordsetHash(po_Recordset As RC6.cRecordset, _
                                Optional ByVal p_HashAlgorithm As e_HashAlgorithm = hashalgo_SHA256, _
                                Optional ByVal p_HashFormat As e_HashFormat = hashformat_HexLowerCase) As Variant
  Dim la_Sql() As Byte ' SQL statement that produce the recordset
  Dim la_Content() As Byte  ' RS content to hash
  Dim l_HashAsHex As Boolean ' When true, we will return the hash as a Hex string. When false, a Byte Array will be returned
  Dim l_Start As Long  ' Start of hashable array data
  Dim l_Len As Long ' Length of hashable array data
 
  ' Find the SQL statement in the recordset content.
  ' The bytes after the SQL statement will produce identical hashes
  ' for indentical RS content selected by any SQL statement
 
  ' Special thanks to Olaf Schmidt for the idea to search the RS for the SQL statement
  ' and begin hashing from after that point in order to ensure identical RS content produces an identical hash value
 
  la_Sql = po_Recordset.SQL
  If UBound(la_Sql) = -1 Then
      Err.Raise 5, , "This method requires a recordset that was created by an SQL statement."
  End If
 
  la_Content = po_Recordset.Content
 
  l_Start = InStrB(1, la_Content, la_Sql) + UBound(la_Sql) + 1
  l_Len = (UBound(la_Content) + 1) - l_Start + 1
 
  ' Remove everything before and including the SQL statement from the content array
  New_c.MemCopy VarPtr(la_Content(0)), VarPtr(la_Content(l_Start - 1)), l_Len
  ReDim Preserve la_Content(l_Len - 1)
 
  ' Hash the remaing byte array content
  l_HashAsHex = (p_HashFormat <> hashformat_ByteArray)
 
  Select Case p_HashAlgorithm
  Case hashalgo_SHA256
      Rc6RecordsetHash = New_c.Crypt.SHA256(la_Content, l_HashAsHex)
     
  Case hashalgo_SHA1
      Rc6RecordsetHash = New_c.Crypt.SHA1(la_Content, l_HashAsHex)
 
  Case hashalgo_SHA384
      Rc6RecordsetHash = New_c.Crypt.SHA384(la_Content, l_HashAsHex)
 
  Case hashalgo_SHA512
      Rc6RecordsetHash = New_c.Crypt.SHA512(la_Content, l_HashAsHex)
 
  Case hashalgo_MD5
      Rc6RecordsetHash = New_c.Crypt.MD5(la_Content, l_HashAsHex)
 
  Case Else
      Err.Raise 5, , "Unknown hash type: " & p_HashAlgorithm
  End Select
 
  If p_HashFormat = hashformat_HexUpperCase Then
      Rc6RecordsetHash = UCase$(Rc6RecordsetHash)
  End If
End Function

' LZMAComp/Decomp Helpers to make it possible to use less code/dims in certain scenarios.
' The "Inplace" versions overwrite the passed byte array with the resulting compressed/decompressed byte array
' The "Return" versions return an appropriately compressed/decompressed byte array.

Public Sub LZMADeCompInplace(pa_CompressedBytes() As Byte)
  Dim la_DecompressedBytes() As Byte
 
  New_c.Crypt.LZMADeComp pa_CompressedBytes, la_DecompressedBytes
  pa_CompressedBytes = la_DecompressedBytes
End Sub

Public Function LZMADeCompReturn(pa_CompressedBytes() As Byte) As Byte()
  Dim la_DecompressedBytes() As Byte
 
  New_c.Crypt.LZMADeComp pa_CompressedBytes, la_DecompressedBytes
  LZMADeCompReturn = la_DecompressedBytes
End Function

Public Sub LZMACompInplace(pa_UncompressedBytes() As Byte, Optional ByVal Level_0to9 As Long = 4, Optional ByVal DictSizePowerOfTwo As Long = 4194304)
  Dim la_CompressedBytes() As Byte
 
  New_c.Crypt.LZMAComp pa_UncompressedBytes, la_CompressedBytes, Level_0to9, DictSizePowerOfTwo
  pa_UncompressedBytes = la_CompressedBytes
End Sub

Public Function LZMACompReturn(pa_UncompressedBytes() As Byte, Optional ByVal Level_0to9 As Long = 4, Optional ByVal DictSizePowerOfTwo As Long = 4194304) As Byte()
  Dim la_CompressedBytes() As Byte
 
  New_c.Crypt.LZMAComp pa_UncompressedBytes, la_CompressedBytes, Level_0to9, DictSizePowerOfTwo
  LZMACompReturn = la_CompressedBytes
End Function

Enjoy!

Micro chart in VB

$
0
0
This compact chart takes into account both positive and negative values from an input. Thus, this VB chart takes into account a lower bound as well as an upper bound. The lower bound represents the lowest value whereas the upper bound represents the highest value over the input. The project in this repository shows two VB charts and both use the PictureBox object from VB6.

Download: Micro chart in VB

The first project found in folder src/chart_short contains the shortest source code for a chart. Basically the implementation is represented by a function named chart that draws on a PictureBox object based on some consecutive numeric values. The second chart found in folder src/chart contains an addition to the first, namely it draws the x-axis and y-axis, and the corresponding baseline ticks.

Name:  chart_short.jpg
Views: 61
Size:  11.5 KB

The second chart project shows an addition to the first, namely it draws the x-axis and y-axis, and the corresponding baseline ticks. Also, the position of the chart can be changed inside the object with the help of four variables responsible for the vertical position, the horizontal position, the width of the chart and the height of the chart. The screenshot below shows the output of the function:

Name:  chart (1).jpg
Views: 56
Size:  18.0 KB
Attached Images
  

FYI: Store many images as StdPicture without hitting the GDI objects limit

$
0
0
I recently had to store many images that are StdPicture objects.
The problem is that each one create a new GDI object, and the (default) limit of max GDI objects is 1000.
(It is set on the key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota)

One solution would be to store them in disk, but that would have been slow (to save and load), so I wanted to keep them on memory.

To convert StdPictures to byte arrays using API would have required a lot of work, since the pictures can be Icons, Bitmaps or Windows metafiles. Also if they were just bitmaps, they can be 8, 16, 32 bits, with palette or even monochrome.
To handle all possible formats would have required lot of code and testing.

But there is a very simple solution: use PropertyBags.

It is easy to store pictures into a PropertyBag object, and when needed back to StdPicture.

I had code like this (air code):

Code:

Private mPictures() as StdPicture

Private Sub StorePictures()
    Dim c As Long

    ReDim mPictures(12000)
    For c = 0 To 12000   
        Set mPictures(c) = [Some pic  in StdPicture format]
    Next
End Sub

Public Property Get MyPicture(Index As Long) As StdPicture
    Set MyPicture = mPictures(Index)
End Property

Turned to this:

Code:

Private mPictures() as PropertyBag

Private Sub StorePictures()
    Dim c As Long
    Dim pb As PropertyBag

    ReDim mPictures(12000)
    For c = 0 To 12000   
        Set pb = New PropertyBag
        pb.WriteProperty "i",  [Some pic  in StdPicture format]
        Set mPictures(c) = pb
    Next
End Sub

Public Property Get MyPicture(Index As Long) As StdPicture
    Set MyPicture = mPictures(Index).ReadProperty("i")
End Property

It does not use any system GDI object.
It is of course a bit slower, but not much.

Helper functions to avoid running out of resources

$
0
0
A process has limited resources available.
They can be of several kinds, here we cover GDI objects, RAM and Disk.

If your program must be able to handle, or at least not to crash with huge amounts of data, you'll probably will need to check resources to see if you can safely do something, or decide to use files over variables, or whatever.

The normal GDI object limit is of 10000. Each font, bitmap, pen, brush, metafile, etc consume GDI handles.
The actual value of GDI the handles limit can be found on the registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota

About the RAM memory, a 32 bits process can use as much as 2 GB.

And the disk, is the free space on the system unit.

Here are the functions:

Code:

Private Declare Function GetDiskFreeSpaceEx Lib "kernel32" Alias "GetDiskFreeSpaceExA" (ByVal lpRootPathName As String, lpFreeBytesAvailableToCaller As Currency, lpTotalNumberOfBytes As Currency, lpTotalNumberOfFreeBytes As Currency) As Long

Private Type PROCESS_MEMORY_COUNTERS_EX
    cb As Long
    PageFaultCount As Long
    PeakWorkingSetSize As Long
    WorkingSetSize As Long
    QuotaPeakPagedPoolUsage As Long
    QuotaPagedPoolUsage As Long
    QuotaPeakNonPagedPoolUsage As Long
    QuotaNonPagedPoolUsage As Long
    PagefileUsage As Long
    PeakPagefileUsage As Long
    PrivateUsage As Long
End Type

Private Declare Function GetProcessMemoryInfo Lib "PSAPI.DLL" (ByVal hProcess As Long, ppsmemCounters As PROCESS_MEMORY_COUNTERS_EX, ByVal cb As Long) As Long
Private Declare Function GetTempPath Lib "kernel32" Alias "GetTempPathA" (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long
Private Declare Function GetGuiResources Lib "user32.dll" (ByVal hProcess As Long, ByVal uiFlags As Long) As Long
Private Declare Function GetCurrentProcess Lib "kernel32" () As Long
Private Declare Function OpenProcess Lib "kernel32.dll" (ByVal dwDesiredAccessas As Long, ByVal bInheritHandle As Long, ByVal dwProcId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long


Public Function FolderExists(ByVal nFolderPath As String) As Boolean
    On Error Resume Next

    FolderExists = (GetAttr(nFolderPath) And vbDirectory) = vbDirectory
    Err.Clear
End Function

Public Function GetTempFolder() As String
    Dim lChar As Long
    Static sValue As String
   
    If sValue = "" Then
        sValue = String$(255, 0)
        lChar = GetTempPath(255, sValue)
        sValue = Left$(sValue, lChar)
        If Right$(sValue, 1) <> "\" Then sValue = sValue & "\"
    End If
    GetTempFolder = sValue
End Function

Public Function GetProcessTempPath() As String
    Static sValue As String
   
    If sValue = "" Then
        sValue = GetTempFolder & "BSP_temp" & CStr(GetCurrentProcessId)
        If Right$(sValue, 1) <> "\" Then sValue = sValue & "\"
        If Not FolderExists(sValue) Then
            MkDir sValue
        End If
    End If
    GetProcessTempPath = sValue
End Function

Public Function GDIResourcesLow() As Boolean
    Static sMaxGDIObjects As Long
    Const GR_GDIOBJECTS = 0
    Const HKEY_LOCAL_MACHINE = &H80000002
    Dim iGDICount As Long
   
    If sMaxGDIObjects = 0 Then
        sMaxGDIObjects = QueryRegValue(HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows", "GDIProcessHandleQuota")
        If sMaxGDIObjects = 0 Then
            sMaxGDIObjects = 9000
        Else
            sMaxGDIObjects = sMaxGDIObjects - 1000
        End If
        If sMaxGDIObjects < 100 Then sMaxGDIObjects = 100
    End If
   
    iGDICount = GetGuiResources(GetCurrentProcess, GR_GDIOBJECTS)
    GDIResourcesLow = (iGDICount >= sMaxGDIObjects)
End Function

Public Function GDIResourcesCritical() As Boolean
    Static sMaxGDIObjects As Long
    Const GR_GDIOBJECTS = 0
    Const HKEY_LOCAL_MACHINE = &H80000002
    Dim iGDICount As Long
   
    If sMaxGDIObjects = 0 Then
        sMaxGDIObjects = QueryRegValue(HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows", "GDIProcessHandleQuota")
        If sMaxGDIObjects = 0 Then
            sMaxGDIObjects = 9500
        Else
            sMaxGDIObjects = sMaxGDIObjects - 500
        End If
        If sMaxGDIObjects < 150 Then sMaxGDIObjects = 150
    End If
   
    iGDICount = GetGuiResources(GetCurrentProcess, GR_GDIOBJECTS)
    GDIResourcesCritical = (iGDICount >= sMaxGDIObjects)
End Function

Public Function GetGDIUsedObjectsCount() As Long
    Const GR_GDIOBJECTS = 0
   
    GetGDIUsedObjectsCount = GetGuiResources(GetCurrentProcess, GR_GDIOBJECTS)
End Function

Public Function FreeMemoryAvailableIsLow() As Boolean
    Dim pmc As PROCESS_MEMORY_COUNTERS_EX
    Dim iProcessHandle As Long
    Dim LRet As Long
    Const PROCESS_QUERY_INFORMATION = 1024
    Const PROCESS_VM_READ = 16
   
    iProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, 0, GetCurrentProcessId)
    If iProcessHandle = 0 Then Exit Function
   
    pmc.cb = LenB(pmc)
    LRet = GetProcessMemoryInfo(iProcessHandle, pmc, pmc.cb)
    If LRet = 0 Then Exit Function
    FreeMemoryAvailableIsLow = pmc.WorkingSetSize > 1600000000
    LRet = CloseHandle(iProcessHandle)
End Function

Public Function FreeMemoryAvailableIsCritical() As Boolean
    Dim pmc As PROCESS_MEMORY_COUNTERS_EX
    Dim iProcessHandle As Long
    Dim LRet As Long
    Const PROCESS_QUERY_INFORMATION = 1024
    Const PROCESS_VM_READ = 16
    iProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, 0, GetCurrentProcessId)
    If iProcessHandle = 0 Then Exit Function
   
    pmc.cb = LenB(pmc)
    LRet = GetProcessMemoryInfo(iProcessHandle, pmc, pmc.cb)
    If LRet = 0 Then Exit Function
    FreeMemoryAvailableIsCritical = pmc.WorkingSetSize > 1700000000
    LRet = CloseHandle(iProcessHandle)
End Function

Public Function FreeDiscSpaceIsCritical() As Boolean
    Dim BytesFreeToCalller As Currency, TotalBytes As Currency
    Dim TotalFreeBytes As Currency, TotalBytesUsed As Currency
   
    If GetDiskFreeSpaceEx(GetTempFolder, BytesFreeToCalller, TotalBytes, TotalFreeBytes) Then
        FreeDiscSpaceIsCritical = (CCur(100) * BytesFreeToCalller \ TotalBytes < 1)
    End If
End Function

HTH.
Attached Files

Binary files inside EXCEL VBA

$
0
0
This application converts any executable file to VBA source code that can be included as a '.bas' module in an EXCEL file. Once inserted into the EXCEL file, the VBA code can be used to completely restore the executable file to disk in the same directory as the EXCEL file. Also, once recomposed on disk, the executable file can be executed automatically. Use the EXCEL file in the "test" directory for testing.


Download: Binary files inside EXCEL VBA


Name:  scr.jpg
Views: 58
Size:  52.3 KB

Name:  excel.jpg
Views: 129
Size:  34.1 KB

Name:  hex.jpg
Views: 55
Size:  17.6 KB
Attached Images
   

Binary metamorphosis

$
0
0
The VB6 applications shown here use the hexadecimal system to encode the binary content of an executable file. The point here is that one may compile an executable file that contains another executable file inside. Once the new executable file is executed, it is able to write the embedded executable file on disk as an independent executable file.


Download: Binary metamorphosis


Name:  2.jpg
Views: 4
Size:  59.7 KB


Name:  tini.jpg
Views: 4
Size:  15.1 KB
Attached Images
  

Array Information

$
0
0
Below is a follow-up for a discussion here on vbForums.

Every array in VB is managed by VB through the use of a SFAEARRAY as defined here by Microsoft as part of Windows. Each array is assigned a SAFEARRAY structure when it is created. The SAFEARRAY contains information on the array itself, not the contents of the array. This structure contains everything needed for VB manage the array including the number of dimensions, byte size of the elements in the array, how many references have been made to the array, the lower bound of each dimension and the number of elements in the dimension and the location in memory of the data in the array.


Once an array has been dimensioned with DIM, this SAFEARRAY also contains a flag that says it cannot be ReDim'd. On the other hand, if the array is declared in the DIM statement without array dimension and bounds, it can be ReDim'd as often as needed during the program’s life. An array with its bounds set by Dim statement are called static arrays and those that have their bounds set via ReDim are called dynamic arrays.

Other than the flag telling VB that it is a static array and can’t be re-dimensioned, these types of arrays use the same SAFEARRAYs.

Many types of arrays are possible: Byte, Integer, Long, Single, Double, Date, Currency, Boolean, String and Object. In VBA for office 2010+ there can also be LongLong arrays when running in a 64-bit host like Excel. In Office 2010+ arrays can be declared as LongPtr but during run-time those are set to 32-bit if the host is 32-bit or LongLong if the host is 64-bit.

Other than Variants, all arrays are of fixed element size (a Variant can re-used as a single value or a different type of array or even arrays within arrays). An array can contain a large number of Longs, for example, but it cannot have Longs and Strings in the same array.

A Variant doesn’t have to be an array but it can be. Strangely, any member of a Variant array itself can be an array of any type, including more Variants.

VB provides almost all of the tools required to work with arrays in its native language so we normally don’t have to be concerned with the SAFEARRAY structure. You can access individual elements of an array by specifying the subscripts. You can make an array virtually any size you want and you can set the lower bound to start with anything you want (doesn’t have to be 0).

However, there are a few limitations that are problematic. A dynamic array might have one or more dimensions assigned to it or it might be uninitialized (occurs initially during a run and after an Erase statement). There are no VB methods to find out this information. The standard way of dealing with this is to just try to use it and if it is uninitialized it will raise an error that you can catch and deal with.

Also, you can’t determine at any point in time how many dimensions the dynamic array has, if any. Once you know the number of dimensions you can use LBound and UBound to determine the bounds of each array. Again, the only way to find out is to attempt to use it and catch the error VB raises.

So for many years most of us have used a function that returns the number of dimensions. Under the hood, this function finds the location of the SAFEARRAY and it reads the first 2 bytes which contain the number of dimensions for the array (0 if uninitialized). So you are probably accessing the SAFEARRAYs of some of your arrays without even knowing it.

There is a problem with the routines I have seen and used for this. They all use a passed parameter that is a Variant. Strangely enough, VB allows you to basically use any variable for the Variant parameter to a procedure and if it is not a Variant, VB makes a copy of it (such as a Long array) and wraps it up in a Variant structure for that procedure. So you can be wasting a lot of memory and time to make a copy of the non-Variant array just so VB can pass a Variant to the function you use to get the number of dimensions. So I wrote a set of functions that you can use for any type of VB array to find out how many dimensions it has and optionally, all of the rest of the information contained in its SAFEARRAY. It is much faster and efficient that the variant type (I have a function for Variants too) because it uses a reference to the array and does not have to copy it into a Variant.

You would think that it would be possible to pass the name of any kind of array to a function and have it figure out all of the array parameters. But no, in every VB procedure you have to declare the Type of each parameter passed to/from the procedure. So if I have an array called “anArray” that is an array of Longs, I can send it only to a Sub or Function that has “xxx() as Long” as a parameter. That makes things somewhat more complex. All I really need to know about the array is the internal location of the array (a pointer, I know, VB doesn’t do pointers…). Even worse, although VB has functions for pointers to Strings, regular variables and objects, it does not have a function for the address of an array. So we play a game and re-use the VarPtr function (a function that returns the address of a regular variable) and I Alias it to something I call “VarPtrArray” and have that tell us the address of the array, not a regular variable.

So each of the Functions I am providing just calls another routine I wrote that does the actual getting of the array information and passes the address of the array to it. So if you want to skip all of the individual type functions (one for a Byte array, one for a Long array, etc.) you can just call the main function (“GetArrayDims” and pass it the address of your array such as “VarPtrArray(anArray)”. Since that’s not normal VB language, I provided all of those other functions for you that use the more conventional name and type of the array. Either way works.

The same set of functions work in VB6, 32-bit VBA and 64-bit VBA. Note that the function LongPtrNumDims is not available if you are running VB6 or Office pre-2010 because the LongPtr type did not exist in those. Also, LongLongNumDims is only available if you’re running 64-bit Office for the same reason.

There is a function (VarNumDims) that determines if a variant is an array and if so, returns the exact same information as the other array info routines. Note that it is possible that the Variant doesn’t contain an array in which case it returns -1 instead of 0 or more dimensions.

I wrote these for 2 reasons: 1) I wanted a more efficient way of getting the number of dimensions than using the one with passing a copy of my array through a Variant, and 2) I am working on a set of routines that lets you move whole variables and UDT’s between programs or to/from files and I needed to transfer a whole array at one time so I needed to know the memory address of the data block so I can copy it in one move. More on this later.

Functions
VarNumDims – for Variants
ByteNumDims – for Byte arrays
IntNumDims – for integer arrays
LongNumDims –for Long arrays
SingleNumDims – for Single arrays
DoubleNumDims – for Double arrays
DateNumDims – for Date arrays
CurrNumDims – for Currency arrays
BoolNumDims – for Boolean arrays
StringNumDims – for String arrays
LongPtrNumDims – for LongPtr arrays (only on VBA7, i.e., Office 2010+)
LongLongNumDims – for LongLong arrays (only on VBA7 and running 64-bit)
ObjNumDims – for Object arrays
GetArrayDims - All of the above functions except StringNumDims call this one. It can also be called directly by all except Variants and String arrays. Instead of calling this with an array type you call it with VarPtrArray(array) which is what the above functions do anyway. Be careful not to give VarPtrArray a non-array variable; it will return the pointer (address) of any variable it is given). Also, do not send a Variant directly to this function since a Variant is laid out in memory differently than other variables including arrays. Use VarNumDims for Variants. There is a third parameter for this function which you should not set yourself. It is set False for all arrays except for a special Variant array (they call it a ByRef Variant array but it is not the same ByRef as in a procedure call).

Return - The number of dimensions in the array.
0 The array has no dimensions. It is uninitialized (start of run or Erase’d).
>0 The number of dimensions in the array.
-1 Only for a Variant. The Variant is not an array.

Caution – The functions give a snapshot of the array at the time you execute the function. It isn’t dealing with the data in the array but only the structure of the array. This structure information is accurate until the variable goes out of scope and is deleted or the array is Redim’d or Erase’d. As the programmer, you are in charge of ReDim’s so you can re-run any of the functions as needed. Just know the data are not live but are a snapshot in time.

Optional parameter “GetExtraInfo” – Defaults to False but if set True, generates more info about he specified array. There is a Public User Defined Type (UDT) called tArrayInfo and a Public variable called “ArrayInfo” of this type that is discussed below. Some data can always be found in this variable after one of these function calls (taken from the array’s SAFEARRAY) and there are a few more things you can obtain by calling one fo the functins with GetExtraInfo = True.

Public Type tArrayInfo - see variable ArrayInfo just below this that uses this Type
Size As Long ' Extra info, size of data ni the array
NumElements As Long ' Extra Info, Number of elements in all array dimenstions
Bounds() As Long – Extra Info, pairs of Lower/Upper bounds, # pairs = # Dims
Example- Dim a(1 to 2, 0 to 4, 99 to 100) is put in pairs in this order 99,100,0,4,1,2
cDims As Integer - The number of dimensions
Features As Integer – Combination of the following possibilities
0x0001 Array is allocated on the stack.
0x0002 Array is statically allocated.
0x0004 Array is embedded in a structure.
0x0010 Array may not be resized or reallocated.
0x0020 The SAFEARRAY MUST contain elements of a UDT.
0x0040 The SAFEARRAY MUST contain MInterfacePointers elements.
0x0080 An array that has a variant type.
0x0100 An array of BSTRs.
0x0200 An array of IUnknown*.
0x0400 An array of IDispatch*.
0x0800 An array of VARIANTs.
0xF0E8 Bits reserved for future use.
ElementSize As Long - The size of a single element
cLocks As Long - Number of locks on the array
pvData As Long/LongLong - Pointer to the array data.

The Public variable ArrayInfo is of the above type. Obviously, the values in the variable only mean something if it’s an array (variant might not be) and the array has dimensions. It made more sense to me to re-use this public variable for each of the functions rather than having a bunch of variables of this type. If for some reason you need more than one of these at the same time you can easily declare another variable of the same type and copy ArrayInfo as needed.

None of the code provided needs to be modified for your use. I have a master library of procedures I use all the time and I have incorporated this code into my library. You can do the same or you can leave it in its own .bas module as it is now.

I have included sample programs for VB6 program as well as Excel file. Hopefully everything is clear. If not, please let me know.

Update to v1.1.0 - See posts #5 and #6 below. Apparently VB handles String arrays differently so I modified my routine for getting array info for strings. Note that the old way works for static string arrays (one where the number of arrays and lower/upper bounds are set with a Dim statement instead of a ReDim statement).
Attached Files

delete this post

VB6 nestable UDT-based Node-Class with fast teardown

$
0
0
Not much to it, just a Demo for an UDT-based cNode-Class,
which can be extended (by adding Fields to the Class-internal UDT).

This Node-Class is "self-contained" (no extra-modules are needed).

Performance is quite good... adding 1,001,000 Nodes total as 1000ChildNodes on 1000ChildNodes takes:
- about 0.33sec in the IDE
- about 0.22sec native compiled (all Options)

cNode will hand out "dynamically created wrapper instances of itself -
(using internal Indexes which point into the Root-UDT-Array)", on all cNode-returning Properties, which are:
- Root() As cNode
- Parent() As cNode
- NodeById(ByVal ID As Long) As cNode
- Child(ByVal IdxZeroBased As Long) As cNode
- FirstChild() As cNode
- NextSibling() As cNode
- PrevSibling() As cNode
- LastChild() As cNode


cNode
Code:

Option Explicit

Private Declare Sub AssignArrTo Lib "kernel32" Alias "RtlMoveMemory" (pDst() As Any, pSrc() As Any, Optional ByVal CB& = 4)

Private Type tNode
  PIdx As Long
  ChCount As Long
  ChIdxs() As Long
  'define UserData-NodeProps from here onwards...
  Text As String
  '... a.s.o. (see the Prop-Mapping-Section at the end of this Class)
End Type

'we need only 3 Private Vars to present a Node-instance
Private mIdx As Long, mNodes() As tNode, mInternalInstance As Boolean

Private Sub Class_Initialize()
  ReDim mNodes(0)
End Sub
Private Sub Class_Terminate() 'cleanup the fake arr-reference...
  Dim aNull(): If mInternalInstance Then AssignArrTo mNodes, aNull '...but only when created internally here
End Sub

Public Sub AddNode(Text As String) 'for best add-performance, include all the (UDT-)Prop-Values as Params here
  Dim UB As Long, CC As Long
      UB = UBound(mNodes)
 
  CC = -mNodes(0).PIdx + 1: mNodes(0).PIdx = -CC '<- mNodes(0).PIdx holds the negative, total ChildNode-Count
  If CC >= UB Then ReDim Preserve mNodes(32 + CC * 1.6)
 
 
  With mNodes(CC) 'set the direct UDT-entries of our new ChildNode
    .PIdx = mIdx  '... starting with the ParentIndex (which is the Index of *this* (Parent)Node-instance)
    .Text = Text
    '... a.s.o. for more UDT-Values (see the UDT-def at the top of this Class)
  End With
 
  With mNodes(mIdx) 'also put the right Index-entry into the Child-Array of the UDT for this (Parent)Node-Instance
    If .ChCount = 0 Then ReDim .ChIdxs(4)
    If .ChCount >= UBound(.ChIdxs) Then ReDim Preserve .ChIdxs(.ChCount * 1.6)
    .ChIdxs(.ChCount) = CC 'set the Index of the new Child (CC is equivalent with that)
    .ChCount = .ChCount + 1
  End With
End Sub
 
Friend Sub Init(ByVal Idx As Long, Nodes() As tNode) 'do not call this method from the outside
  mIdx = Idx: mInternalInstance = True 'set the Idx + flag this instance as "internally created"
  Erase mNodes: AssignArrTo mNodes, Nodes 'make a "fake" Array-copy
End Sub

Public Property Get ID() As Long 'to provide a unique Identifier within the Tree for this Node
  ID = mIdx 'on the outside, this is only useful to compare Nodes for identity
End Property

Public Property Get TotalNodeCount() As Long
  TotalNodeCount = -mNodes(0).PIdx
End Property

Public Property Get Level() As Long 'determines the "Hierarchy-Depth" of the current Node
  Dim i As Long: i = mIdx
  Do While i: i = mNodes(i).PIdx: Level = Level + 1: Loop
End Property

Public Property Get Root() As cNode
  Set Root = New cNode: Root.Init 0, mNodes
End Property
Public Property Get Parent() As cNode
  If mIdx Then Set Parent = New cNode: Parent.Init mNodes(mIdx).PIdx, mNodes
End Property
Public Property Get NodeById(ByVal ID As Long) As cNode
  Set NodeById = New cNode: NodeById.Init ID, mNodes
End Property
Public Property Get Child(ByVal IdxZeroBased As Long) As cNode
  Set Child = New cNode: Child.Init mNodes(mIdx).ChIdxs(IdxZeroBased), mNodes
End Property
Public Property Get ChildCount() As Long
  ChildCount = mNodes(mIdx).ChCount
End Property

Public Property Get FirstChild() As cNode
  If mNodes(mIdx).ChCount = 0 Then Exit Property 'no first Child available here (return Nothing)
  Set FirstChild = New cNode: FirstChild.Init mNodes(mIdx).ChIdxs(0), mNodes
End Property
Public Property Get NextSibling() As cNode
  If mIdx = 0 Then Exit Property 'the Root-Node has no siblings
  With mNodes(mNodes(mIdx).PIdx)
    If .ChCount <= 1 Then Exit Property 'with a ChildCount <=1 there's no next Sibling
    If .ChIdxs(.ChCount - 1) = mIdx Then Exit Property 'the last Child has no next Sibling
    Dim i As Long
    i = mIdx - .ChIdxs(0) 'simple optimization-attempt (avoiding the loop, when the second-next line checks out true)
    If i < 0 Then i = 0 Else If i > .ChCount - 2 Then i = .ChCount - 2   
    If .ChIdxs(i) = mIdx Then Set NextSibling = New cNode: NextSibling.Init .ChIdxs(i + 1), mNodes: Exit Property
    For i = 0 To .ChCount - 2
      If .ChIdxs(i) = mIdx Then Set NextSibling = New cNode: NextSibling.Init .ChIdxs(i + 1), mNodes: Exit For
    Next
  End With
End Property
Public Property Get PrevSibling() As cNode
  If mIdx = 0 Then Exit Property 'the Root-Node has no siblings
  With mNodes(mNodes(mIdx).PIdx)
    If .ChCount <= 1 Then Exit Property 'with a ChildCount <=1 there's no previous Sibling
    If .ChIdxs(0) = mIdx Then Exit Property 'the first Child has no previous Sibling
    Dim i As Long
    i = mIdx - .ChIdxs(0) 'simple optimization-attempt (avoiding the loop, when the second-next line checks out true)
    If i < 1 Then i = 1 Else If i > .ChCount - 1 Then i = .ChCount - 1
    If .ChIdxs(i) = mIdx Then Set PrevSibling = New cNode: PrevSibling.Init .ChIdxs(i - 1), mNodes: Exit Property
    For i = 1 To .ChCount - 1
      If .ChIdxs(i) = mIdx Then Set PrevSibling = New cNode: PrevSibling.Init .ChIdxs(i - 1), mNodes: Exit For
    Next
  End With
End Property
Public Property Get LastChild() As cNode
  If mNodes(mIdx).ChCount = 0 Then Exit Property 'no last Child available here (return Nothing)
  Set LastChild = New cNode: LastChild.Init mNodes(mIdx).ChIdxs(mNodes(mIdx).ChCount - 1), mNodes
End Property

'Ok, finally the mapping of (non-navigation-related) UDT-Props to and from the outside
Public Property Get Text() As String
  Text = mNodes(mIdx).Text
End Property
Public Property Let Text(RHS As String)
  mNodes(mIdx).Text = RHS
End Property

Into a Test-Form:
Code:

Option Explicit

Private Root As cNode

Private Sub Form_Click()
  AutoRedraw = True: FontName = "Tahoma": Cls: Tag = Timer
 
  If Root Is Nothing Then
    Set Root = New cNode 'only Root-Nodes are created with the New-Operator (on the outside of cNode)
        Root.Text = "RootNode"
       
    AddChildNodesTo Root, 1000, "ChildLevel1_"
   
    Dim i As Long
    For i = 0 To Root.ChildCount - 1
        AddChildNodesTo Root.Child(i), 1000, "ChildLevel2_"
    Next
   
    Print "Construction-Time:", Format(Timer - Tag, " 0.00sec")
    Print "Total-NodeCount:", Root.TotalNodeCount
    Print "Root-ChildCount:", Root.ChildCount
    Print "ChildCount of a Level1-Child:  "; Root.FirstChild.ChildCount
    Print "ChildCount of a Level2-Child:  "; Root.FirstChild.FirstChild.ChildCount
    Print "Level-PrintOut:", Root.Level; Root.LastChild.Level; Root.LastChild.LastChild.Level
    With Root.LastChild.LastChild
        Print vbLf; "Infos for the Last ChildNode:"; vbLf; "  " & .Text
        Print "      IsChildOf: "; .Parent.Text
        Print "        IsChildOf: "; .Parent.Parent.Text
    End With
   
  Else
    Set Root = Nothing
    Print "Destruction-Time:", Format(Timer - Tag, " 0.00sec")
  End If
End Sub

Sub AddChildNodesTo(N As cNode, ByVal ChildCount As Long, TextPrefix As String)
  Dim i As Long
  For i = 0 To ChildCount - 1: N.AddNode TextPrefix & i: Next
End Sub

Have fun,

Olaf

[VB6] Event Tracing for Windows - Monitoring File Activity with ETW

$
0
0

VBEventTrace v1.0
Using Event Tracing for Windows in VB6

Event Tracing for Windows (ETW) is a notoriously complex and unfriendly API, but it's extremely powerful. It allows access to messages from the NT Kernel Logger, which provides a profound level of detail about activity on the system. It provides details about many types of activity, but this first project will focus on File Activity. I also plan to follow this up with a monitor for TcpIp and Udp connections.

Given the complexity and unfriendliness that's given it the reputation of the world's worst API, why use it? You can find many projects that monitor file activity, using methods like SHChangeNotify, FindFirstChangeNotification, and monitoring open handles. But the reality is these are all high level methods that don't cover quite a bit of activity. The kernel logger shows activity coming from low level disk and file system drivers. This project started with me wanting to know what was causing idle hard drives to spin up, and none of the higher levels methods offered a clue. Programs like ProcessHacker and FileActivityView use the NT Kernel Logger as well, but I wanted two things: Better control over the process, and doing it in VB6. Why? Well, if you've seen my other projects, you know I'm excessively fond of going way beyond what VB6 was meant for both in terms of low level stuff and modern stuff.

Intro

This project tracks most of the FileIo events, providing a great deal of control over what events you watch and filtering them to find what you're looking for. It also looks up name and icon of the process that generated the activity (not always available). With no filtering or only light filtering, a tremendous amount of data is generated. The VB TextBox and ListView simply could not keep up with the rapid input, and all sorts of memory and display issues ensued where text and List Items disappeared. So while the project was already complicated to begin with, the only way to cope with this was to use an API-created Virtual ListView (created via API and using the LVS_OWNERDATA style so it only includes the data currently being displayed).

How It Works
Have a read here for an introduction to setting up a Kernel Logger with ETW, and then realize it's even *more* complicated than that article suggests, because of some VB6 specific issues, and the hell on earth involved in interpreting the data.

Just starting the tracing session has 3 steps. You start with the EVENT_TRACE_PROPERTIES structure. Now, it's daunting enough on it's own. But when you read the article linked, you realize you have to have open bytes appended *after* the structure for Windows to copy the name into. Then the article doesn't touch on a recurring theme that was the source of a massive headache implementing it... in other languages, structures get automatically aligned along 8 byte intervals (a Byte is 1 byte, an Integer 2 bytes, a Long 4 bytes... alignment is making each Type a multiple of a certain number of bytes). Not so in VB. It took quite a bit of crashing and failures to realize this, then properly pad the structures. The code uses it's own structure for the StartTrace function that looks like this:

Code:

Public Type EtpKernelTrace
    tProp As EVENT_TRACE_PROPERTIES
    padding(0 To 3) As Byte
    LoggerName(0 To 31) As Byte 'LenB(KERNEL_LOGGER_NAMEW)
    padding2(0 To 3) As Byte
End Type

Needed to include 4 bytes of padding after the structure, then add room for the name, then make sure it's all aligned to 8 byte intervals. Now we're ready to go, with tStruct being a module-level EtpKernelTrace var:

Code:

With tStruct.tProp
    .Wnode.Flags = WNODE_FLAG_TRACED_GUID
    .Wnode.ClientContext = 1&
    .Wnode.tGUID = SelectedGuid
    .Wnode.BufferSize = LenB(tStruct)
    .LogFileMode = EVENT_TRACE_REAL_TIME_MODE 'We're interested in doing real time monitoring, as opposed to processing a .etl file.
    If bUseNewLogMode Then
        .LogFileMode = .LogFileMode Or EVENT_TRACE_SYSTEM_LOGGER_MODE
    End If
    'The enable flags tell the system which classes of events we want to receive data for.
    .EnableFlags = EVENT_TRACE_FLAG_DISK_IO Or EVENT_TRACE_FLAG_DISK_FILE_IO Or EVENT_TRACE_FLAG_FILE_IO_INIT Or _
                    EVENT_TRACE_FLAG_DISK_IO_INIT Or EVENT_TRACE_FLAG_FILE_IO Or EVENT_TRACE_FLAG_NO_SYSCONFIG
    .FlushTimer = 1&
    .LogFileNameOffset = 0&
    .LoggerNameOffset = LenB(tStruct.tProp) + 4 'The logger name gets appended after the structure; but the system looks in 8 byte alignments,
                                                'so because of our padding, we tell it to start after an additional 4 bytes.
End With

'We're now ready to *begin* to start the trace. StartTrace is only 1/3rd of the way there...
hr = StartTraceW(gTraceHandle, StrPtr(SelectedName & vbNullChar), tStruct)

This begins to start a trace session. There's SelectedGuid and SelectedName because there's two options here. In Windows 7 and earlier, the name has to be "NT Kernel Logger", and the Guid has to be SystemTraceControlGuid. If you use that method, there can only be 1 such logger running. You have to stop other apps to run yours, and other apps will stop yours when you start them. On Windows 8 and newer, there can be several such loggers, and you supply a custom name and GUID, and inform it you want a kernel logger with the flag added with bUseNewLogMode. This project supports both methods. The EnableFlags are the event providers you want enabled. This project wants the disk and file io ones, but there's many others. Onto step 2...

Code:

Dim tLogfile As EVENT_TRACE_LOGFILEW
ZeroMemory tLogfile, LenB(tLogfile)
tLogfile.LoggerName = StrPtr(SelectedName & vbNullChar)
tLogfile.Mode = PROCESS_TRACE_MODE_REAL_TIME Or PROCESS_TRACE_MODE_EVENT_RECORD 'Prior to Windows Vista, EventRecordCallback wasn't available.
tLogfile.EventCallback = FARPROC(AddressOf EventRecordCallback) 'Further down, you can see the prototype for EventCallback for the older version.
gSessionHandle = OpenTraceW(tLogfile)

We have to tell it *again* we want to use real time mode, not a .etl log file, and at this point we supply a pointer to a callback that receives events. This project uses a newer type of callback available in Vista+, but has prototypes for the older one. Like a WndProc for subclassing, this has to be in a standard module (.bas); to put it in a class module/form/usercontrol, you'd need the kind of self-subclassing code like you find on the main form (but be careful copying/pasting that, it's been slightly modified and only works with Forms).

The final step is a single call: To ProcessTrace. Only then will you begin receiving events. But of course, this simple call couldn't be simple. ProcessTrace doesn't return until all messages have been processed, which in a real-time trace means indefinitely until you shut it off. So if you call it, execution stops. In that thread. In other languages, spinning off a new thread to call ProcessTrace is easy. In VB, it's painful. This project makes use of The trick's VbTrickThreading project to launch a new thread for the ProcessTrace call. The downside here is that means event tracing is only possible in a compiled exe, making debugging difficult.

Once you've called ProcessTrace, your callback begins receiving messages. We need to match them up with their provider, and then check the OpCode...

Code:

Public Sub EventRecordCallback(EventRecord As EVENT_RECORD)
'...
If IsEqualIID(EventRecord.EventHeader.ProviderId, DiskIoGuid) Then
    iCode = CLng(EventRecord.EventHeader.EventDescriptor.OpCode)
   
    'Some events use the same MOF structure and are processed similarly, so we group them together and separate
    'the codes for filtering and logging later.
    If (iCode = EVENT_TRACE_TYPE_IO_READ) Or (iCode = EVENT_TRACE_TYPE_IO_WRITE) Then

The EVENT_RECORD structure is also a nightmare. Many different parts of it had to having alignment padding added, and it tripped me up for a good long while. Extra thanks to The trick for helping me figure out the right alignment on this part.

From here, we're ready to process the data. The raw data is returned in MOF structures, e.g. this one for one of the Open/Create messages. There's ways to automate the processing of them, but that makes everything so far seem simple, and is the domain for a future project. For now, we manually process the raw data, which we copy from the pointer in .UserData in the event record. The documentation doesn't mention *at all* that even if you're running a 32bit application, these structures have 64bit sizes. The official documentation doesn't note which "uint32" types are pointers, and thus are 8 bytes instead of 4, so I had to go digging in some deep system files. The original 32bit structures are all included, but currently this project only works on 64bit Windows. It's possible to tell automatically via flags in the event record... perhaps in the future.

Here what the File Open/Create structure looks like, and how we set it up:

Code:

Public Type FileIo_Create64 'Event IDs: 64
    IrpPtr As Currency
    FileObject As Currency
    ttid As Long
    CreateOptions As CreateOpts
    FileAttributes As FILE_ATTRIBUTES
    ShareAccess As Long
    OpenPath(MAX_PATH) As Integer
End Type

Fortunately VB has the Currency data type, which we also used for our event trace handles, which is 8 bytes. We can use this because there's no point where we have to interact a numeric representation of the value... it's just all raw bytes behind the scenes. Unfortunately, FileAttributes is only what's passed to the NtOpenFile API and not an actual query of the file's attributes, so is almost always 0 or FILE_ATTRIBUTES_NORMAL. We pick MAX_PATH for the size of the array, because using a fixed-size array avoids VB's internal SAFEARRAY type, which would make copying a structure from a language without it much more complicated. Converting a string of integer's to a normal string is trivial, but the real problems comes when you see what it is: files names look like \Device\HarddiskVolume1\folder\file.exe. To convert those into normal Win32 paths the project creates a map by querying each possible drive letter in the QueryDosDevice API, which returns a path like that for each drive.

Not all events contain a file name, so the project stores a record with the FileObject, which allows us to match other operations on the same file, and get the name. The documentation says we're supposed to receive event code 0 for names... but I've never seen that message come in. Perhaps on earlier Windows versions.

Perhaps the biggest problem in processing the data is that while there's an ProcessID and ThreadID in the event record's header, the process id is very often -1. Sometimes that information is returned in other events. This project goes through incredible lengths to correlate every with every other event in order to track down the process whenever possible. So many events will display -1 at first, and get updated later.

There's still a lot of work to be done in process attribution, and getting info about files already open before the trace starts. I attempted to copy ProcessHacker's use of a KernelRundownLogger, but so far have not been successful. I'll be look at other methods, but if I didn't put out a Version 1, who knows how long it would be.

Once we've captured the events, we store it in a the ActivityLog structure, which is the master data store for what's displayed on the ListView.

Options

You can see in the screenshot a number of options. There's the main controls for the trace; you don't really need to worry about 'Flush', it's there for completeness and shouldn't be needed. Stop is always enabled because in the event of crashes, you can stop previous sessions. You can save the trace; it saves what you see in the ListView, tab separated. There's options for which events you want to capture, whether to use the new logger method described earlier (Win8+), and the refresh interval for the ListView. The items aren't added to the ListView; they're stored in the ActivityLog structure, and the ListView is in virtual mode, so it only asks for what it's currently displaying. The refresh interval is how often it checks for new events and sets the last one as visible, creating a view that is always scrolled to the bottom but without the invisible items stored in the ListView itself, dramatically improving speed. (The greyed out option is for future work, not currently implemented)

Very important is the filtering system, if you're looking for certain activity. Each field allows multiple entries separated with a | (bar, it also accepts broken bars found on some keyboards). There's a button that displays a message explaining the syntax and the flow... the first thing checked is whether it's from a process we're interested in based on the process options. You can use DOS wildcards in the Process name field and File name fields, but not the paths at this point... for now the paths are strictly checked on a 'Starts with...' basis. After checking the process, then it checks 'Path must match', then 'Exclude paths', then 'File name must match', finally 'Exclude file name'.

Finally on the right there's a message log, which displays information about starting/stopping the trace, when a different function has correlated a previously unidentified process id, and any errors that arise.

Not shown: If you right click the ListView, there's a popup menu with options to open the selected items, show the selected items in Explorer, copy selected file names, copy all file names, copy the selected lines (tab separated), copy all lines, show properties of the process, and show the process in Explorer.

Requirements
PLEASE TAKE NOTE. This program has atypical requirements.

-Windows Vista or newer 64bit. Although like all VB6 apps the app itself is 32bit, it handles data structures generated by the system, and is currently only coded to handle 64bit structures. To run on 32bit Windows, use the regular MOF structures instead of the x64 ones (and change the size checks at the start of each processing routine).

-This program can only start event tracing when compiled, due to the need for multithreading that cannot be done in a single thread.

-You must Run As Administrator to have permission to access the NT Kernel Logger, which this app uses.

-There are no external dependencies. However, the demo uses a manifest for Common Controls 6.0 styles, and it's advised you also use them in any other project.

-Unicode is supported in the ListView for displaying files etc, but the filter TextBoxes are just regular VB ones, so you'd need to replace those to use Unicode in filtering.

Windows 10 is strongly recommended. I have not had the opportunity to test this on other OSs.

This API is *extremely* complicated and finicky, so there's bound to be bugs. Especially on other Windows versions. Let me know, I'll see what I can do.
Attached Files

Connecting to a Bitcoin node with Winsock and performing a handshake

$
0
0
Considering that Bitcoin is open source C++, would it be possible to emulate a handshake and send it through VB6/mswinsck? Even with a glitchy request that isn't granted, but causes `bitcoind` to do some thinking from a request submitted by an external non-bitcoin client.

Happy to pay $100 ETH to anyone who can help me do this from VB6. The idea is to hammer on my own node to see if I can break it. I'm currently running a live Bitcoin node and can share access to the VPS if needed. I think this would be an interesting use of VB6 and if successful would showcase the current year relevance of Classic VB (which I really wish they'd create an official modernized version of) - I digress. Bitcoin-core doesn't have a bug bounty program and this is purely for personal research. I'm thinking roughly 9X% chance of failure but whatever - and I don't think there exists a practical way to spider all of the Bitcoin network for IP:P2P-PORT lists so it is unlikely if not impossible for a blackhat to recreate such a bug for nefarious purposes.

Thanks!

Name:  corn.jpg
Views: 13
Size:  40.2 KB
Attached Images
 

Work with paths longer than MAX_PATH

$
0
0
Windows 10 allows to have paths > MAX_PATH (260 characters), but you'll have problems with VB and APIs.

Here is a workaround:

Code:

Private Declare Function GetShortPathNameW Lib "kernel32" (ByVal lpszLongPath As Long, ByVal lpszShortPath As Long, ByVal cchBuffer As Long) As Long
Code:

Public Sub ShortenPath(nPath As String)
    Const MAX_PATH = 260
    Dim iRet As Long
    Dim iBuff As String
   
    If Len(nPath) > MAX_PATH Then
        iRet = GetShortPathNameW(StrPtr("\\?\" & nPath), 0, 0)
        If iRet Then
            iBuff = Space$(iRet - 1)
            If GetShortPathNameW(StrPtr("\\?\" & nPath), StrPtr(iBuff), iRet) Then
                If Left$(iBuff, 4) = "\\?\" Then iBuff = Mid(iBuff, 5)
                nPath = iBuff
            End If
        End If
    End If
End Sub

Prepending "\\?" to the path allows the GetShortPathNameW API to handle it and return a short path that the program can use.

PS: I passed the argument ByRef to make it work faster.
Viewing all 1487 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>