How does the Left() function work with the GetWindowsDirectory API when passed a fixed-length string? - excel

I could not make my title both descriptive & concise. I am reading through a book called Power Programming with VBA, and one of the examples uses a Windows API called GetWindowsDirectoryA, which takes the string lpBuffer & long nSize parameters. Once it is declared, the author uses it in a subroutine to retrieve the Windows directory.
What confuses me is the way in which this happens. Here is the code:
#If VBA7 And Win64 Then
Declare PtrSafe Function GetWindowsDirectoryA Lib "kernel32" _
(ByVal lpBuffer As String, ByVal nSize As Long) As Long
#Else
Declare Function GetWindowsDirectoryA Lib "kernel32" _
(ByVal lpBuffer As String, ByVal nSize As Long) As Long
#End If
Sub ShowWindowsDir()
Dim WinPath As String * 255 'Why does this work even if I make it a regular variant string?
Dim WinDir As String
WinPath = Space(255) 'Also works fine if I change this number or comment it out, though if I comment it out, the symbols remain
WinDir = Left(WinPath, GetWindowsDirectoryA(WinPath, Len(WinPath)))
MsgBox WinDir, vbInformation, "Windows Directory"
End Sub
Nothing is wrong with how it works. When I watch it execute step-by-step in the editor, it seems to somehow perform an extra step that isn't obvious to me.
When WinPath is declared it is an empty string with a capacity of 255 characters. I am not sure why this is necessary, since removing the fixed-length nature of the string still works fine.
WinPath is filled with 255 spaces for unknown reasons. It works just as well without this.
Here is my paradox. We are passing WinPath as an argument to Left, but WinPath has only 255 spaces in it, & the second part of Left normally tells it to truncate the 1st parameter to X number of spaces. How does providing GetWindowsDirectoryA(WinPath, Len(WinPath)) circumvent this normal logic, and suddenly produce the result: "C:\Windows"?
I feel like if I understand how this works, it will help me understand how the other APIs do business as well. Btw, the comments in the code are mine, not the author's.

Before a function may be called, all its parameters need to be evaluated. Until that happens, the function is not called yet.
Left has two parameters, and we provide an expression for the second parameter. That expression, GetWindowsDirectoryA(WinPath, Len(WinPath)), has to be evaluated in full before Left is called.
The expression has a side effect of modifying the contents of WinPath, so after GetWindowsDirectoryA() returns, WinPath contains new value.
Only then execution enters Left, and it gets a chance to read the arguments that have been passed to it.
Note that it would not even matter in this case if WinPath was actually pushed on the stack before GetWindowsDirectoryA was executed. If that was the case, it's the variable that is pushed, not its contents. So when Left would have been called, it would have read the WinPath variable from the stack and it would have anyway pointed to the modified data.
As for your concerns about the initial contents of WinPath, the buffer has to be allocated before you call GetWindowsDirectoryA, so you do need to fill it with some throwaway characters such as spaces or vbNullChars. You can do that by having the * 255 modifier or by calling = Space(255), doing both is redundant. Note however that the buffer should be MAX_PATH characters long, which is 260.

When the function is executed, the parameter list needs to be in positive order when it pops up from the stack, so it needs to be in reverse order(from back to front) when loading, GetWindowsDirectoryA executes first, then WinPath gets a value, and then returns the value to the parameter list.
Here's a simple Win32 program.
#include <windows.h>
#include <iostream>
void f(int a, int b)
{
printf("a = %d\nb = %d\n", a, b);
}
int main()
{
int i = 1;
f(i, ++i);
getchar();
return 0;
}
result:
a = 2
b = 2
But not
a = 1
b = 2

Related

ByRef Argument Type Mismatch Excel VBA

I am using windows 7, Excel 2010, VBA. I am getting an error
"ByRef Argument Type Mismatch". I am assuming it is a problem with my variable type. I found lots of questions like mine but I can't find anything that has helped me figure out my problem.
Variable Declarations
'Force explicit variable declaration
Option Explicit
Private dptData(8) As String
Private TSdata(8) As String
Private fiscalYear(8) As String
Calling Function
parseUserData fiscalYear, dptData, TSdata
Called function Prototype
Function parseUserData(fiscalYear As String, dptDataAs String, TSdata As String)
You're passing an array to a String. Change the function's signature to accept a Variant instead.
Public Function parseUserData(fiscalYear As Variant, dptDataAs Variant, TSdata As Variant)
Debug.Assert IsArray(fiscalYear) And IsArray(dptDataAs) And IsArray(TSdata)
A String parameter can only ever accept a String argument1. Variant on the other hand, can accept anything - including an array - but then you'll want to Assert that you're dealing with an array, so that you halt execution (and prevent a bug) if that's not the case.
Why use a Variant over a typed array?
Using a typed array would work, but a typed array can't be coerced from a Variant parameter - which means this:
Public Sub DoSomething(ByRef args() As String)
...can't be invoked with this otherwise perfectly valid array of strings:
DoSomething Array("string1", "string2", "string3") ' can't pass a variant array!
Changing the signature to DoSomething(ByRef args As Variant) makes it work. All you need to do is to use a meaningful, descriptive, pluralized name to your variant array parameters, so that when IntelliSense is shown when you invoke that procedure, the name tells you everything you need to know.
But... 'Variant' is evil!
No different than many other languages, type safety in VBA is essentially smokes and mirrors. Variant is a very powerful tool and does have its uses - avoid it when you can doesn't mean unlearn its existence. Using it to pass array references around between procedures doesn't hurt the code's readability, maintainability, or stability. Variant enables duck typing and late-binding, and is a legitimate COM type.
It's a hammer. Just make sure not everything becomes a nail, and you'll do great.
1VBA will implicitly convert other value types to a String, but an array can't be coerced into a string, implicitly or explicitly.
It seems you wanted a string array after all but it may be worth mentioning that you can declare a fixed width string var.
Dim dptData As String * 8
dptData = "abc"
Debug.Print Len(dptData) & "|" & dptData & "|"
'result from Immediate window
'8|abc |
dptData = "abcdefghijk"
Debug.Print Len(dptData) & "|" & dptData & "|"
'result from Immediate window
'8|abcdefgh|

