I am using this sample as a basis for a program I'm making. After approximately 618 keystrokes, the program throws this error:
CallbackOnCollectedDelegate was detected
Message: A callback was made on a garbage collected delegate of type 'KeyLogger!KeyLogger.CallBackFunction+DelegateCallBack::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.
The error is thrown most times the application is run, but not every time and not at the same keystroke count. From the error message, I think it sounds like the garbage collector is collecting the delegate, how can I prevent this?
The program I made is essentially a modified version of that vb.net project, but it does not actually store the keystrokes.
Thank you for your help!
Code within CallBack.vb:
Option Strict Off
Option Explicit On
Module CallBackFunction
'******************************************************************************************
' Sample for retrieving keystrokes by use of the "kbLog32.dll"
' (c) 2004 by Nadeem Afanah.
'******************************************************************************************
'CallBack function
Delegate Sub DelegateCallBack(ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer)
Sub CallBack(ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer)
'here we track only WM_CHAR and WM_KEYDOWN
If msg = WM_KEYDOWN Then
...
End If
End Sub
End Module
Code in Declarations.vb:
Option Strict Off
Option Explicit On
Module Declarations
'******************************************************************************************
' Sample for retrieving keystrokes by use of the "kbLog32.dll"
' (c) 2004 by Nadeem Afanah.
'******************************************************************************************
'******************************************************************************************
'DLL declarations
Public Declare Function StartLog Lib "kbLog32" (ByVal hWnd As Integer, ByVal lpFuncAddress As DelegateCallBack) As Integer
Public Declare Sub EndLog Lib "kbLog32" ()
'----------------------------------------------------------------------------------------
Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Integer, ByVal hWndInsertAfter As Integer, ByVal x As Integer, ByVal y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer) As Integer
Declare Function FindWindow Lib "user32" Alias "FindWindowA"(ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA"(ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, ByVal lpsz2 As String) As Integer
'****************************************************************************************
' Keyboard messages
Public Const WM_KEYUP As Short = &H101s
Public Const WM_KEYDOWN As Short = &H100s
Public Const WM_CHAR As Short = &H102s
Public Const WM_SYSKEYDOWN As Short = &H104s
Public Const WM_SYSKEYUP As Short = &H105s
'SetWindowPos messages
Public Const SWP_NOSIZE As Short = &H1s
Public Const SWP_NOMOVE As Short = &H2s
Public Const HWND_TOPMOST As Short = -1
Public Const SWP_SHOWWINDOW As Short = &H40s
'******************************************************************************************
End Module
Look at the code in Form1.vb where it does this:
StartLog(nhWnd_text, AddressOf CallBack)
This is where it's saying, take the location of the Callback function an use it to hanle messages I receive regarding keyboard events.
Try something like this:
Friend Class Form1
Inherits System.Windows.Forms.Form
''Add this ----------------------------
<MarshalAs(UnmanagedType.FunctionPtr)> _
Private DelSub as New DelegateCallBack(AdressOf CallBack)
''-------------------------------------
''In the sub Sub Command1_Click
''Change this -------------------------
StartLog(nhWnd_text, AddressOf CallBack)
''To this -----------------------------
StartLog(nhWnd_text, DelSub)
''-------------------------------------
End Class
What we're doing here is creating a local "delegate sub" (think of it as a variable which points at a sub). We're pointing this at the Callback sub. We're then using this delegate sub instead of passing in a reference directly to the Callback sub.
The difference is that the .Net framework now knows that there is something pointing at that sub so won't garbage collect it (clear it from memory)
The MarshallAs bit is a little superfluous as that's the default marshalling but it simply means we're explicitly telling .Net that we're using the delegate to access unmanaged code (something outside the .Net framework)
Just for the record, I still had to download the code as it was actually the bit in Form1.vb that was relevant - But thanks for trying :)
Related
So I need to use the SetTimer API in my Excel VB project, but after I execute the interval timer, the program crashes as soon as you attempt to run another macro. Even when simply clicking the macro button in Developer tab. The code:
Public Declare Function SetTimer Lib "user32" ( _
ByVal HWnd As Long, _
ByVal nIDEvent As Long, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" ( _
ByVal HWnd As Long, _
ByVal nIDEvent As Long) As Long
Public timId As Long, lala As Long, i As Integer
Public Sub CallTm()
timId = SetTimer(0, 0, 100, AddressOf Test)
End Sub
Public Sub AnotherSub()
MsgBox "This is not gonna be shown"
End Sub
Public Sub Test()
Cells(1, 1).Value = i
i = i + 1
End Sub
It seems it's not a problem with KillTimer. Simply setting the interval with SetTimer is like a switch for making sure no more macros can be run (or it will simply crash if you attempt that). I remember seeing Error 50290 if that's any more help.
Why is it so and how can it be fixed?
By the way, I'm making a snake game in Excel for a school project.
It seems like after the Timer is set, nothing can happen since the timer takes up all the thread? or smth like that and it can't be "interrupted".
Really, how is this API supposed to be used? It seems like a fatal error which makes it completely useless...
You're corrupting the stack, because your Test procedure does not match the signature of TimerProc. You should read and understand the documentation for API calls before simply making a blind stab at using them.
You can find the documentation for SetTimer at MSDN as well, just like all other WinAPI documentation.
the prototype of timerPorc is this
VOID CALLBACK TimerProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ UINT_PTR idEvent,
_In_ DWORD dwTime
);
that can be translated to vb as follow
sub Test(byval hWnd as long, byval uMsg as long,byval idIvent as long, byval dwTime as long)
'your code here
end sub
Good afternoon,
I am attempting to use SendMessage to pass a string from a VB6 EXE, to a .NET 2013 EXE. I know that the message is getting in to the .NET EXE, because I'm able to set a breakpoint on it and it comes up when I call SendMessage from the VB6 EXE. The problem I am having is retrieving the string.
This is how I am attempting to do it:
VB6 Code:
Option Explicit
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, Source As Any, ByVal bytes As Long)
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hwnd As Long, ByVal msg As Long, wParam As Long, lParam As Any) As Long
Private Const APPVIEWER_OPEN = &H400
Private Sub Command1_Click()
Dim hwndAppViewer As Long
Dim bytBuffer(1 To 255) As Byte
Dim sParams As String
Dim lStringAddress As Long
hwndAppViewer = FindWindow(vbNullString, "DotNetReceiver")
If hwndAppViewer > 0 Then
sParams = "STRINGDATA"
CopyMemory bytBuffer(1), sParams, Len(sParams)
lStringAddress = VarPtr(bytBuffer(1))
SendMessage hwndAppViewer, APPVIEWER_OPEN, Me.hwnd, lStringAddress
End If
End Sub
Here is the .NET code:
Imports System.Runtime.InteropServices
Public Class Form1
Protected Overrides Sub WndProc(ByRef m As Message)
Dim sPolicyInformation As String
If m.Msg = &H400 Then
sPolicyInformation = Marshal.PtrToStringAnsi(m.LParam)
Else
MyBase.WndProc(m)
End If
End Sub
End Class
The problem comes when I try and retrieve the string. I am getting a blank string. I noticed that the number in the VB6 lStringAddress and the number in .NET m.lParam are completely different, so I must be missing something about how I'm passing the address through lParam.
Any ideas what I might be missing?
Thank you.
You are sending an ANSI string to VB.NET. VB6 was designed for all MS's OSs and 9x wasn't unicode. So all strings passed to API calls will be converted to ANSI. Windows will convert that ANSI string to unicode for the VB.NET program when it recieves it.
Use the sendmessagew function and send the first element of a byte array that's null terminated.
Dim MyStr() as byte
MyStr = "cat" & chrw(0)
The pass only the first element to SendMessageW ie MyStr(0). Windows API uses null terminated C strings. COM and VB6 use BStr (a size header and a non null terminated string).
When passing strings by ref you pass the address of the header. When passing by value you pass the address of the first character (making it a c string if you tack a null on the end).
I try to make an app whitch will make mouse to go to external points on desktop.
I have a problem with this application. I receive error then press the button:
A call to PInvoke function 'WindowsApplication1!WindowsApplication1.Form1::mouse_event' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
Code is:
Public Declare Auto Function SetCursorPos Lib "User32.dll" _
(ByVal X As Integer, ByVal Y As Integer) As Long Public
Declare Auto Function GetCursorPos Lib "User32.dll" _
(ByRef lpPoint As Point) As Long Public
Declare Sub mouse_event Lib "user32" Alias "mouse_event" _
(ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, _
ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Public Const MOUSEEVENTF_LEFTDOWN = &H2 ' left button down
Public Const MOUSEEVENTF_LEFTUP = &H4 ' left button up
SetCursorPos(x, y) 'moves cursor to x,y position
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0) 'Invoke mouse down event
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0) 'Invoke mouse up event
This is a pretty self-explainable error.
"...PInvoke signature does not match the unmanaged target signature..."
Change your code to this:
<DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)> _
Public Shared Sub mouse_event(dwFlags As Integer, dx As Integer, dy As Integer, dwData As Integer, dwExtraInfo As IntPtr)
End Sub
mouse_event function
First of all, you are using the API declaration type of VB6, that does not have any advantage compared with the DLLImport, and also you've to update the signature for .NET, change all those Longs for Integers.
Secondly, the mouse_event function has been superseded by the SendInputs method as MSDN says, and recommends the usage of the new method.
So, as a suggestion, you could stop worriying about your P/invoking and use my SendInputs Helper Class which provides easy-of-use methods to set the mouse position and to simulate a mouse-button click.
Example usage:
SendInputs.MousePosition(100, 500)
SendInputs.MouseClick(SendInputs.MouseButton.LeftPress)
I am implementing an application in C#.net that is passing a message to an VB6 application.
For testing I created 2 applications both in C#.NET:- One sends message and 2nd receives the message.
The receiving application (C#.NET) makes use of the following function to catch the message:-
Protected Override void WndProc(ref Message m)
I now need to implement this receiver app in VB6.. How do we implement Protected Override void WndProc(ref Message m) in VB6? Or is there any other alternative?
Overriding the default windows procedure is possible in VB6 and is called Subclassing.
In a module:
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
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
Private Const GWL_WNDPROC As Long = (-4)
Private originalWindowProcAddr As Long
Public Sub subclassForm(hwnd As Long)
'// replace existing windows procedure save its address
originalWindowProcAddr = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf NewWindowProc)
End Sub
Public Function NewWindowProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Debug.Print "received message for:", hwnd, "message:", uMsg
'// forward message to default
NewWindowProc = CallWindowProc(originalWindowProcAddr, hwnd, uMsg, wParam, lParam)
End Function
Public Sub unSubclassForm(hwnd As Long)
'// must tidy up by restoring the original window proc
SetWindowLong hwnd, GWL_WNDPROC, originalWindowProcAddr
End Sub
In the form
Private Sub Form_Load()
subclassForm Me.hwnd
End Sub
Private Sub form_Unload(Cancel As Integer)
unSubclassForm Me.hwnd
End Sub
Failing to call unSubclassForm will crash the VB IDE, as will breaking into debug mode.
I have followed this method:-
http://support.microsoft.com/kb/176058/en-us
Alternate link: https://web.archive.org/web/20150118054920/http://support.microsoft.com:80/kb/176058
How To Pass String Data Between Applications Using SendMessage
SUMMARY
There are many ways to achieve inter-process communication using Visual Basic. Unless you establish an OLE Automation client server relationship, string data is difficult to handle cleanly. The main reason is that 32-bit applications run in a separate address space, so the address of a string in one application is not meaningful to another application in a different address space. Using the SendMessage() API function to pass a WM_COPYDATA message avoids this problem.
This article demonstrates how to pass string data from one application to another by using the SendMessage API function with the WM_COPYDATA message.
WARNING: One or more of the following functions are discussed in this article; VarPtr, VarPtrArray, VarPtrStringArray, StrPtr, ObjPtr. These functions are not supported by Microsoft Technical Support. They are not documented in the Visual Basic documentation and are provided in this Knowledge Base article "as is." Microsoft does not guarantee that they will be available in future releases of Visual Basic.
Visual Basic does not support pointers and castings in the manner of Visual C++. In order to pass string data from one Visual Basic application to another, the Unicode string must be converted to ASCII prior to passing it to the other application. The other application must then convert the ASCII string back to Unicode.
The following summarizes how to pass string data from one application to another.
Step-by-Step Example
Convert the string to a byte array using the CopyMemory() API.
Obtain the address of the byte array using the VarPtr() intrinsic function and copy the address and length of the byte array into a COPYDATASTRUCT structure.
Pass the COPYDATASTRUCT to another application using the WM_COPYDATA message, setting up the other application to receive the message.
Unpack the structure on the target system using CopyMemory(), and convert the byte array back to a string using the StrConv() intrinsic function.
The next section shows you how to create a sample program that demonstrates passing string data from one application to another.
Steps to Create the Sample
To create this sample, you create two separate projects; a sending project and a target project.
Create the target application:
Start a new Standard EXE project in Visual Basic. Form1 is created by default. This project will be your target application.
Add a Label control to Form1.
Copy the following code to the Code window of Form1:
Private Sub Form_Load()
gHW = Me.hWnd
Hook
Me.Caption = "Target"
Me.Show
Label1.Caption = Hex$(gHW)
End Sub
Private Sub Form_Unload(Cancel As Integer)
Unhook
End Sub
Add a module to the project and paste the following code in the Module1 code window:
Type COPYDATASTRUCT
dwData As Long
cbData As Long
lpData As Long
End Type
Public Const GWL_WNDPROC = (-4)
Public Const WM_COPYDATA = &H4A
Global lpPrevWndProc As Long
Global gHW As Long
'Copies a block of memory from one location to another.
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(hpvDest As Any, hpvSource As Any, ByVal cbCopy As Long)
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
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As _
Long) As Long
Public Sub Hook()
lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
AddressOf WindowProc)
Debug.Print lpPrevWndProc
End Sub
Public Sub Unhook()
Dim temp As Long
temp = SetWindowLong(gHW, GWL_WNDPROC, lpPrevWndProc)
End Sub
Function WindowProc(ByVal hw As Long, ByVal uMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
If uMsg = WM_COPYDATA Then
Call mySub(lParam)
End If
WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, _
lParam)
End Function
Sub mySub(lParam As Long)
Dim cds As COPYDATASTRUCT
Dim buf(1 To 255) As Byte
Call CopyMemory(cds, ByVal lParam, Len(cds))
Select Case cds.dwData
Case 1
Debug.Print "got a 1"
Case 2
Debug.Print "got a 2"
Case 3
Call CopyMemory(buf(1), ByVal cds.lpData, cds.cbData)
a$ = StrConv(buf, vbUnicode)
a$ = Left$(a$, InStr(1, a$, Chr$(0)) - 1)
Form1.Print a$
End Select
End Sub
Save the project and minimize the Visual Basic IDE.
Create the Sending Application
Start a second instance of the Visual Basic IDE and create a new Standard EXE project in Visual Basic. Form1 is created by default.
Add a CommandButton to Form1.
Copy the following code to the Code window of Form1:
Private Type COPYDATASTRUCT
dwData As Long
cbData As Long
lpData As Long
End Type
Private Const WM_COPYDATA = &H4A
Private Declare Function FindWindow Lib "user32" Alias _
"FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName _
As String) As Long
Private Declare Function SendMessage Lib "user32" Alias _
"SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal _
wParam As Long, lParam As Any) As Long
'Copies a block of memory from one location to another.
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(hpvDest As Any, hpvSource As Any, ByVal cbCopy As Long)
Private Sub Command1_Click()
Dim cds As COPYDATASTRUCT
Dim ThWnd As Long
Dim buf(1 To 255) As Byte
' Get the hWnd of the target application
ThWnd = FindWindow(vbNullString, "Target")
a$ = "It Works!"
' Copy the string into a byte array, converting it to ASCII
Call CopyMemory(buf(1), ByVal a$, Len(a$))
cds.dwData = 3
cds.cbData = Len(a$) + 1
cds.lpData = VarPtr(buf(1))
i = SendMessage(ThWnd, WM_COPYDATA, Me.hwnd, cds)
End Sub
Private Sub Form_Load()
' This gives you visibility that the target app is running
' and you are pointing to the correct hWnd
Me.Caption = Hex$(FindWindow(vbNullString, "Target"))
End Sub
Save the project.
Running the Sample
Restore the target application and press the F5 key to run the project. Note that the value of the hWnd displayed in the label.
Restore the sending application and press the F5 key to run the project. Verify that the hWnd in the form caption matches the hWnd in the label on the target application. Click the CommandButton and the text message should be displayed on the form of the target application.
I am trying to add an 'About' button to the System menu of my app, but the code that I found is throwing an error -
Unable to find an entry point named 'AppendMenu' in DLL 'user32'.
I wonder if someone could please take a look at the code and advise on how what I would need to do to fix it? Thanks.
Private Declare Function GetSystemMenu Lib "user32" (ByVal hWnd As IntPtr, ByVal bRevert As Boolean) As IntPtr
Private Declare Function AppendMenu Lib "user32" (ByVal hMenu As IntPtr, ByVal uFlags As Int32, ByVal uIDNewItem As IntPtr, ByVal lpNewItem As String) As Boolean
Private Const MF_STRING As Integer = &H0
Private Const MF_SEPARATOR As Integer = &H800
Private Sub AddSysMenuItems()
'Get the System Menus Handle.
Dim hSysMenu As IntPtr = GetSystemMenu(Me.Handle, False)
'Add a standard Separator Item.
AppendMenu(hSysMenu, MF_SEPARATOR, 1000, Nothing)
'Add an About Menu Item.
AppendMenu(hSysMenu, MF_STRING, 1001, "About")
End Sub
Well, the message is accurate, there is no entry point named "AppendMenu" in user32.dll. It actually has two versions of it. One is named AppendMenuA, the A means Ansi. The legacy version that uses 8-bit encoded strings, commonly used in old C programs. And AppendMenuW, the W means Wide. It takes a Unicode string like all winapi functions do on modern Windows versions.
Your old-style Declare statement is using the legacy function. You should use the Alias keyword to give the proper entrypoint name:
Private Declare Function AppendMenu Lib "user32.dll" Alias "AppendMenuA" (ByVal hMenu As IntPtr, ByVal uFlags As Int32, ByVal uIDNewItem As IntPtr, ByVal lpNewItem As String) As Boolean
Or just plain call it AppendMenuA. Using the legacy function isn't very pretty, although it won't have a problem converting "About" to Unicode. But do favor the modern way to declare pinvoke functions, it has many advantages beyond automatically mapping to the A or W version:
Imports System.Runtime.InteropServices
Imports System.ComponentModel
...
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function AppendMenu(ByVal hMenu As IntPtr, ByVal uFlags As Int32, ByVal uIDNewItem As IntPtr, ByVal lpNewItem As String) As Boolean
End Function
...
If Not AppendMenu(hSysMenu, MF_STRING, IntPtr.Zero, "About") Then
Throw New Win32Exception()
End If