I was wondering if anyone knew how to define and call pointers to functions/subs in VB like we used to do in C? I tried to use the AddressOf opperator, but I can't seem to get it to work. I attempted a simple test: When a command button is clicked, it calls a function by pointer...
I tried various arangements, but the closest I came was -
[In a form (with 1 text box and one command button...) -]
Private Sub Command1_Click()
Dim FnPtr As Long
FnPtr = GetFnPtr(AddressOf testfunction)
Call FnPtr '<-Didn't work; gave error
End Sub
[In a module -]
'Function wrapper?
Public Sub testfunction(testval As Integer)
Form1.Text1.Text = "DID IT!"
End Sub
'Assign address
Public Function GetFnPtr(ByVal ptr As Long) As Long
GetFnPtr = ptr
End Function
The AddressOf keyword was added to VB5 so we could call API functions that uses callbacks, like EnumWindows. But according to the MSDN:
Pointers to Visual Basic functions cannot be passed within Visual Basic itself. Currently, only pointers from Visual Basic to a DLL function are supported.
When it comes to the DLL part it states:
You can create your own call-back function prototypes in DLLs compiled with Visual C++ (or similar tools). To work with AddressOf, your prototype must use the __stdcall calling convention. The default calling convention (_cdecl) will not work with AddressOf.
Basically it doesn't work with ActiveX DLLs that you create in VB.
However Public functions/subs declared in a Form or a Class module can be called using the CallByName function.
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
All of those return the address of a storage, and you could use those together with CopyMemory but fact remains that you can't call a function from within VB using a function address.
I played around with the thought that what if there actually was a way to call a function by using the address in VB how would that look? Well Microsoft claims that it's impossible so there is obviously no native way of doing it. So I started thinking if there was any API function at all that takes an address to call a function and then it hit me... Hey, I've subclassed more Forms then I can count and I always makes a call to CallWindowProc when I do so. That's of course used to call the origional WndProc but how does Window know where that function resides... Simply because the first argument is the address to that function.
So can Windows really know if there actually is a WndProc at that memory location? No, I thought it can of course not know if I use it as a message pump or not so I tried it out and you can actually call any function by address using this approach!
The only drawback is the number of arguments. A WndProc has 4 arguments and CallWindowProc put these on the stack so your function have to pull them of there. So the function you want to call must have exactly 4 arguments, even if it doesn't use them. The great thing is that these arguments can be of any type as long as it's acceptable to pass the values ByRef. Arguments of the type Long can be passed either ByRef or ByVal but all other types or objects must be called ByRef since you would then pass a pointer to them.
Well there is one other drawback... Callback procedures can only reside in regular BAS modules not in Form or Class modules.
Let's try this out... It's FUN!!!! Create a new project and add a BAS module to it. Copy and paste the following code:
VB Code:
Private Declare Function CallWindowProc _
Lib "user32.dll" Alias "CallWindowProcA" ( _
ByVal lpPrevWndFunc As Long, _
ByVal hwnd As Long, _
ByVal msg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Private Sub ShowMessage( _
msg As String, _
ByVal nUnused1 As Long, _
ByVal nUnused2 As Long, _
ByVal nUnused3 As Long)
'This is the Sub we will call by address
'it only use one argument but we need to pull the others
'from the stack, so they are just declared as Long values
MsgBox msg
End Sub
Private Function ProcPtr(ByVal nAddress As Long) As Long
'Just return the address we just got
ProcPtr = nAddress
End Function
Public Sub YouCantDoThisInVB()
Dim sMessage As String
Dim nSubAddress As Long
'This message will be passed to our Sub as an argument
sMessage = InputBox("Please input a short message")
Now just call the YouCantDoThisInVB() Sub, that's where the magic happens.
I admit that my example is just a very cumbersome way of showing a simple MsgBox, but just think of the potential new world that just opened. How we now actually can create generic sort classes in VB by using a callback that does the comparison instead of using Variant data types. Such things C programmers always have done.
You have to excuse me, I usually don't blow my own horn, but this was really COOL.
I had been compiling a C++ DLL to call functions using addresses, but of course that's a much easier way of doing it (although it essentially comes to the same thing in the end).
And also, to get around the 4 arguments limitations, you could pass a pointer to a SAFEARRAY containing your arguments list and read the data out of that. Obviously the method isn't suitable for common use, but for those occasions when you need it it's great.
Now if only we could find a way to get around the BAS module limitation... I know you can do it using machine code, so maybe there are ways to do that too.
Now if only we could find a way to get around the BAS module limitation... I know you can do it using machine code, so maybe there are ways to do that too.
The AddressOf keyword only works with procedures in modules. The reason MS implemented it this way is because normally callbacks are called from an API function that runs in a separate thread and as such they can't be used with classes or forms that's created by the main thread in VB.
Since besides the AddressOf keyword there isn't any other native way to get the address of a function I doubt you will find a way. However I don't think that is much of a limitation. The fact that the callback needs four arguments is not much of a limitation either. All callbacks have a determent signature that you must follow so that doesn't matter much. If you need more arguments you can simply do as the API functions does, pass the address of a UDT (struct).
The reason MS implemented it this way is because normally callbacks are called from an API function that runs in a separate thread and as such they can't be used with classes or forms that's created by the main thread in VB.
Why is that? Surely it is possible to get the base address of the class and then add the offset of the function? Or is it because VB shifts them around in memory, so they're not always in the same place?
A function in a class becomes a method of the object. Every new instance of the object will get it's own copy.
Yes but suppose you want a callback from a class module. Say you write a class to subclass a form (i.e. you use a property to attach the form to that class). You could do it by putting the WndProc in a .bas module, and in that module have a collection of pointers to classes, and with each callback call the method in the appropriate class. Long-winded, it works, but it's clumsy.
Now, wouldn't it be much easier if we could just stick the WndProc in the class module, and use something like AddressOf(Me.WndProc) from within that class? Surely each instance knows where its own functions are, otherwise the code wouldn't work at all because none of the calls would be resolved.
I don't have much knowledge of pointers. But I just know that memory is allocated to a variable only when it is used for the first time. But this seems to be something different.
I do this:
VB Code:
Private Sub Command1_Click()
Dim i As Long, s As String
For i = 1 To 10
Debug.Print StrPtr(CStr(i))
Next
End Sub
And get this result:
1804836
1961516
1804836
1961516
1804836
1961516
1804836
1961516
1804836
1961516
NO matter how many times I run this code, it seems to be working with only 2 memory locations. Shouldn't it be either a fixed location or random location?
Pradeep
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
Nope. The Integer is stored in the same location all the time but in the loop you're creating a new string each time using the CStr function. That string is stored in a new location each time. That VB can reuse the same memory location every other time just means that the compiler does something right .
If you want to see the storage of the integer you need to use code simular to this:
VB Code:
Private Sub Command1_Click()
Dim i As Long
For i = 1 To 10
Debug.Print VarPtr(i)
Next
End Sub
That would result in the same address all ten times, however it might not be the same each time the procedure runs, but if you just keep clicking on the button it probably will be.
No, what I was wondering with the StrPtr(CStr(i)) was that Shouldn't it be a different location every time. Why it is exactly 2 locations each time (not more, not less).
Pradeep
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
Simply because the compiler is somewhat smart. When you create the new string the other falls out of scope since there is no variable pointing to it. So that memory location can then be reused.
Simply because the compiler is somewhat smart. When you create the new string the other falls out of scope since there is no variable pointing to it. So that memory location can then be reused.
Can't it work with just one location, since there is no other memory allocation being done other than this line StrPtr(CStr(i)) ?
Pradeep
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
Oh No! I think I'm a bit confused.
It is reusing the same memory location (just one location).
I was under the impression that StrPtr returns the address of a STRING variable. Isn't his true?
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
It returns the address where a string is located. You create a string by calling the CStr function, the string returned by that function must be stored somewhere and it's in the location returned by StrPtr.
BTW, your last example still shows that the CStr function will alternate which address is used to store the memory location. In this case you just call it twice directly after each other but there is still two different addresses used. First after the string has fallen out of scope can the address be reused, which in this case will be in the next loop turned since you've already recreated a string out of the i variable.
It returns the address where a string is located. You create a string by calling the CStr function, the string returned by that function must be stored somewhere and it's in the location returned by StrPtr.
No it doesn't seem to be:
This thing:
VB Code:
Private Sub Command1_Click()
Dim i As Long, str1 As String
str1 = "SOMETHING" 'allocated it the memory
For i = 1 To 10
str1 = "SOMETHING"
Debug.Print VarPtr(str1), StrPtr(str1)
Next
End Sub
Result:
1241276 1747140
1241276 1804836
1241276 1747140
1241276 1804836
1241276 1747140
1241276 1804836
1241276 1747140
1241276 1804836
1241276 1747140
1241276 1804836
Obviously the string has been allocated memory before teh debug.print statement now. The StrPtr and VarPtr are just retrieving the values. But they seem to point to different memory locations for the same variable. Or the StrPtr function retrieves something else, not the address of this variable.
Pradeep
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
Well a VB string is stored as a BSTR, if you would use VarPtr on a VB String you would get the address of the BSTR, which is a pointer to a pointer of the string. The StrPtr however will get the address of the first Unicode character in the string.
The origional pointer (the pointer that points to the pointer that points to the characters, hmm... Pointers to Pointers are confusing) doesn't change. However if you assign a new value to the string a new string is created and the StrPtr in the string is changed to point to that location. The reason your StrPtr is changing is because you constantly give it a new value (which happens to be exactly the same text, but that string is recreated in every loop turn) inside the loop.
I drawed a simple image for you that probably explains this a lot better.
So I should never rely on StrPtr for address of any string. Instead I should use VarPtr to get address of any variable - whether string or not.
EDIT: Uuhh.. I cant rate ur post!! It says - You must spread some Reputation around before giving it to Joacim Andersson again.
Nevertheless this was very helpful.
Pradeep
Last edited by Pradeep1210; Jun 11th, 2005 at 03:30 PM.
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
No! If you want the address of the actual text you need to use StrPtr because that is where the text is stored. But assigning a new string (even if it's happens to be the same text) will of course change the pointer so it now points to where this text is stored in memory.
So it depends on if you want the address of the text or the address of the BSTR (the address that points to an address of the text).
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
Just to make this clear (for anyone else that might be reading this thread) I just want to give an example of when you would need to use StrPtr. VB internally stores any strings in Unicode format. However as soon as you pass a string to any API function VB will replace that with an Ansi string. So if you want to call an API function that requires a string like GetUserName or GetWindowsDirectory you would use the Ansi versions of those APIs instead of the wide (or Unicode) version of those functions.
So let's take the GetUserName function as an example. You would probable wrap up this call into a regular VB function that would return the user name. So it would probably look something like this:
VB Code:
Private Declare Function GetUserName _
Lib "advapi32.dll" Alias "GetUserNameA" ( _
ByVal lpBuffer As String, _
ByRef nSize As Long) As Long
Public Function CurrentUser() As String
Dim sBuff As String
'make sure this buffer string is big enough to hold
'the return value.
sBuff = Space$(256) 'or sBuff = String(256, vbNullChar)... it does not matter
So we have declared the ANSI version of GetUserName and we have also wrapped the call up in a public function called CurrentUser. Inside this function we've declared a string and also made sure that it is big enough to hold the returned value.
So what does VB do now? It will take our Unicode string, called sBuff, and replace that with an ANSI version of it. So it creates a new string before the GetUserName is called. It also have to turn the returned string back into a Unicode string again so we can return the result.
That means that we during this time have created no less then 5 strings. The first string is created as soon as we declare the sBuff variable. It will point to an empty string (you have to understand that an empty string is still a string). It then creates another string that is returned by our call to the Space function (so StrPtr is now shifted to the new string containing a lot of spaces). This was the second string. We now call the API function in which VB has to take our Unicode string (containing nothing but spaces) and create a new Ansi string (that would be our third string).
This string is now filled with the current user name (no new string have been created to do this, the memory location of the string have just been modified). VB now have to turned back this Ansi string into a Unicode string (this is the forth string that VB will create) and assign that back to sBuff.
The fifth string is created by our Left$ call that trims of the NULL value and the returned value of this string is then pushed on to the stack as the return value of our function.
Everyone that have used VB for a while knows that calling an API function is normally much faster then doing the "pure" VB way. This is normally true but what will VB do more then create all of those string above when we call the GetUserName API? For each and every API call we make VB will also make another API call. This call will be to the GetLastError API function. VB will always call this function after each and every API call you make and the return value will be used to set the Err.LastDLLError property.
You should never call the GetLastError function from VB, just check the LastDLLError of the Err object instead! The simple reason for this is that VB internally will make a lot of API calls so if you would call the GetLastError function you would not know if the returned value was because of an error you caused or because of an error caused by VB itself.
With that said, let's see how we can make a Unicode call to the GetUserName function that will be a lot faster since less strings have to be created. In this case you would use StrPtr to pass the pointer to the string (if you would use VarPtr here you would probably cause a GPF and VB + your app would crash and burn). Since StrPtr returns an address rather then the string itself you need to change the declaration of the function.
VB Code:
Private Declare Function GetUserName _
Lib "advapi32.dll" Alias "GetUserName[b][color=red]W[/color][/b]" ( _
ByVal lpBuffer As [b][color=red]Long[/color][/b], _
ByRef nSize As Long) As Long
Public Function CurrentUser() As String
Dim sBuff As String
'make sure this buffer string is big enough to hold
In this case you only pass a pointer (or a Long integer value) to the GetUserName function so there is no new strings created by VB. Since there is no conversion in the first place from a Unicode string to an Ansi string, VB doesn't have to do the opposite either so there is 2 strings here that never will be created.
Needless to say the above code will run much faster since it contains less string creations. So why don't we always use this approach in VB? The simple answer is that Win9x/ME expects the Ansi string and not a Unicode string. So if you develop for those platforms you can't use the above approach.
Before anyone else points this out I just want to make it clear that I'm fully aware that there is an error in my statement above about how VB creates the initial empty string when you declare a String variable. This error in my statement was intentional and the one that figures out what it is will win the grand price (what that is will be determent later ).
Anyway the whole idea of all this was to show that if you would use VarPtr instead of StrPtr in the Unicode call above your application would have crashed and burned since Windows would then have written the string (the user name in this example) to the wrong memory address.
A variation on the function pointer method using a UDT as parameter.
Put the following code in a module, drop a test button on a form and call Start from the click event.
Code:
Option Explicit
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Public Type MySubParams
ID As Long
Name As String
End Type
Sub MySub1(p As MySubParams, a As Long, b As Long, c As Long)
Debug.Print VarPtr(p)
MsgBox "MySub1 " & p.ID & " " & p.Name
End Sub
Sub MySub2(p As MySubParams, a As Long, b As Long, c As Long)
Debug.Print VarPtr(p)
MsgBox "MySub2 " & p.ID & " " & p.Name
End Sub
Function CallSub(address As Long, params As Long)
CallSub = CallWindowProc(address, params, 0&, 0&, 0&)
End Function
Sub Start()
Dim a As Long
Dim p As MySubParams
p.ID = 1
p.Name = "Test1"
Debug.Print VarPtr(p)
Debug.Print CallSub(AddressOf MySub1, VarPtr(p))
p.ID = 2
p.Name = "Test2"
Debug.Print CallSub(AddressOf MySub2, VarPtr(p))
End Sub