Converting Excel MAcro from 32 Bit to 64 Bit

I have a quite large excel macro written in 32bit and need to convert to use n excel 64 bit. Is there an easy way to do this conversion?
It is a large macro and not possible to convert line by line.
I have included part of the code below.
'32-bit API declarations
Declare Function SHGetPathFromIDList Lib "shell32.dll" _
Alias "SHGetPathFromIDListA" (ByVal pidl As Long, ByVal pszPath As String) As Long
Declare Function SHBrowseForFolder Lib "shell32.dll" _
Alias "SHBrowseForFolderA" (lpBrowseInfo As BROWSEINFO) As Long
Sub DisplayDirectoryDialogBox()
Dim Msg As String
FileCount = 0
Msg = "Select a location containing the files you want to list."
SelectedDir = GetDirectory(Msg)
If SelectedDir = "" Then End
With Application
.StatusBar = "WAIT..."
.ScreenUpdating = False
Credit to Gserg:
Manually add PtrSafe to all declarations. Manually convert all Longs in the declarations that represent pointers to LongPtrs (in the code shown, that would be only pidl). Manually update declarations of Long variables in procedures that are passed as these parameters to LongPtr. There's no other way. You may try to look into refactoring tools/addins that may automate the replacements to a degree. – GSerg Jul 18 at 8:55

BASIC: What does an exclamation mark at the end of a function name mean?

DECLARE FUNCTION AanUit! (Baan AS INTEGER, Aktie AS INTEGER)
DECLARE FUNCTION fVraagStatus! (Baan AS INTEGER)
DECLARE FUNCTION fMelding! (Tekst AS STRING, Warning AS INTEGER)
FUNCTION fVraagStatus (VraagBaan AS INTEGER)
´ retrieve status somewhere
fVraagStatus = False or True
END FUNCTION
FUNCTION fMelding (Tekst AS STRING, Warning AS INTEGER)
´ locate (move cursor), print stuff
END FUNCTION
My Question: What does an exclamation mark at the end of a function name mean?
I am rewritting an old program to .net and came across something weird and i want to understand why there is a '!' at the end of the function decleration. I don't really need to write my own code, it's Just for my peace of mind.
It means the function returns a SINGLE. Exclamation point is a shortcut for As Single.

How do you pass a VB string argument to a function written in C?

