I am new to VB.Net. I came from vb6 and VBA so I'm still learning. I am trying to clear the memory space for when I retrieve text using my GetWinTxt() function. I am not sure if I am properly releasing the memory space or not. I have researched MSDN and a multitude of other sites including this one trying to learn about this and I find it somewhat confusing. This function will be used a lot and i don't want a memory leak. It seems to be working fine as there isn't any error codes but can someone with more knowledge please tell me if I'm doing this right? Also should I be using Marshal.DestroyStructure() before I use Marshal.FreeHGlobal()? And if so how would I use that in my code below?
Option Explicit On
Imports System.Runtime.InteropServices
Declare Auto Function SendMessage Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Shared Function FindWindowEx(ByVal parentHandle As IntPtr, ByVal childAfter As IntPtr, ByVal lclassName As String, ByVal windowTitle As String) As IntPtr
End Function
Friend Const WM_GETTEXT = &HD
Friend Const WM_GETTEXTLENGTH = &HE
Private Sub Button5_Click(sender As Object, e As EventArgs) Handles Button5.Click
Dim Hwnd As IntPtr = FindWindow(Nothing, "Untitled - Notepad") 'notepad parent win
Dim Handle As IntPtr = FindWindowEx(Hwnd, IntPtr.Zero, "Edit", Nothing) 'notepad edit area
MsgBox(Lf.GetWinTxt(Handle))'Lf is my class name
End Sub
Friend Function GetWinTxt(hwnd As IntPtr) As String
Dim TextLen As Integer = SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0) + 1
Dim WinHandle As IntPtr = Marshal.AllocHGlobal(TextLen)
Call SendMessage(hwnd, WM_GETTEXT, TextLen, WinHandle)
Dim txt As String = Marshal.PtrToStringUni(WinHandle)
GetWinTxt = txt
Dim ByteString(TextLen) As Byte ' the next 3 lines are what i'm not sure if i'm doing it right or not
Marshal.Copy(WinHandle, ByteString, 0, TextLen)
Marshal.FreeHGlobal(txt)
End Function
EDITED FINAL CODE:
Public Function GetWinTxt(hwnd As IntPtr) As String
Dim TextLen As IntPtr = SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0) + 1
Dim f As Integer = TextLen
Winhandle = Marshal.AllocHGlobal(f)
Call SendMessage(hwnd, WM_GETTEXT, f, Winhandle)
Dim txt As String = Marshal.PtrToStringUni(Winhandle)
Return txt
Try
Dim ByteString(f) As Byte
Marshal.Copy(Winhandle, ByteString, 0, f)
Marshal.FreeHGlobal(Winhandle)
Catch ex As Exception
Return ""
Debug.Print(ex.Message)
End Try
End Function
The answer is no, unless you call method like Marshal. StructureToPtr().
You should use the counterpart of whatever method you used to allocate it.
Marshal. Destroy Structure will free all substructures that specify unmanaged memory block points to. But you don't have any structure in it.
In addition, you call Dim WinHandle As IntPtr = Marshal.AllocHGlobal(TextLen) Allocates memory from the unmanaged memory of the process to WinHandle, then free anther one, Marshal.FreeHGlobal(txt). This will cause memory leaks.
try to change it to Marshal.FreeHGlobal(WinHandle).
Related
I made some research, but I can't find something really "interesting". I tried my best to find any kind of documentation or questions that are closest to my case as following:
How to find main window title name of application
how to get the window title of a process
How to get the Title Bar Text by its Process Id
getting the name of a process
How do I get list of Process Names running
Check to see if process is running
How To Get Process Owner ID
How to get the title/name of the last active window?
Get Process ID from Window Title
and also
Process.GetProcessesByName Method
The code I am using to open the process window
Private Async Function ParentMethod() As Task
Dim filePath As String = Await Task.Run(
Function()
Return Directory.EnumerateFiles(My.Settings.Cartellasalvataggio, titolo & ".mp3",
SearchOption.AllDirectories).FirstOrDefault()
End Function)
If Not String.IsNullOrEmpty(filePath) Then
LinkLabel1.Text = "File exist already"
LinkLabel1.Visible = True
PictureBox7.Visible = True
Else
MsgBox("it doesn't exist")
End If
End Function
and the helper class
Imports System.IO
Imports System.Runtime.InteropServices
Public Class NativeMethods
<DllImport("shell32.dll", SetLastError:=True)>
Private Shared Function SHOpenFolderAndSelectItems(
pidlFolder As IntPtr, cidl As UInteger,
<[In], MarshalAs(UnmanagedType.LPArray)> apidl As IntPtr(),
dwFlags As UInteger) As Integer
End Function
<DllImport("shell32.dll", SetLastError:=True)>
Private Shared Sub SHParseDisplayName(
<MarshalAs(UnmanagedType.LPWStr)> name As String,
bindingContext As IntPtr, <Out> ByRef pidl As IntPtr,
sfgaoIn As UInteger, <Out> ByRef psfgaoOut As UInteger)
End Sub
Public Shared Sub OpenFolderAndSelectFile(filePath As String)
Dim dirPath As String = Path.GetDirectoryName(filePath)
Dim fileName As String = Path.GetFileName(filePath)
OpenFolderAndSelectFile(dirPath, fileName)
End Sub
Public Shared Sub OpenFolderAndSelectFile(dirPath As String, fileName As String)
Dim nativeFolder As IntPtr
Dim psfgaoOut As UInteger
SHParseDisplayName(dirPath, IntPtr.Zero, nativeFolder, 0, psfgaoOut)
If nativeFolder = IntPtr.Zero Then
' Log error, can't find folder
Return
End If
Dim nativeFile As IntPtr
SHParseDisplayName(Path.Combine(dirPath, fileName),
IntPtr.Zero, nativeFile, 0, psfgaoOut)
Dim fileArray As IntPtr()
If nativeFile = IntPtr.Zero Then
' Open the folder without the file selected if we can't find the file
fileArray = New IntPtr(-1) {}
Else
fileArray = New IntPtr() {nativeFile}
End If
SHOpenFolderAndSelectItems(nativeFolder, CUInt(fileArray.Length), fileArray, 0)
Marshal.FreeCoTaskMem(nativeFolder)
If nativeFile <> IntPtr.Zero Then
Marshal.FreeCoTaskMem(nativeFile)
End If
End Sub
End Class
then calling it with
NativeMethods.OpenFolderAndSelectFile(filepath,filename & "extension"))
Since I am opening the process this way and NOT with Process class, almost all of them are not suitable to be considered for my case as many of them refer to notepad, while I think the explorer window title and ID changes for every file ( obviously), while "notepad" process, stay "notepad".
I also tried BringToFront, but this latter moves a control in front of other controls, but in this case Explorer is not a control, right?
The least I want to do is to
Get a list of active windows & their process names
as It will waste memory and time usage for no reason as I will need to "filter" process to find my process.
Hope we can find a solution to this, Thanks in advance.
Mattia
This is the solution to it using FindWindowW e SetWindowPos Api.
It is showing Explorer folder on top of top most form.
<DllImport("user32.dll", EntryPoint:="FindWindowW")>
Public Shared Function FindWindowW(<MarshalAs(UnmanagedType.LPTStr)> ByVal lpClassName As String, <MarshalAs(UnmanagedType.LPTStr)> ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll")>
Shared Function SetWindowPos(ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As UInteger) As Boolean
End Function
Shared ReadOnly HWND_TOPMOST As IntPtr = New IntPtr(-1)
Const SWP_NOSIZE As UInt32 = &H1
Const SWP_NOMOVE As UInt32 = &H2
Const SWP_SHOWWINDOW As UInt32 = &H40
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim inptr = FindWindowW("CabinetWClass", Nothing)
SetWindowPos(inptr, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE Or SWP_SHOWWINDOW)
End Sub
Hi currently I'm having this code below.
It retrieves the filename of open excel document from process and displays it.
For now it can retrieve it from my own pc, but when I wanted to remotely retrieves it from other pc, it doesn't work.
I have authorized access and I could actually get the process id and name of remote pcs only.
Dim w As Object
Dim processQ As String
Dim processes As Object
Dim processA As Object
Dim pname As String
w = GetObject("winmgmts:{impersonationLevel=impersonate}\\" & pc & "\root\cimv2")
processQ = "SELECT * FROM win32_process WHERE name = 'EXCEL.EXE'"
processes = w.execquery(processQ)
For Each processA In processes
activeprocess = Process.GetProcessById(processA.processid)
MsgBox(processA.processid & processA.name)
Dim windows As IDictionary(Of IntPtr, String) = GetOpenWindowsFromPID(processA.processid)
MsgBox(windows.Count())
For Each kvp As KeyValuePair(Of IntPtr, String) In windows
Dim value As String = kvp.Value.ToString
If InStr(value, "Excel") = False Then
MsgBox(value)
End If
Next
Next
Can anyone tell me what should I do and what's wrong with this?
<DllImport("USER32.DLL")>
Private Shared Function GetShellWindow() As IntPtr
End Function
<DllImport("USER32.DLL")>
Private Shared Function GetWindowText(ByVal hwnd As IntPtr, ByVal lpString As StringBuilder, ByVal nMaxCount As Integer) As Integer
End Function
<DllImport("USER32.DLL")>
Private Shared Function GetWindowTextLength(ByVal hwnd As IntPtr) As Integer
End Function
<DllImport("USER32.DLL", SetLastError:=True)>
Private Shared Function GetWindowThreadProcessId(ByVal hwnd As IntPtr, <Out()> ByRef lpdwProcessId As UInt32) As UInt32
End Function
<DllImport("USER32.DLL")>
Private Shared Function IsWindowVisible(ByVal hwnd As IntPtr) As Boolean
End Function
Private Delegate Function EnumWindowsProc(ByVal hwnd As IntPtr, ByVal lParam As Integer) As Boolean
<DllImport("USER32.DLL")>
Private Shared Function EnumWindows(ByVal enumFunc As EnumWindowsProc, ByVal lParam As Integer) As Boolean
End Function
Private hShellWindow As IntPtr = GetShellWindow()
Private dictWindows As New Dictionary(Of IntPtr, String)
Private currentProcessID As Integer
Public Function GetOpenWindowsFromPID(ByVal processID As Integer) As IDictionary(Of IntPtr, String)
dictWindows.Clear()
currentProcessID = processID
EnumWindows(AddressOf enumWindowsInternal, 0)
Return dictWindows
End Function
Public Function enumWindowsInternal(ByVal hwnd As IntPtr, ByVal lParam As Integer) As Boolean
If (hwnd <> hShellWindow) Then
Dim windowPid As UInt32
If Not IsWindowVisible(hwnd) Then
Return True
End If
Dim length As Integer = GetWindowTextLength(hwnd)
If (length = 0) Then
Return True
End If
GetWindowThreadProcessId(hwnd, windowPid)
If (windowPid <> currentProcessID) Then
Return True
End If
Dim stringBuilder As New StringBuilder(length)
GetWindowText(hwnd, stringBuilder, (length + 1))
dictWindows.Add(hwnd, stringBuilder.ToString)
End If
Return True
End Function
Sorry I'm quite new to vb.net.. but I'm learning thanks!
This question is basically regarding to loop all workbooks in all excel instances!
Your main issue you are facing is you are not using any of the process's you come across. Therefore, you will not get anything that way. Inside of the loop for the process's you then create a new instance of ExcelApplication and then try to loop through the Workbooks. By default when you do this there is only 1 at that time, hence why you get only 1 Workbook and also why you will only ever see 1 Workbook.
Solution (Tried & Tested)
You need to look into Windows API calls to get what you need. A few of them are:
GetDesktopWindow()
EnumChildWindows()
GetClassName()
EnumWindowsProc()
AccessibleObjectFromWindow()
Imports Microsoft.Office.Interop
Imports System.Runtime.InteropServices
Public Class Form1
Private Declare Function GetDesktopWindow Lib "user32" () As Integer
Private Declare Function EnumChildWindows Lib "user32.dll" (ByVal WindowHandle As IntPtr, ByVal Callback As EnumWindowsProc, ByVal lParam As IntPtr) As Boolean
Private Declare Function GetClassName Lib "user32.dll" Alias "GetClassNameA" (ByVal hWnd As IntPtr, ByVal lpClassName As String, ByVal nMaxCount As Integer) As Integer
Private Delegate Function EnumWindowsProc(ByVal hwnd As IntPtr, ByVal lParam As Int32) As Boolean
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal Hwnd As Int32, ByVal dwId As Int32, ByRef riid As Guid, <MarshalAs(UnmanagedType.IUnknown)> ByRef ppvObject As Object) As Int32
Private Const OBJID_NATIVE = &HFFFFFFF0
'Required to show the workbooks. Used in function to add to.
Private lstWorkBooks As New List(Of String)
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
lstWorkBooks.Clear()
GetExcelOpenWorkBooks()
End Sub
Private Sub GetExcelOpenWorkBooks()
Try
'Get handle to desktop
Dim WindowHandle As IntPtr = GetDesktopWindow()
'Enumerate through the windows (objects) that are open
EnumChildWindows(WindowHandle, AddressOf GetExcelWindows, 0)
'List the workbooks out if we have something
If lstWorkBooks.Count > 0 Then MsgBox(String.Join(Environment.NewLine, lstWorkBooks))
Catch ex As Exception
End Try
End Sub
Public Function GetExcelWindows(ByVal hwnd As IntPtr, ByVal lParam As Int32) As Boolean
Dim Ret As Integer = 0
Dim className As String = Space(255) 'Return the string with some padding...
Ret = GetClassName(hwnd, className, 255)
className = className.Substring(0, Ret)
If className = "EXCEL7" Then
Dim ExcelApplication As Excel.Application
Dim ExcelObject As Object = Nothing
Dim IDispatch As Guid
AccessibleObjectFromWindow(hwnd, OBJID_NATIVE, IDispatch, ExcelObject)
'Did we get anything?
If ExcelObject IsNot Nothing Then
ExcelApplication = ExcelObject.Application
'Make sure we have the instance...
If ExcelApplication IsNot Nothing Then
'Go through the workbooks...
For Each wrk As Excel.Workbook In ExcelApplication.Workbooks
'If workbook ins't in the list then add it...
If Not lstWorkBooks.Contains(wrk.Name) Then
lstWorkBooks.Add(wrk.Name)
End If
Next
End If
End If
End If
Return True
End Function
End Class
I am trying to write text to Win32 resources, but I have failed with it.
Here it is after writing the text:
And here is how it should look like:
Here's my code:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
WriteResourceStr(Target.Text, "hello")
End Sub
#Region "Second"
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _
Private Shared Function UpdateResource(ByVal hUpdate As IntPtr, ByVal lpType As String, ByVal lpName As String, ByVal wLanguage As UShort, ByVal lpData As IntPtr, ByVal cbData As UInteger) As Boolean
End Function
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _
Private Shared Function BeginUpdateResource(ByVal pFileName As String, <MarshalAs(UnmanagedType.Bool)> ByVal bDeleteExistingResources As Boolean) As IntPtr
End Function
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _
Private Shared Function EndUpdateResource(ByVal hUpdate As IntPtr, ByVal fDiscard As Boolean) As Boolean
End Function
Public Function WriteResourceStr(ByVal filename As String, ByVal bytes As String) As Boolean
Try
Dim handle As IntPtr = BeginUpdateResource(filename, False)
Dim file1 As String = bytes
Dim fileptr As IntPtr = ToPtr(file1)
Dim res As Boolean = UpdateResource(handle, "RCData", "CONFIG", 1, fileptr, System.Convert.ToUInt16(file1.Length))
EndUpdateResource(handle, False)
Catch ex As Exception
Return False
End Try
Return True
End Function
Private Function ToPtr(ByVal data As Object) As IntPtr
Dim h As GCHandle = GCHandle.Alloc(data, GCHandleType.Pinned)
Dim ptr As IntPtr
Try
ptr = h.AddrOfPinnedObject()
Finally
h.Free()
End Try
Return ptr
End Function
#End Region
So seems like it doesn't write ANSI, but in Unicode. How to change that?
Hopefully somebody replies.
The simplest way to get this is to just overload UpdateResource and let Windows make the Unicode to ANSI conversion for you:
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Ansi)> _
Private Shared Function UpdateResource(ByVal hUpdate As IntPtr, _
ByVal lpType As String, ByVal lpName As String, ByVal wLanguage As UShort,
ByVal lpData As String, ByVal cbData As Integer) As Boolean
End Function
Note the changed lpData type and the changed CharSet. The call now simply becomes:
Dim handle As IntPtr = BeginUpdateResource(filename, False)
If handle = IntPtr.Zero Then Throw New Win32Exception
Dim res As Boolean = UpdateResource(handle, "RCData", "CONFIG", 1, _
bytes, bytes.Length)
If Not EndUpdateResource(handle, False) Then Throw New Win32Exception
I'll have to restate the nonsensical nature of the call. RCData is a numbered resource type and not a string. Using a language ID of 1 makes little sense, that's Arabic so you wouldn't expect a Latin string in the resource. Whatever app reads this resource is unlikely to find it back.
Doing this correctly would require an overload that declares lpType as IntPtr so you can pass CType(10, IntPtr) for the RT_RCData resource type. The ToPtr() function is extremely evil, it returns a dangling pointer that will cause random data corruption. Just let the pinvoke marshaller generate the pointer by declaring the lpData argument as Byte(). You'd then use Encoding.GetBytes() to use the proper ANSI conversion. Thus:
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _
Private Shared Function UpdateResource(ByVal hUpdate As IntPtr, _
ByVal lpType As IntPtr, ByVal lpName As String, ByVal wLanguage As UShort,
ByVal lpData As Byte(), ByVal cbData As Integer) As Boolean
End Function
With an additional overload required if lpName is a numbered instead of a named resource, use IntPtr.
I can get text from external application text box but now I want to get text from my desired text box from external application.
My English is not so good that's why see Image Below.
The Below Code Return The First Text Box Value Only.
Imports System.Runtime.InteropServices
Public Class Form1
Private Const WM_GETTEXT As Integer = &HD
Declare Auto Function SendMessage Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal msg As Integer, _
ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, _
ByVal childAfter As IntPtr, _
ByVal lclassName As String, _
ByVal windowTitle As String) As IntPtr
End Function
Declare Auto Function FindWindow Lib "user32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'Find the running notepad window
Dim Hwnd As IntPtr = FindWindow(Nothing, TextBox1.Text)
'Alloc memory for the buffer that recieves the text
Dim Handle As IntPtr = Marshal.AllocHGlobal(100)
'send WM_GWTTEXT message to the notepad window
Dim NumText As Integer = SendMessage(Hwnd, WM_GETTEXT, 50, Handle)
'copy the characters from the unmanaged memory to a managed string
Dim Text As String = Marshal.PtrToStringUni(Handle)
'Display the string using a label
Label1.Text = Text
'Find the Edit control of the Running Notepad
Dim ChildHandle As IntPtr = FindWindowEx(Hwnd, IntPtr.Zero, "Edit", Nothing)
'Alloc memory for the buffer that recieves the text
Dim Hndl As IntPtr = Marshal.AllocHGlobal(200)
'Send The WM_GETTEXT Message
NumText = SendMessage(ChildHandle, WM_GETTEXT, 200, Hndl)
'copy the characters from the unmanaged memory to a managed string
Text = Marshal.PtrToStringUni(Hndl)
'Display the string using a label
Label2.Text = Text
End Sub
End Class
You'll have to loop through children of the main window (External Application) and get their properties.
You'll use the following:
<DllImport("User32.dll")> _
Public Function EnumChildWindows _
(ByVal WindowHandle As IntPtr, ByVal Callback As EnumWindowProcess, _
ByVal lParam As IntPtr) As Boolean
End Function
Public Delegate Function EnumWindowProcess(ByVal Handle As IntPtr, ByVal Parameter As IntPtr) As Boolean
Public Function GetChildWindows(ByVal ParentHandle As IntPtr) As IntPtr()
Dim ChildrenList As New List(Of IntPtr)
Dim ListHandle As GCHandle = GCHandle.Alloc(ChildrenList)
Try
EnumChildWindows(ParentHandle, AddressOf EnumWindow, GCHandle.ToIntPtr(ListHandle))
Finally
If ListHandle.IsAllocated Then ListHandle.Free()
End Try
Return ChildrenList.ToArray
End Function
For for details, check this How can I get properties of controls contained in a popup message box using VB.Net
' try this on excel vbe
External_application_handle=findwindow(vbNullString,"External_application")
textbox_1_handle=findwindowex(External_application_handle,0&,"Edit",vbNullString)
next_handle=textbox_1_handle
textbox_2_handle=findwindowex(External_application_handle,next_handle,"Edit",vbNullString")
Length = SendMessage(textbox_2_handle, WM_GETTEXTLENGTH, 0,0)
buffer$=space(Length)
call sendmessage(textbox_2_handle,Length+1,buffer$)
msgbox buffer