I'm currently developing a little hobby project to display health information in a game on my G15 keyboard through VB.NET.
When I use ReadProcessMemory via an API call, it keeps returning zero. The MSDN documentation referred me to use the Marshal.GetLastWin32Error() call to find out what is wrong and it returns 1400: INVALID WINDOW HANDLE.
I am now confused about whether the first argument of the function wants a window handle or a process id. Regardless, I have tried both with FindWindow and hardcoding the process id while the application is running (getting it from task manager).
I have tried three different games, Urban Terror, Grand Theft Auto: SA and 3D pinball for windows, getting the memory addresses from an application called Cheat Engine; they all seem to fail.
Here is the code I'm using to do it:
API Call:
Private Declare Function ReadProcessMemory Lib "kernel32" ( _
ByVal hProcess As Integer, _
ByVal lpBaseAddress As Integer, _
ByRef lpBuffer As Single, _
ByVal nSize As Integer, _
ByRef lpNumberOfBytesWritten As Integer _
) As Integer
Method:
Dim address As Integer
address = &HA90C62&
Dim valueinmemory As Integer
Dim proc As Process = Process.GetCurrentProcess
For Each proc In Process.GetProcesses
If proc.MainWindowTitle = "3D Pinball for Windows - Space Cadet" Then
If ReadProcessMemory(proc.Handle.ToInt32, address, valueinmemory, 4, 0) = 0 Then
MsgBox("aww")
Else
MsgBox(CStr(valueinmemory))
End If
End If
Next
Dim lastError As Integer
lastError = Marshal.GetLastWin32Error()
MessageBox.Show(CStr(lastError))
Could somebody please explain to me why it is not working? Thanks in advance.
First, your method signature is wrong, Single=Float while the original parameter is of the LPBUF type.
Use this method signature:
<DllImport("kernel32.dll", SetLastError=true)> _
Public Shared Function ReadProcessMemory( _
ByVal hProcess As IntPtr, _
ByVal lpBaseAddress As IntPtr, _
<Out()>ByVal lpBuffer() As Byte, _
ByVal dwSize as Integer, _
ByRef lpNumberOfBytesRead as Integer
) As Boolean
End Function
Second, I believe that the hProcess handle expects a handle opened by OpenProcess function, not the window handle.
Thank you arul, I have sort of fixed my problem.
Dim address As Integer
address = &HA90C62&
Dim floatvalueinmemory() As Byte
Dim proc As Process = Process.GetCurrentProcess
For Each proc In Process.GetProcesses
If proc.MainWindowTitle = "3D Pinball for Windows - Space Cadet" Then
Dim winhandle As IntPtr = OpenProcess(PROCESS_ACCESS.PROCESS_VM_READ, True, proc.Id)
If ReadProcessMemory(winhandle, address, floatvalueinmemory, 4, 0) = 0 Then
Dim lastError As Integer
lastError = Marshal.GetLastWin32Error()
MessageBox.Show(CStr(lastError))
MsgBox("aww")
Else
MsgBox("woo")
End If
CloseHandle(winhandle)
End If
Next
Now it believes the handle is valid and attempts to read the processes memory, but I get the error message 299 : Only part of a ReadProcessMemory or WriteProcessMemory request was completed.
Does anyone have any ideas as to how I should proceed to fix this issue?
message 299 : Only part of a ReadProcessMemory or WriteProcessMemory request was completed meant the memory I was trying to read was protected.
Thanks for all your help, I'm going to mark arul's answer as the answer.
Related
I need to be able to list all current Access applications. The GetObject command is well thought out, but it is not very efficient when it comes to simultaneously processing batches of read/write accdb files and ensure that there is only one Access instance per file. I found approaches to my problem in some rare places on the Net and I was actually able to tinker with exactly what I needed.
But my solution has some rather strange and annoying side effects: when I use it, Access instances don't really close but get invisible while keeping applications opened: I can't even make them visible again with .Visible= True, the action just don't work and I must kill them by hand. I have even seen remaining Access instances mixing in the task manager with the Excel instance Workbooks...
The fact is that I have very little knowledge of the Windows APIs that it implements: it's by chance if my solution works.
So I'm asking you here to help me finalize this code that does a simple thing, return a collection of Applications Access objects currently opened.
Here is the code:
Option Explicit
Private Declare Function GetDesktopWindow Lib "user32" () As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As LongPtr, ByVal dwId As Long, riid As UUID, ppvObject As Object) As Long
Private Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, ByRef lpiid As UUID) As Long
Private Type UUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}"
Private Const OBJID_NATIVEOM As Long = &HFFFFFFF0
Public Function AccessInstances() As Collection
Dim hWndDesk As LongPtr, hWnd As LongPtr
Dim iid As UUID, obj As Object
Dim acApp As Access.Application
Set AccessInstances = New Collection
hWndDesk = GetDesktopWindow
Do
hWnd = FindWindowEx(hWndDesk, hWnd, "OMain", vbNullString)
Call IIDFromString(StrPtr(IID_IDispatch), iid)
If AccessibleObjectFromWindow(hWnd, OBJID_NATIVEOM, iid, obj) = 0 Then
Set acApp = obj
AccessInstances.Add acApp
End If
Loop Until hWnd = 0
Set acApp = Nothing
End Function
The command that triggers the problems is AccessibleObjectFromWindow. I understand that there is an intermediate FindWindowEx call to do before invoking this command, but I ignore how it must be done, this totally out of my scope.
I thought that the Application Objects reserved by the collection could be what forces the application to stay open, but I never use them in a static or module level private variable, which implies that they are necessarily set to Nothing when the program stops, whether I do it myself explicitly or not, like in this example:
Sub ListAccessInstances()
Dim acApp As Access.Application
For Each acApp In AccessInstances
Debug.Print acApp.Name
Next
End Sub
Edit / additional information :
I was able to highlight the seemingly systematic problem that the function produces.
The principle is that the function produces side effects that do not exist when it is not used: Access instances remain open. A question that arises is whether or not these instances are empty. It seems to me that closing the last instance will totally close this leftover, but I am still uncertain when this may depend on the answer to the previous question.
The test procedure I have used is two-stage. A first procedure located in an Access database opens with the Shell command about ten other Access databases and a second one closes them (Getobject(aFile).Quit) . Thus an Access database remains always open.
The test consists in using or not using the incriminated function between the two procedures and to note what differs in the application manager, and also in the result of the function itself. This test is considered successful if there is no other instance left than the current one having used this function between the openings and closings. I remind you that this function is supposed to be purely readable and therefore without any consequence on the system.
1°) The test described above is generally positive: the instances are cleaned after they are closed. Nevertheless, I still saw one or two of them dragging.
2°) When you close the bases manually instead of using the closing procedure, the instances remain. Alexandru, could you try this test and tell me if you observe the same thing?
This is the demonstration, whose reproducibility I don't know yet, that the function does produce a system malfunction. In real work I had noticed that sometimes some instances still had their base (CurrentDb) open under the conditions I have described: locked in their invisibility. In fact, other visible effects in the task manager occur more or less randomly. For example to have an open and functional Access instance that does not appear in the task manager.
My approach to build this function has been very empirical. In particular, I learned from a code that allows the same thing with Excel. Since Excel is now mono-instance, I could not test this function, but I assume nevertheless that it is well written and that it works without side effects.
Here is the excerpt of the code we are interested in:
Function GetXLapp(hWinXL As Long, xlApp As Object) As Boolean
Dim hWinDesk As Long, hWin7 As Long
Dim obj As Object
Dim iid As GUID
Call IIDFromString(StrPtr(IID_IDispatch), iid)
hWinDesk = FindWindowEx(hWinXL, 0&, "XLDESK", vbNullString)
hWin7 = FindWindowEx(hWinDesk, 0&, "EXCEL7", vbNullString)
If AccessibleObjectFromWindow(hWin7, OBJID_NATIVEOM, iid, obj) = S_OK Then
Set xlApp = obj.Application
GetXLapp = True
End If
End Function
One can see that there are two successive window calls, this is the aspect I shunted in an experiment that was not supposed to work, but it still gave the result I have here. Functional, but producing instability. That's it, my question is whole, should we make this intermediate call with Access and if so how? Is it something else?, etc.
Try this
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As LongPtr, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long
Private Declare PtrSafe Function FindWindowExA Lib "User32" (ByVal hwndParent As LongPtr, ByVal hwndChildAfter As LongPtr, ByVal lpszClass As String, ByVal lpszWindow As String) As LongPtr
Public Function getAccessInstanceList() As Collection
Dim GUID&(0 To 3), acc As Object, hWnd
GUID(0) = &H20400
GUID(1) = &H0
GUID(2) = &HC0
GUID(3) = &H46000000
Set getAccessInstanceList = New Collection
Do
hWnd = FindWindowExA(0, hWnd, "OMain", vbNullString)
If hWnd = 0 Then Exit Do
If AccessibleObjectFromWindow(hWnd, &HFFFFFFF0, GUID(0), acc) = 0 Then
getAccessInstanceList.add acc.Application
End If
Loop
End Function
I've been banging my head on this one all day, and I just can't make sense of the various (sometimes conflicting) documentation on this. To add to the confusion, at some point during the day, this did (sort of) work - i.e. didn't throw an access violation error. But the data at the other end of the pipe was nonsense, so I suspect it only 'worked' by accident.
I have a vb.net program (using .net 3.0 so no support for System.IO.NamedPipes :( ) which creates a named pipe and waits for another app to connect and send some data. As an 'ack', I then want the vb program to send back the total length of the message received. I'm able to create the pipe, wait for a connection and receive the message, but it barfs on trying to send the 'ack' using WriteFile. The (current) definition I'm using for WriteFile is based on the corresponding ReadFile, which seems to work fine:
Declare Function ReadFile Lib "kernel32" ( _
ByVal hFile As Integer, _
ByRef lpBuffer As Byte, _
ByVal nNumberOfBytesToRead As Integer, _
ByRef lpNumberOfBytesRead As Integer, _
ByVal lpOverlapped As Integer) _
As Integer
Declare Function WriteFile Lib "kernel32" ( _
ByVal hFile As Long, _
ByRef lpBuffer As Byte, _
ByVal nNumberOfBytesToWrite As Integer, _
ByRef lpNumberOfBytesWritten As Integer, _
ByVal lpOverlapped As Integer) _
As Integer
The stripped down code (error checking and debug printing removed) looks like this:
Dim openMode = PIPE_ACCESS_DUPLEX Or FILE_FLAG_FIRST_PIPE_INSTANCE
Dim pipeMode = PIPE_WAIT Or PIPE_TYPE_MESSAGE Or PIPE_READMODE_MESSAGE
Dim res ' Result of dll calls
pipeN2Q = CreateNamedPipe("\\.\pipe\N2Q", openMode, pipeMode, 10, 1024, 1024, 2000, IntPtr.Zero)
res = ConnectNamedPipe(pipeN2Q, 0)
Dim rxCount As Integer = 0 ' To hold the number of bytes received
Dim txCount As Integer = 0 ' To hold the number of bytes sent
Dim txReq As Integer = 2 ' To hold the number of bytes we're going to ask to be sent during the 'ack'
Dim dataIn(256) As Byte
res = ReadFile(pipeN2Q, dataIn(0), 256, rxCount, Nothing)
Dim recvBuffer As String = System.Text.Encoding.ASCII.GetString(dataIn, 0, rxCount)
Dim dataOut(2) As Byte
dataOut(0) = 42
dataOut(1) = 43
res = WriteFile(pipeN2Q, dataOut(0), txReq, txCount, Nothing)
Once the code gets to WriteFile, it throws an AccessViolationException - "Attempted to read or write protected memory." I'm assuming this is complaining about the dataOut parameter, but it gives no further details. Things I've tried so far include:
Changing the declaration of WriteFile so that lpBuffer is declared: ByVal lpBuffer as IntPtr
Allocating dataOut using Marshal.AllocHGlobal(txReq) and initialised using Marshal.WriteInt16()
Allocating a large buffer for dataOut (1024 bytes) and initialising them to zeros
To be clear, the message coming from the other app is received perfectly and recvBuffer has the string exactly as sent. I just can't persuade WriteFile to cooperate (except once, perhaps by chance, and I haven't been able to repeat it). I'm hoping it's just something in either the declaration or the initialisation of the variables - any clues?
Based on Hans' suggestion to use pinvoke.net, here is the combination of signatures and calls which eventually worked for me - just in case anyone else can use it. I know I should probably convert those first 3 declarations to follow the same format as ReadFile / WriteFile, but it's working for now...
Declare Function CreateNamedPipe Lib "kernel32" Alias "CreateNamedPipeA" ( _
ByVal lpName As String, ByVal dwOpenMode As Integer, _
ByVal dwPipeMode As Integer, ByVal nMaxInstances As Integer, _
ByVal nOutBufferSize As Integer, ByVal nInBufferSize As Integer, _
ByVal nDefaultTimeOut As Integer, ByVal lpSecurityAttributes As IntPtr) _
As SafeFileHandle
Declare Function ConnectNamedPipe Lib "kernel32" ( _
ByVal hNamedPipe As SafeFileHandle, ByVal lpOverlapped As System.Threading.NativeOverlapped) _
As SafeFileHandle
Declare Function CloseHandle Lib "kernel32" ( _
ByVal hFile As SafeFileHandle) _
As Integer
<DllImport("kernel32.dll", SetlastError:=True)> Friend Shared Function WriteFile( _
ByVal File As SafeFileHandle, _
ByVal Buffer As System.Text.StringBuilder, _
ByVal NumberOfBytesToWrite As Integer, _
ByRef NumberOfBytesWritten As Integer, _
ByRef Overlapped As System.Threading.NativeOverlapped) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("kernel32.dll")> Friend Shared Function ReadFile( _
ByVal File As SafeFileHandle, _
ByVal Buffer As System.Text.StringBuilder, _
ByVal NumberOfBytesToRead As Integer, _
ByRef NumberOfBytesRead As Integer, _
ByRef Overlapped As System.Threading.NativeOverlapped) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
...
pipeN2Q = CreateNamedPipe("\\.\pipe\N2Q", openMode, pipeMode, 10, 1024, 1024, 2000, IntPtr.Zero)
If Not pipeN2Q.IsInvalid Then
If Not ConnectNamedPipe(pipeN2Q, Nothing).IsInvalid Then
Dim rxCount As Integer = 0
Dim txCount As Integer = 0
Dim dataIn As New System.Text.StringBuilder
res = ReadFile(pipeN2Q, dataIn, 256, rxCount, Nothing)
If res <> 0 Then
Dim dataOut As New StringBuilder(dataIn.Length.ToString)
res = WriteFile(pipeN2Q, dataOut, dataOut.Length, txCount, Nothing)
Else
Debug.Print("ReadFile ERROR: " & Err.LastDllError)
End If
Else
Debug.Print("ConnectNamedPipe ERROR: " & Err.LastDllError)
End If
If CloseHandle(pipeN2Q) <> 0 Then
Debug.Print("Pipe closed")
Else
Debug.Print("CloseHandle ERRPR: " & Err.LastDllError)
End If
Else
Debug.Print("CreateNamedPipe ERROR: " & Err.LastDllError)
End If
Trying out some API programming, I encountered a problem with the capGetDriverDescription function of AVICAP32.dll:
Declare Function capGetDriverDescriptionA Lib "avicap32.dll" (ByVal wDriver As Short, _
ByVal lpszName As String, ByVal cbName As Integer, ByVal lpszVer As String, _
ByVal cbVer As Integer) As Boolean
As far as I know, the lpszName parameter is a buffer for getting the driver description, and I read somewhere that the buffer should be passed as a reference type (ByRef instead of ByVal), and of course it should be a reference because of storing the information I need after returning from the function. But in this function, it's a value type, and it works fine!
even I tried to change it to ByRef but the application didnt run!
What knowledge am I missing? How can this buffer store my information while it's being passed a value type?
This is how the code calls the function to get available webcams:
Private Sub LoadDeviceList()
On Error Resume Next
Dim strName As String = Space(100)
Dim strVer As String = Space(100)
Dim bReturn As Boolean
Dim x As Integer = 0
Do
bReturn = capGetDriverDescriptionA(x, strName, 100, strVer, 100)
If bReturn Then
lst1.Items.Add(strName.Trim)
End If
x += 1
Application.DoEvents()
Loop Until bReturn = False
End Sub
I'll start by saying I don't know much about this subject (marshaling data can be a deep subject), but I think Default Marshaling for Strings (MSDN) may help you. Scroll down to the part that says Fixed-Length String Buffers. According to this, when a string is marshaled to the API, even if passed byRef, it can't be modified by the callee.
I haven't tested, but according to the MSDN example, to get a value back from your function, the definition would become
Declare Function capGetDriverDescriptionA Lib "avicap32.dll" (ByVal wDriver As Short, _
ByVal lpszName As StringBuilder, ByVal cbName As Integer, ByVal lpszVer As String, _
ByVal cbVer As Integer) As Boolean
Changing lpszName from String to StringBuilder. Apparently StringBuilder will work as a buffer to get the string back from the function.
Then you would call your function like so,
StringBuilder sb = new StringBuilder(256)
capGetDriverDescription(Driver, sb, sb.Capacity + 1, ....
return sb.ToString()
I found a very similar question, How do I import and call unmanaged C dll with ANSI C string "char *" pointer string from VB.NET?.
Is there a managed VB.net way to get the Process ID from the HWND rather than using this Windows API call.
Private Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As IntPtr, _
ByRef lpdwProcessId As Integer) As Integer
Sub GetProcessID()
'start the application
Dim xlApp As Object = CreateObject("Excel.Application")
'get the window handle
Dim xlHWND As Integer = xlApp.hwnd
'this will have the process ID after call to GetWindowThreadProcessId
Dim ProcIdXL As Integer = 0
'get the process ID
GetWindowThreadProcessId(xlHWND, ProcIdXL)
'get the process
Dim xproc As Process = Process.GetProcessById(ProcIdXL)
End Sub
No, this isn't wrapped by .NET. But there's absolutely nothing wrong with calling the native API functions. That's what the framework does internally, and that's why P/Invoke was invented, to make it as simple as possible for you to do this yourself. I'm not really sure why you're seeking to avoid it.
Of course, I would recommend using the new-style declaration, which is the more idiomatic way of doing things in .NET (rather than the old VB 6 way):
<DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, _
ByRef lpdwProcessId As Integer) As Integer
End Function
Your other option, if you absolutely cannot get over the irrational compulsion to stay with managed code, is to make use of the Process class. This can be used to start an external process, and has a property (Id) that can be used to retrieve the process's ID. I'm not sure if that will work for you. You specifically avoid telling us why you're using CreateObject in the first place.
I am trying to call TrackPopupMenu function to display a menu at runtime from managed VB.NET code.
Below is the error I am getting :
PInvokeStackImbalance was detected Message: A call to PInvoke function
'UeWIPopupX!UeWIPopupX.mDeclares::TrackPopupMenu' 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.
Below is the declaration I am using for TrackPopupMenu function :
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)> _
Friend Function TrackPopupMenu(ByVal hMenu As Long, ByVal wFlags As Integer, ByVal x As Integer, ByVal y As Integer, ByVal nReserved As Integer, ByVal hWnd As IntPtr, ByVal lprc As RECT) As Integer
End Function
Below is the code for calling TrackPopupMenu function :
dim lpRc as RECT
Dim tP As POINTAPI
Dim lR as Integer
Dim lUn as Integer
lUn = TPM_RIGHTBUTTON Or TPM_TOPALIGN Or TPM_LEFTALIGN Or TPM_RETURNCMD
tP.x = 50
tP.y = 100
'Here I am getting the error
lR = TrackPopupMenu(m_ppMenu.Tools(1).hMenu, lUn, tP.x, tP.y, 0, m_hWndOwner, lpRC)
Below is the declaration for rectangle RECT:
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Structure RECT
Dim Left As Integer
Dim Top As Integer
Dim Right As Integer
Dim Bottom As Integer
End Structure
All the arguments during call of TrackPopupMenu has some values.
I tried different callingConvention but still getting the error.
I am not able to solve this. Does anyone know how to resolve this issue ?
Your declaration is wrong. The first argument is a handle to the menu, it must therefore be IntPtr. The last argument is a pointer to RECT. ByRef in VB.NET. Since it isn't actually used, you are better off declaring it ByVal IntPtr so you don't need the RECT declaration. Pass IntPtr.Zero in your call. The return value is Boolean, not Integer. Throw a Win32Exception if you get a False return. Fix:
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Friend Function TrackPopupMenu(ByVal hMenu As IntPtr, ByVal wFlags As Integer, _
ByVal x As Integer, ByVal y As Integer, ByVal nReserved As Integer, _
ByVal hWnd As IntPtr, ByVal ignored As IntPtr) As Boolean
End Function
There are some hints that you didn't get the menu right, hard to imagine how you came up with Long as the argument type. Do note that this function is already ably wrapped in .NET, having to pinvoke it should be extremely rare. TrackPopupMenu is already called by the .NET ContextMenu class. The ContextMenuStrip class gives a more modern version of it with better rendering options.