Context: I'm implementing a SHA-256 hashing to Lotusscript.
Hashing works on 32bits. LotusScript has only signed Long. When result are bigger than 2,147,483,647 we get overflow.
QUESTION: how to turn off overflow detection on Long?
If impossible, is there a working around? I was thinking at what Richard Schwartz has wroten in http://femkegoedhart.com/2012/02/05/lotusscript-timedifference-long-data-type-grrr/, how could I use NotesDateTime to help work around my problem?
Should I "forget this" but then how to hash in Lotusscript (ok I can think to use LS2J and use a standard java SHA 256)
Of course if you've got a LS implementation of SHA-256 it's also a good response :-)
Thank for your help, I feel a bit lost...
part of the code:
Dim T1 As Long
Dim h2 As Long
Dim Sigma1 As Long
Dim K_t As Long
h2=1541459225
Sigma1 = 21895337
K_t=1116352408
MsgBox String(32-Len(Bin$(h2)),"0")+Bin$(h2)+" " +"Bin$(h2)"+Chr$(13)+ _
String(32-Len(Bin$(Sigma1)),"0")+Bin$(Sigma1)+" " +"Bin$(Sigma1)"+Chr$(13)+ _
String(32-Len(Bin$(K_t)),"0")+Bin$(K_t)+" " +"Bin$(K_t)",,"DEBUG"
T1 = h2 + Sigma1
MsgBox "T1 = " & T1 & " binary representation:" + String(32-Len(Bin$(T1)),"0")+ Bin$(T1)
T1 = h2 + Sigma1 + K_t ' **this line cause overflow**
MsgBox "T1 = " & T1 & " binary representation:" + String(32-Len(Bin$(T1)),"0")+ Bin$(T1)
NB I based my implementation on the work of Chris Veness: http://www.movable-type.co.uk/scripts/sha256.html
You can't turn off the overflow detection on a LONG in LotusScript. Also your sample above the BIN() function will attempt to convert to a long, so that rules out the use of DOUBLE.
The dateTime, object while it treats the number as LONG unsigned, it is probably a bit of hack to use.
Personally I'd recommend creating a small C DLL which does the work and passes it back as a string.
Alternatively create your code in Java as it doesn't have the same limitations that LS has.
As answered Simon O'Doherty, there is no way to turn off the overflow detection on a LONG in LotusScript.
I abandon the LS way... for now (maybe will I do an other attempt one day. Frantisek Kossuth, your right I need to take from the begining the variable limits... that the tricky part here)
Thanks for your help!
*Simon: I prefere to do LS2J than DLL!
I join the very simple class in java:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHA256 {
public String sha256(String message) throws NoSuchAlgorithmException {
MessageDigest md1 = MessageDigest.getInstance("SHA-256");
String resu = A9Utility.bytesToHex(md1.digest(message.getBytes()));
System.out.println("here SHA256: resu ="+resu);
return resu;
}
}
for A9Utility.bytesToHex, I used the https://code.google.com/p/a9cipher/source/browse/src/cosc385final/A9Utility.java
so my call in LS is now:
UseLSX "*javacon"
Use "SHA256"
[...]
Dim mySession As JavaSession
Dim myClass As JavaClass
Dim myObject As JavaObject
Set mySession = New JavaSession()
Set myClass = mySession.GetClass("SHA256")
Set myObject = myClass.CreateObject()
Dim resu As String
resu = myObject.sha256(message)
You can use an COM component in LS and call it. There are a number of well developed and proven SHA-256 (and other hashes) COM components out there.
If you follow this route, you will need to 'handle' the return value as a string or some other binary encoding (base64?) as the result may (or may not) require unsigned longs to hold a raw value. Most of these components provide methods for doing this.
Bit 'old fashioned' for todays apps, but hey, you're using LS, so maybe that is not a concern.
Related
I was chastised by a professional developer with a lot of years of experience for Hard Coding my DB name
OK I get it we sometimes carry our bad codding habits with us till we learn the correct way to code
I have finally learned to use Interpolated Strings (personal view they are not pretty)
My Question involves the two Sub's posted below GetDB runs first then HowMany is called from GetDB
Sorry for stating the obvious my reason is I think that NewWord.db gets declared in GetDB and works in HowMany without the same construction Just a Wild Guess
Notice NO $ or quotation used in HowMany
Both Sub's produce desired results
The question is Why don't both statements need to be constructed the same?
Public Sub HowMany()
'Dim dbName As String = "NewWord.db"
Dim conn As New SQLiteConnection("Data Source ='{NewWord.db}';Version=3;")
tot = dgvOne.RowCount ' - 1
tbMessage.Text = "DGV has " & tot.ToString & " Rows"
End Sub
Private Sub GetDB()
Dim str2 As String
Dim s1 As Integer
'Dim dbName As String = "NewWord.db"
Using conn As New SQLiteConnection($"Data Source = '{"NewWord.db"}' ;Version=3;")
conn.Open()
That second method is a ridiculous and pointless use of string interpolation. What could possibly be the point of inserting a literal String into a literal String? The whole point is that you can insert values determined at run time. That second code is equivalent to using:
"Data Source = '" & "NewWord.db" & "' ;Version=3;"
What's the point of that? The idea is that you retrieve your database name from somewhere at run time, e.g. your config file, and then insert that into the template String, e.g.
Dim dbName = GetDbNameFromExternalFile()
Using conn As New SQLiteConnection($"Data Source = '{dbName}' ;Version=3;")
Now the user can edit that external file to change the database name after deploying the application. How could they change the name in your code?
To be clear, string interpolation is just native language support for the String.Format method. You can see that if you make a mistake that generates an exception and the that exception will refer to the String.Format method. In turn, String.Format is a way to make code that multiple values into a long template easier to read than if multiple concatenation operators were used.
Having lots of quotes and ampersands makes code hard to read and error-prone. I've lost count of the number of times people miss a single quote or a space or the like in a String because they couldn't read there messy code. Personally, I'll rarely use two concatenation operators in the same expression and never three. I'll do this:
Dim str = "some text" & someVar
but I'll rarely do this:
Dim str = "some text" & someVar & "some more text"
and I'll never do this:
Dim str = "some text" & someVar & "some more text" & someOtherVar
Before string interpolation, I would use String.Format:
Dim str = String.Format("some text{0}some more text{1}", someVar, someOtherVar)
Nowadays, I'll generally use string interpolation:
Dim str = $"some text{someVar}some more text{someOtherVar}"
Where I may still use String.Format over string interpolation is if one value is getting inserted in multiple places and/or where the text template and/or the expressions are long so that I can break the whole thing over multiple lines, e.g.
Dim str = String.Format("some text{0}some more text{1}yet more text{0}",
someVar,
someOtherVar)
I have no idea what NewWord.db is so I made a class to represent it.
Public Class NewWord
Public Shared Property db As String = "The db Name"
End Class
HowMany is not a very good name for your sub. Try to use more descriptive names.
The first sub doesn't even use the connection. The connection string in that code is a literal string. It will not consider NewWord.db as a variable. You will not notice this because you never attempt to open the connection. In my version you check the connection string with a Debug.Print.
I changed the last line to use and interpolated string. It is not necessary to call .ToString on tot.
Private Sub DisplayGridCount()
Dim conn As New SQLiteConnection("Data Source ='{NewWord.db}';Version=3;")
Debug.Print(conn.ConnectionString)
Dim tot = DataGridView1.RowCount
TextBox1.Text = $"DGV has {tot} Rows"
End Sub
The second snippet starts off with 2 unused variables. I deleted them. Again, the Debug.Print to show the difference in the 2 strings.
Private Sub TestConnection()
Using conn As New SQLiteConnection($"Data Source = '{NewWord.db}' ;Version=3;")
Debug.Print(conn.ConnectionString)
'conn.Open()
End Using
End Sub
As to where to store connection strings see https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/protecting-connection-information and Where to store Connection String
I tried to read the Report.PrtDevMode property using the appropriate structures. If I do a Unicode conversion on the string I get closer, but the values still don't seem correct.
I expect to see intOrientation = 2 and strDeviceName = "C552 Color" and a paper size of 11x17.
I am testing on Windows 10 and Server 2008 with Microsoft Access 2010 (32-bit)
What I have Tried
A simplified copy and paste from the help file:
Private Type str_DEVMODE
RGB As String * 94
End Type
Private Type type_DEVMODE
strDeviceName As String * 32
intSpecVersion As Integer
intDriverVersion As Integer
intSize As Integer
intDriverExtra As Integer
lngFields As Long
intOrientation As Integer
intPaperSize As Integer
intPaperLength As Integer
intPaperWidth As Integer
intScale As Integer
intCopies As Integer
intDefaultSource As Integer
intPrintQuality As Integer
intColor As Integer
intDuplex As Integer
intResolution As Integer
intTTOption As Integer
intCollate As Integer
strFormName As String * 32
lngPad As Long
lngBits As Long
lngPW As Long
lngPH As Long
lngDFI As Long
lngDFr As Long
End Type
Public Sub CheckCustomPage()
Dim DevString As str_DEVMODE
Dim DM As type_DEVMODE
Dim strDevModeExtra As String
Dim rpt As Report
Dim intResponse As Integer
' Opens report in Design view.
DoCmd.OpenReport "rptNavigationPaneGroups", acDesign
Set rpt = Reports("rptNavigationPaneGroups")
If Not IsNull(rpt.PrtDevMode) Then
strDevModeExtra = rpt.PrtDevMode
' Gets current DEVMODE structure.
' (I added the StrConv function)
DevString.RGB = StrConv(strDevModeExtra, vbUnicode)
LSet DM = DevString
' List the device name.
If DM.strDeviceName <> rpt.Printer.DeviceName Then
Debug.Print "Found: '" & DM.strDeviceName & _
"' instead of '" & rpt.Printer.DeviceName & "'"
End If
End If
Set rpt = Nothing
End Sub
If I use the vbUnicode conversion, I get this output:
Found: 'C552 Color ”-é/ '
instead of 'C552 Color'
Without the conversion, the name is completely unreadable:
Found: '????? ?? A?A?????? ?'
instead of 'C552 Color
I reviewed numerous articles, documentation, and posts describing the usage of the .PrtDevMode property and associated structure.
I compared the structure type with examples in the help file, online documentation, and other sources, but I don't find any examples that involve Unicode conversion, which seems like it might be a piece of the puzzle.
I could use the .Printer object to retrieve many of these properties, but I understand that some settings like the Media Type are only available through the PrtDevMode structure (which is designed to mirror the Win32 SDK).
I could resort to using API calls to query the system printers, but this doesn't solve the problem of needing to write the structure back to the report print settings.
Using this structure would also help me to serialize this data in a readable format so that it can be stored in version control, and written back to the report after the database is rebuilt from source files (which is my ultimate goal).
Any non-null pointers in the right direction?
After further testing, I was able to get it working this morning. As I suspected, I was close on the Unicode conversion, but there were three important modifications that made the difference.
First, I needed to declare the two strings as byte arrays, not simply string buffers. While the string buffer works with the winspool.drv API calls, reading the Report.PrtDevMode property works better with a byte array.
Private Type type_DEVMODE
strDeviceName(1 To 32) As Byte ' <--- Byte array
'strDeviceName As String * 32
intSpecVersion As Integer
intDriverVersion As Integer
intSize As Integer
...
The second part was that the conversion from Unicode needs to be performed only on the string values, not the entire structure.
' Read the string values from DevMode structure
strDevice = NTrim(StrConv(dm.strDeviceName, vbUnicode))
strForm = NTrim(StrConv(dm.strFormName, vbUnicode))
Thirdly, the string is a null-terminated string, so it needs to be trimmed at the null character after the conversion from Unicode.
'---------------------------------------------------------------------------------------
' Procedure : NTrim
' Author : Adam Waller
' Date : 5/15/2020
' Purpose : Trim a null-terminated string.
'---------------------------------------------------------------------------------------
'
Public Function NTrim(strText) As String
Dim lngPos As Long
lngPos = InStr(1, strText, vbNullChar)
If lngPos > 0 Then
NTrim = Left$(strText, lngPos - 1)
Else
NTrim = strText
End If
End Function
With these three changes, I am now able to read the .PrtDevMode property with the expected values. Hopefully this will be helpful for someone else out there as well!
If you find that it works differently on your system, or have additional input, please feel free to leave a comment!
Hi Guys, I have searched for different methods for this thing but couldn't get it right. Used AutoEllipses Property, Set the MaximumSize too but to no avail. How can i get a label to show the name of the file being scanned like in the pic i attached? I mean, the label should show some part from the beginning of full path of the file then some dots and then the file name with extension.
You might consider a few things; however, the range of possibilities is too large to cover, here.
There are three (3) things you need to know in order to code this properly: the actual measured size of the filepath string you are sending to the label; the measured size of the label; and the length (in characters) of your file name. There may be a fancy function that reduces the number of things you need to do and know; however, I am not about to read oodles of documentation.
All of the above things need to be dynamic so that your label can take different String objects and render them, properly.
Dim filePath As String = ""
Dim FileDirectory As String = ""
Dim fileName As String = ""
Dim filePathLength As SizeF = 0.0
Dim labelLength As Double = 0.0
Dim fileNameLength As Integer = 0.0
' Come up with a way for measuring your string:
Dim _GraphicsUnit As Graphics = Me.CreateGraphics()
' Receive your file path, here:
' and work with your file path-related Strings:
filePath = ' SOMETHING
fileDirectory = Path.GetDirectoryName(filePath)
fileName = Path.GetFileName(filePath)
fileNameLength = fileName.Length()
' Measure the length of you path
filePathLength = _GraphicsUnit.MeasureString(filePath, INSERTFONT) * _GraphicsUnit.Inches 'or other usable unit
If filePathLength > SIZEOFLABEL Then
While filePathLength > SIZEOFLABEL
' Grab a substring of the the fileDirecory, append the "...", and keep measuring until shorter
' than SIZEOFLABEL.
' Your algorithm will need to figure out how and when to re-append the fileName
End While
End If
The above is pseudo-code and is rife with errors. The above is means to demonstrate some of the tools .Net can provide you, here namely the GraphicsUnit stuff and the Path. stuff. Both of those are helpful. You will essentially be juggling those two 'things' and the SubString() method.
My attempt is to show you how to begin to think about the problem you have in front of you so that you can begin to tackle the problem (because as the comments above state, there isn't much out there that will do what you need). Your initial question does not provide any original code on which to base the above pseudo-code; in other words, I don't feel like coding your whole project but at least want to get the answers ball rolling.
An Additional Thought: .MaxLength
The above approach is quite memory intensive - requiring a lot of repetition that may not be be necessary. Simply knowing the size - in this case the MaxLength property - might be helpful. Setting the .MaxLength property of the TextBox will allow you to know how many characters can fit in the box (you'd need to consider a few other elements, e.g. font, size, etc.).
Knowing this number, you could avoid looping altogether:
SubString of fileDirectory equal to the length of .MaxLength property, remove number of characters equating to size of fileName and "..." and append the latter two.
I got an answer to this problem here Shorten The File Path and it's a very short solution as far as code is concerned.
You can use the PathCompactPathExW pInvoke method to accomplish this:
Imports System.Runtime.InteropServices
Imports System.Text
Public Class Program
<DllImport("shlwapi.dll", EntryPoint:="PathCompactPathExW", SetLastError:=True, CharSet:=CharSet.Unicode)> _
Public Shared Function PathCompactPathEx(<MarshalAs(UnmanagedType.LPTStr)> pszOut As System.Text.StringBuilder, _
<MarshalAs(UnmanagedType.LPTStr)> pszSrc As String, _
cchMax As UInteger, _
reserved As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Public Shared Sub Main()
Dim longPath As String = "c:\a\very\very\long\path\that\needs\to\be\shortened\by\calling\the\PathCompactpathEx.ext"
Dim length As Integer = 40
Dim result As String = CompactPath(longPath, length)
'Prints c:\a\very\very\...\PathCompactpathEx.ext
Console.WriteLine(result)
Console.ReadLine()
End Sub
Public Shared Function CompactPath(longPathName As String, wantedLength As Integer) As String
'NOTE: You need to create the builder with the required capacity before calling function.
'See http://msdn.microsoft.com/en-us/library/aa446536.aspx
Dim sb As New StringBuilder(wantedLength + 1)
PathCompactPathEx(sb, longPathName, CUInt(wantedLength + 1), 0)
Return sb.ToString()
End Function
End Class
In VBA and VB6 I can assign something to mid for Example mid(str,1,1)="A" in VBS this doesn't work.
I need this because String concatenation is freakin' slow
Here is the actual code i hacked together real quick
Function fastXMLencode(str)
Dim strlen
strlen = Len(str)
Dim buf
Dim varptr
Dim i
Dim j
Dim charlen
varptr = 1
buf = Space(strlen * 7)
Dim char
For i = 1 To strlen
char = CStr(Asc(Mid(str, i, 1)))
charlen = Len(char)
Mid(buf, varptr, 2) = "&#"
varptr = varptr + 2
Mid(buf, varptr, charlen) = char
varptr = varptr + charlen
Mid(buf, varptr, 1) = ";"
varptr = varptr + 1
Next
fastXMLencode = Trim(buf)
End Function
How can i get this to work in VBS?
Authoritative source explicitly stating it's not available in VBScript:
Visual Basic for Applications Features Not In VBScript:
Strings: Fixed-length strings LSet, RSet Mid Statement StrConv
VBA has both a Mid Statement and a Mid Function. VBScript only has the Mid Function.
The one other option you have if you are stuck doing this in VBScript is to make API calls. Since you are already comfortable working directly with the string buffer this might not be too big a jump for you. This page should get you started: String Functions
Sorry, it looks like API calls are out, too: Rube Goldberg Memorial Scripting Page: Direct API Calls Unless you want to write an ActiveX wrapper for your calls, but we're starting to get into an awful lot of work (and additional maintenance requirements) now.
You can't do that. You will have to rebuild the string from scratch.
This is not possible, mid(str,1,1) is a function which just returns a number (str is not passed 'by reference' and is not altered).
It's never too late to suggest an answer, even to a decade-old question...
One great thing about VBScript is that even though it's a subset of VB/VBA it still lets you declare (and use) classes. And having classes implies them having properties, with getters and/or setters... if you can guess where I'm going...
So if you wrap a class around a VBA-compatible implementation of Mid(), it's actually possible to emulate Mid() statements with a Let property. Consider the following code:
Class CVBACompat
' VB6/VBA-Like Mid() Statement
Public Property Let LetMid(ByRef Expression, ByVal StartPos, ByVal Length, ByRef NewValue) ' ( As String, As Long, As Long, As String)
Dim sInsert ' As String
sInsert = Mid(NewValue, 1, Length)
Expression = Mid(Expression, 1, StartPos - 1) & sInsert & Mid(Expression, StartPos + Len(sInsert))
End Property
' VB6/VBA-Like IIf() Function
Public Function IIf(Expression, TruePart, FalsePart)
If CBool(Expression) Then
IIf = TruePart
Else
IIf = FalsePart
End If
End Function
End Class: Public VBACompat: Set VBACompat = New CVBACompat: Public Function IIf(X, Y, Z): IIf = VBACompat.IIf(X, Y, Z): End Function
VBA's language specification says that when Mid( <string-expression>, <start> [, <length> ] ) is used as a statement, what's to be replaced in the source string <string-expression> is the lowest number of characters given the entire RHS expression or as limited by the <lenght> argument, when provided. Now, you can't have optional arguments in VBScript, so if you want to be able to also use statements of the sort Mid( <string-expression>, <start> ) = <expression> you'll have to have two separate implementations.
The implementation proposed above can be checked with the following code (not including checks for error-throwing conditions, which are the same as for the Mid() function anyway) :
Dim f ' As String
Dim g ' As String
f = "1234567890"
g = f
' VB6 / VBA: Mid(f, 5, 2) = "abc"
VBACompat.LetMid(f, 5, 2) = "abc"
' VB6 / VBA: Mid(g, 5, 4) = "abc"
VBACompat.LetMid(g, 5, 4) = "abc"
WScript.Echo "First call " & IIf(f = "1234ab7890", "passes", "fails")
WScript.Echo "Second call " & IIf(g = "1234abc890", "passes", "fails")
In any case, since the proposed solution is just an emulation of otherwise native code implementations (in VB6, VBA), and still implies string concatenation anyway with the overhead of class instantiation + VTable translations, the OP'd be better off creating and making available an ActiveX component (OCX) to do concatenations in series.
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
Which features of the VBA language are either poorly documented, or simply not often used?
This trick only works in Access VBA, Excel and others won't allow it. But you can make a Standard Module hidden from the object browser by prefixing the Module name with an underscore. The module will then only be visible if you change the object browser to show hidden objects.
This trick works with Enums in all vb6 based version of VBA. You can create a hidden member of an Enum by encasing it's name in brackets, then prefixing it with an underscore. Example:
Public Enum MyEnum
meDefault = 0
meThing1 = 1
meThing2 = 2
meThing3 = 3
[_Min] = meDefault
[_Max] = meThing3
End Enum
Public Function IsValidOption(ByVal myOption As MyEnum) As Boolean
If myOption >= MyEnum.[_Min] Then IsValidOption myOption <= MyEnum.[_Max]
End Function
In Excel-VBA you can reference cells by enclosing them in brackets, the brackets also function as an evaluate command allowing you to evaluate formula syntax:
Public Sub Example()
[A1] = "Foo"
MsgBox [VLOOKUP(A1,A1,1,0)]
End Sub
Also you can pass around raw data without using MemCopy (RtlMoveMemory) by combining LSet with User Defined Types of the same size:
Public Sub Example()
Dim b() As Byte
b = LongToByteArray(8675309)
MsgBox b(1)
End Sub
Private Function LongToByteArray(ByVal value As Long) As Byte()
Dim tl As TypedLong
Dim bl As ByteLong
tl.value = value
LSet bl = tl
LongToByteArray = bl.value
End Function
Octal & Hex Literals are actually unsigned types, these will both output -32768:
Public Sub Example()
Debug.Print &H8000
Debug.Print &O100000
End Sub
As mentioned, passing a variable inside parenthesis causes it to be passed ByVal:
Sub PredictTheOutput()
Dim i&, j&, k&
i = 10: j = i: k = i
MySub (i)
MySub j
MySub k + 20
MsgBox Join(Array(i, j, k), vbNewLine), vbQuestion, "Did You Get It Right?"
End Sub
Public Sub MySub(ByRef foo As Long)
foo = 5
End Sub
You can assign a string directly into a byte array and vice-versa:
Public Sub Example()
Dim myString As String
Dim myBytArr() As Byte
myBytArr = "I am a string."
myString = myBytArr
MsgBox myString
End Sub
"Mid" is also an operator. Using it you overwrite specific portions of strings without VBA's notoriously slow string concatenation:
Public Sub Example1()
''// This takes about 47% of time Example2 does:
Dim myString As String
myString = "I liek pie."
Mid(myString, 5, 2) = "ke"
Mid(myString, 11, 1) = "!"
MsgBox myString
End Sub
Public Sub Example2()
Dim myString As String
myString = "I liek pie."
myString = "I li" & "ke" & " pie" & "!"
MsgBox myString
End Sub
There is an important but almost always missed feature of the Mid() statement. That is where Mid() appears on the left hand side of an assignment as opposed to the Mid() function that appears in the right hand side or in an expression.
The rule is that if the if the target string is not a string literal, and this is the only reference to the target string, and the length of segment being inserted matches the length of the segment being replaced, then the string will be treated as mutable for the operation.
What does that mean? It means that if your building up a large report or a huge list of strings into a single string value, then exploiting this will make your string processing much faster.
Here is a simple class that benefits from this. It gives your VBA the same StringBuilder capability that .Net has.
' Class: StringBuilder
Option Explicit
Private Const initialLength As Long = 32
Private totalLength As Long ' Length of the buffer
Private curLength As Long ' Length of the string value within the buffer
Private buffer As String ' The buffer
Private Sub Class_Initialize()
' We set the buffer up to it's initial size and the string value ""
totalLength = initialLength
buffer = Space(totalLength)
curLength = 0
End Sub
Public Sub Append(Text As String)
Dim incLen As Long ' The length that the value will be increased by
Dim newLen As Long ' The length of the value after being appended
incLen = Len(Text)
newLen = curLength + incLen
' Will the new value fit in the remaining free space within the current buffer
If newLen <= totalLength Then
' Buffer has room so just insert the new value
Mid(buffer, curLength + 1, incLen) = Text
Else
' Buffer does not have enough room so
' first calculate the new buffer size by doubling until its big enough
' then build the new buffer
While totalLength < newLen
totalLength = totalLength + totalLength
Wend
buffer = Left(buffer, curLength) & Text & Space(totalLength - newLen)
End If
curLength = newLen
End Sub
Public Property Get Length() As Integer
Length = curLength
End Property
Public Property Get Text() As String
Text = Left(buffer, curLength)
End Property
Public Sub Clear()
totalLength = initialLength
buffer = Space(totalLength)
curLength = 0
End Sub
And here is an example on how to use it:
Dim i As Long
Dim sb As StringBuilder
Dim result As String
Set sb = New StringBuilder
For i = 1 to 100000
sb.Append CStr( i)
Next i
result = sb.Text
VBA itself seems to be a hidden feature. Folks I know who've used Office products for years have no idea it's even a part of the suite.
I've posted this on multiple questions here, but the Object Browser is my secret weapon. If I need to ninja code something real quick, but am not familiar with the dll's, Object Browser saves my life. It makes it much easier to learn the class structures than MSDN.
The Locals Window is great for debugging as well. Put a pause in your code and it will show you all the variables, their names, and their current values and types within the current namespace.
And who could forget our good friend Immediate Window? Not only is it great for Debug.Print standard output, but you can enter in commands into it as well. Need to know what VariableX is?
?VariableX
Need to know what color that cell is?
?Application.ActiveCell.Interior.Color
In fact all those windows are great tools to be productive with VBA.
It's not a feature, but a thing I have seen wrong so many times in VBA (and VB6): Parenthesis added on method calls where it will change semantics:
Sub Foo()
Dim str As String
str = "Hello"
Bar (str)
Debug.Print str 'prints "Hello" because str is evaluated and a copy is passed
Bar str 'or Call Bar(str)
Debug.Print str 'prints "Hello World"
End Sub
Sub Bar(ByRef param As String)
param = param + " World"
End Sub
Hidden Features
Although it is "Basic", you can use OOP - classes and objects
You can make API calls
Possibly the least documented features in VBA are those you can only expose by selecting "Show Hidden Members" on the VBA Object Browser. Hidden members are those functions that are in VBA, but are unsupported. You can use them, but microsoft might eliminate them at any time. None of them has any documentation provided, but you can find some on the web. Possibly the most talked about of these hidden features provides access to pointers in VBA. For a decent writeup, check out; Not So Lightweight - Shlwapi.dll
Documented, but perhaps more obscure (in excel anyways) is using ExecuteExcel4Macro to access a hidden global namespace that belongs to the entire Excel application instance as opposed to a specific workbook.
You can implement interfaces with the Implements keyword.
Dictionaries. VBA is practically worthless without them!
Reference the Microsoft Scripting Runtime, use Scripting.Dictionary for any sufficiently complicated task, and live happily ever after.
The Scripting Runtime also gives you the FileSystemObject, which also comes highly recommended.
Start here, then dig around a bit...
http://msdn.microsoft.com/en-us/library/aa164509%28office.10%29.aspx
Typing VBA. will bring up an intellisense listing of all the built-in functions and constants.
With a little work, you can iterate over custom collections like this:
' Write some text in Word first.'
Sub test()
Dim c As New clsMyCollection
c.AddItems ActiveDocument.Characters(1), _
ActiveDocument.Characters(2), _
ActiveDocument.Characters(3), _
ActiveDocument.Characters(4)
Dim el As Range
For Each el In c
Debug.Print el.Text
Next
Set c = Nothing
End Sub
Your custom collection code (in a class called clsMyCollection):
Option Explicit
Dim m_myCollection As Collection
Public Property Get NewEnum() As IUnknown
' This property allows you to enumerate
' this collection with the For...Each syntax
' Put the following line in the exported module
' file (.cls)!'
'Attribute NewEnum.VB_UserMemId = -4
Set NewEnum = m_myCollection.[_NewEnum]
End Property
Public Sub AddItems(ParamArray items() As Variant)
Dim i As Variant
On Error Resume Next
For Each i In items
m_myCollection.Add i
Next
On Error GoTo 0
End Sub
Private Sub Class_Initialize()
Set m_myCollection = New Collection
End Sub
Save 4 whole keystrokes by typing debug.? xxx instead of debug.print xxx.
Crash it by adding: enum foo: me=0: end enum to the top of a module containing any other code.
Support for localized versions, which (at least in the previous century) supported expressions using localized values. Like Pravda for True and Fałszywy (not too sure, but at least it did have the funny L) for False in Polish... Actually the English version would be able to read macros in any language, and convert on the fly. Other localized versions would not handle that though.
FAIL.
The VBE (Visual Basic Extensibility) object model is a lesser known and/or under-utilized feature. It lets you write VBA code to manipulate VBA code, modules and projects. I once wrote an Excel project that would assemble other Excel projects from a group of module files.
The object model also works from VBScript and HTAs. I wrote an HTA at one time to help me keep track of a large number of Word, Excel and Access projects. Many of the projects would use common code modules, and it was easy for modules to "grow" in one system and then need to be migrated to other systems. My HTA would allow me to export all modules in a project, compare them to versions in a common folder and merge updated routines (using BeyondCompare), then reimport the updated modules.
The VBE object model works slightly differently between Word, Excel and Access, and unfortunately doesn't work with Outlook at all, but still provides a great capability for managing code.
IsDate("13.50") returns True but IsDate("12.25.2010") returns False
This is because IsDate could be more precisely named IsDateTime. And because the period (.) is treated as a time separator and not a date separator. See here for a full explanation.
VBA supports bitwise operators for comparing the binary digits (bits) of two values. For example, the expression 4 And 7 evaluates the bit values of 4 (0100) and 7 (0111) and returns 4 (the bit that is on in both numbers.) Similarly the expression 4 Or 8 evaluates the bit values in 4 (0100) and 8 (1000) and returns 12 (1100), i.e. the bits where either one is true.
Unfortunately, the bitwise operators have the same names at the logical comparison operators: And, Eqv, Imp, Not, Or, and Xor. This can lead to ambiguities, and even contradictory results.
As an example, open the Immediate Window (Ctrl+G) and enter:
? (2 And 4)
This returns zero, since there are no bits in common between 2 (0010) and 4 (0100).
Deftype Statements
This feature exists presumably for backwards-compatibility. Or to write hopelessly obfuscated spaghetti code. Your pick.