I have a problem here: I'm trying to pass a VB6 string to a function written in C, but I think it is different from LPSTR. When the function is called, my VB6 IDE crashes. How do I pass a VB string as an argument to a function in C? Below is my code. Thanks to all:
VB6
Private Declare Function WritestStr Lib “teststr.dll” (ByRef mystr As String) As Long
Private Sub command1_Click()
Dim mystr as string
Call WritestStr(mystr)
Msgbox mystr
End Sub
VC6
include “windows.h”
Int __stdcall WritestStr(LPSTR *mystr)
{
*mystr = “Venancio Guedes”;
return 0;
}
It's been eons since I wrote VB/Win32 function declarations, but I'm fairly sure I remember that by default all parameters to an external library function in VB6 are passed byref; however, the VB6 String type is already a pointer-based type, so a byref parameter that is already passed byref pushes the wrong value onto the stack. That causes the reference to crash inside the DLL. Try passing it ByVal, instead, because the "value" of the parameter is really the string pointer itself, which is what the DLL function expects.
As I said, its been a looong time since I wrote these kinds of declarations, so all standard caveats apply, but I'm pretty sure that's close.
Good luck!
EDIT The ByVal declaration for the DLL function is correct. The fix now should be for you to initialize/allocate the string before the call to the DLL function with spaces, eg:
Dim vbString as String
Dim result as Long
vbString = Space$(255) ' just make sure this number is large enough
result = WritestStr(vbString)
VB strings are called BSTR in the OLE documentation, and is almost compatible with LPWSTR. They are null terminated 2 byte per character Unicode (UTF-16) strings, but with a 32 bit length immediately before the memory the string pointer points to.
Your code uses LPSTR*, which is a pointer to a pointer to a 1 byte per character ANSI string. Obviously, you are doing this so as to return your string to the VB6 code.
Unfortunately, these two are incompatible.
The reason why the code crashes is that you are passing the VB6 variable <mystr> to your function, but by default it is set to vbNullString, which is like:
BSTR mystr = NULL;
But your main problem is that VB cannot possibly use your C function as written. There is no way of writing a Declare statement for LPSTR*. If you changed your C code to
include “windows.h”
Int __stdcall WritestStr(LPSTR mystr)
{
const LPSTR myconststr = “Venancio Guedes”;
if (mystr)
int destlen = strlen(mystr);
int srclen = strlen(myconststr);
if (destlen >= srclen)
{
strcpy(mystr, myconststr);
return 0;
}
return srclen;
}
... you could change the declare to:
Private Declare Function WritestStr Lib “teststr.dll” (ByVal mystr As String) As Long
... and ensure you declare a buffer to accept the string.
You could write a Declare statement for LPSTR, but you need to
Private Sub command1_Click()
Dim mystr As string
Dim nLen As Long
mystr = Space$(1024)
nLen = WritestStr(mystr)
Msgbox Left$(mystr, nLen)
End Sub
This is very reminiscent of how most Win32 API functions work.
Passing mystr as ByVal informs VB6 that it must copy <mystr> from BSTR to a temporary LPSTR, and pass a pointer to that buffer. When it has finished executing WriteStr() it then copies the LPSTR buffer back to the original BSTR.
Allocating a VB string buffer <mystr> to pass to your function gives you something to write back into.
Alternatively, you could rewrite your C program to accept a BSTR natively (the cleanest and more portable solution if you want to have mult-languages). In which case your original VB6 declaration would stand i.e. ByRef mystr As String. Unfortunately, you would still have to write into a buffer like you are doing here.
If you want to have LPWSTR*, LPSTR* or BSTR* you will have to declare your function in a type library - something I don't have the time here to talk about.

Out of String Space in Visual Basic 6

We are getting an error in a VB6 application that sends data back and forth over TCP sockets. We get a runtime error "out of string space". Has anyone seen this or have any thoughts on why this would happen? It seems like we are hitting some VB6 threshhold so any other thoughts would be helpful as well.
As others have pointed out, every string concatenation in VB will allocate a new string and then copy the data over and then de-allocate the original once it can. In a loop this can cause issues.
To work around this you can create a simple StringBuilder class like this one:
Option Explicit
Private data As String
Private allocLen As Long
Private currentPos As Long
Public Function Text() As String
Text = Left(data, currentPos)
End Function
Public Function Length() As Long
Length = currentPos
End Function
Public Sub Add(s As String)
Dim newLen As Long
newLen = Len(s)
If ((currentPos + newLen) > allocLen) Then
data = data & Space((currentPos + newLen))
allocLen = Len(data)
End If
Mid(data, currentPos + 1, newLen) = s
currentPos = currentPos + newLen
End Sub
Private Sub Class_Initialize()
data = Space(10240)
allocLen = Len(data)
currentPos = 1
End Sub
This class will minimize the number of string allocations by forcing the string to be built with spaces in it and then overwriting the spaces as needed. It re-allocates to roughly double its size when it finds that it does not have enough space pre-initialized. The Text method will return the portion of the string that is actually used.
Text found on MSDN:
http://msdn.microsoft.com/en-us/library/aa264524(VS.60).aspx
Visual Basic for Applications
Reference Out of string space (Error
14)
Specifics
Visual Basic permits you to use very
large strings. However, the
requirements of other programs and the
way you manipulate your strings may
cause this error. This error has the
following causes and solutions:
Expressions requiring that temporary strings be created for
evaluation may cause this error. For
example, the following code causes an
Out of string space error on some
operating systems:
MyString = "Hello"
For Count = 1 To 100
MyString = MyString & MyString
Next Count
Assign the string to a variable of another name.
* Your system may have run out of memory, which prevented a string from
being allocated.
Remove any unnecessary applications from memory to create
more space.
For additional information, select the
item in question and press F1.
Adding to Jacco's response, vbAccelerator has a great String Builder class that accomplishes much the same thing but is a little more robust. The author also walks through the solution explaining how it works.
Assuming that you are appending data in a loop, ensure that it's not being appended to itself, which will eat memory extremely quickly.
Example and description of error meaning:
http://msdn.microsoft.com/en-us/library/aa264524.aspx
Sometime in the spring of 2009, Microsoft did an XP update that interferes with Armadillo/Silicon Realms wrapper.
The line of code that was throwing error 14, Out of String space was not logical. There was no problem with a over sized string. It was a simple assignment that I even changed to be "foo" and error 14 still occurred. I think the error is mapped incorrectly in XP.
The answer for us was to remove copyMem-11 from the Armadillo protection project and rewrap the the exe.
It sounds like you are appending a string often. You could try using a StringBuilder class
Also, it could be you have some stale objects that contain strings hanging around that aren't being used and should be freed. Check for circular references perhaps by logging object allocation/frees in Class_Initialize/Class_Finalize

Resources