Tengo una fuga de memória grave en una aplicación, y es debido a una Class de terceros que estoy usando...
Este simple código aloja entre 100-200kb de RAM cada segundo :(
Public Class Form1
Dim isbinded As Boolean = False
Dim Winamp As clsWACC = New clsWACC
Dim WithEvents mytimer As New Timer With {.Interval = 50, .Enabled = True}
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles mytimer.Tick
isbinded = Winamp.Bind()
' Label1.Text = isbinded
' isbinded = Nothing
End Sub
End Class
Necesito usar un Timer rápido de menos de 50 ms, pero simplemente por curiosidad si aumento el Timer a 1000 ms la ram sigue subiendo sin pausa, aunque sube sólo 8 kb cada segundo.
He intentado examinar y corregir la Class pero no encuentro el fallo, esta es la parte de la Class que manipula el code de arriba:
Public Sub New(ByVal handle As IntPtr, ByVal str As String)
'opens the process
hWinamp = OpenProcess(DAccess.PROCESS_ALL_ACCESS, False, waPID)
'If hWinamp.Equals(IntPtr.Zero) Then
' 'exception
'End If
remStr = str
remoteBuf = AllocWinamp(handle, Convert.ToUInt32(str.Length + 1))
Dim localBuf As IntPtr = Marshal.StringToHGlobalAnsi(str)
WriteProcessMemory(hWinamp, remoteBuf, localBuf, str.Length + 1, Nothing)
Marshal.FreeHGlobal(localBuf)
End Sub
Dim path As String = Nothing
Dim regKey As RegistryKey = Registry.CurrentUser.OpenSubKey("Software\Winamp", False)
'Binds to WinAmp
Public Function Bind() As Boolean
Return Bind("")
End Function
Public Function Bind(ByVal PathToExecutable As String) As Boolean
hWnd_Winamp = WinAPI.FindWindow(lpClassName, Nothing)
'waProcess = New Process
waProcess.EnableRaisingEvents = True
'If WinAmp window handle not found, try to launch it
If hWnd_Winamp.Equals(IntPtr.Zero) Then
'if path was not specified, try to find it in the Windows registry
If PathToExecutable = "" Then
'Dim path As String
'regKey = Registry.CurrentUser
'regKey = regKey.OpenSubKey("Software\Winamp", False)
If regKey IsNot Nothing Then
path = Convert.ToString(regKey.GetValue(""))
waProcess.StartInfo.FileName = path & "\Winamp.exe"
regKey.Close()
Else
Return False
End If
Else
waProcess.StartInfo.FileName = PathToExecutable
End If
Try
waProcess.Start()
Catch ex As System.ComponentModel.Win32Exception When ex.ErrorCode = -2147467259
Debug.WriteLine("Executable not found")
Return False
Catch ex As Exception
Debug.WriteLine("unknown exception")
Return False
End Try
waProcess.WaitForInputIdle()
hWnd_Winamp = waProcess.MainWindowHandle()
waPID = waProcess.Id
Else
'WinAmp handle found
'now bind to WinAmp process
'get PID from hWnd
WinAPI.GetWindowThreadProcessId(hWnd_Winamp, waPID)
If waPID = 0 Then
Return False
End If
waProcess = Process.GetProcessById(waPID)
waProcess.EnableRaisingEvents = True
hWnd_Playlist = GetHWND(WinampWindow.Playlist)
hWnd_Equalizer = GetHWND(WinampWindow.Equalizer)
hWnd_Video = GetHWND(WinampWindow.Video)
Return True
End If
'if hWnd of the main window is still zero,
'it was not possible to launch or bind to WinAmp
If hWnd_Winamp.Equals(IntPtr.Zero) Then
Return False
Else
hWnd_Playlist = GetHWND(WinampWindow.Playlist)
hWnd_Equalizer = GetHWND(WinampWindow.Equalizer)
hWnd_Video = GetHWND(WinampWindow.Video)
Return True
End If
End Function
Pero la primera condición no se cumple, es decir, solo se manipula esta parte de la función Bind:
If hWnd_Winamp.Equals(IntPtr.Zero) Then
' NADA
Else
'WinAmp handle found
'now bind to WinAmp process
'get PID from hWnd
WinAPI.GetWindowThreadProcessId(hWnd_Winamp, waPID)
If waPID = 0 Then
Return False
End If
waProcess = Process.GetProcessById(waPID)
waProcess.EnableRaisingEvents = True
hWnd_Playlist = GetHWND(WinampWindow.Playlist)
hWnd_Equalizer = GetHWND(WinampWindow.Equalizer)
hWnd_Video = GetHWND(WinampWindow.Video)
Return True
End If
hWnd_Winamp es un IntPtr
waProcess es un Process (el cual he probado a liberarlo pero sigue pasando lo mismo...)
waPID es un Integer
hWnd_Playlist es un IntPtr
hWnd_Equalizer es un IntPtr
hWnd_Video es un IntPtr
...y GetWindowThreadProcessId es esta función:
'======================== =========================
'====================== GetWindowThreadProcessId =======================
'======================== =========================
'Retrieves the identifier of the thread that created the specified window
'and, optionally, the identifier of the process that created the window.
<DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function GetWindowThreadProcessId( _
ByVal hwnd As IntPtr, _
ByRef lpdwProcessId As Integer) As Int32
End Function
y esta la función GetHWND:
Private Shared Function GetHWND(ByVal Window As WinampWindow) As IntPtr
Return SendWA_IPC(Window, Message.IPC_GETWND)
End Function
Private Shared Function SendWA_IPC(ByVal param As Int64, ByVal MessageName As Message) As IntPtr
Return WinAPI.SendMessage(hWnd_Winamp, WinAPI.Msg.WM_USER, IntPtr.op_Explicit(param), IntPtr.op_Explicit(MessageName))
End Function
'=============================== ===============================
'============================= SendMessage =============================
'=============================== ===============================
'Sends the specified message to a window or windows. It calls the window
'procedure for the specified window and does not return until the window
'procedure has processed the message.
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Public Shared Function SendMessage(ByVal hWnd As IntPtr, _
ByVal Msg As Msg, _
ByVal wParam As IntPtr, _
ByVal lParam As IntPtr) As IntPtr
End Function
¿Alguna sugerencia?
Gracias...
Hay algo que no entiendo y es ... ¿Por que ese necesario buscar cada 50 ms la ventana y el proceso de Winamp? Está claro que es algo muy costoso, ¿no alcanza con obtenerlo una vez?
Saludos
Cita de: Novlucker en 7 Agosto 2013, 17:17 PM
Hay algo que no entiendo y es ... ¿Por que ese necesario buscar cada 50 ms la ventana y el proceso de Winamp? Está claro que es algo muy costoso
La intención de repetirlo cada 50 ms es para detectar cuando el Winamp está en modo "Pausa" o en "Detenido", o para detectar si el proceso sigue activo o si ya no se está ejecutando el Winamp, si se te ocurre una forma más eficiente plz dímelo.
EDITO:Para que entiendas lo que hago, tengo algo así:
Dim WinampIsRunning = winamp.bind() ' Esto devuelve True si el proceso de Winamp se está ejecutando
If WinampIsRunning then
if winamp.state = winamp.state.Paused then...
' actualizar posición del trackbar, labels, textboxes, etc...
elseif winamp.state = winamp.state.Paused then...
elseif winamp.state = winamp.state.Stoped then...
end if
PD: ¿Entonces tu tampoco encuentras el problema en el code de arriba?
Saludos
¿No hay un evento en esa clase para detectar el cambio de estado?
En cuanto al proceso por ejemplo. Lo obtienes todas las veces y seteas EnableRaisingEvents en true, lo que hay que hacer es obtener el proceso, setear EnableRaisingEvents en true, suscribirse al evento Exited, y dejar de buscar el proceso. Cuando se dispara el evento Exited, entonces volvemos a buscar.
Saludos
Cita de: Novlucker en 7 Agosto 2013, 17:17 PM¿Por que ese necesario buscar cada 50 ms la ventana y el proceso de Winamp? Está claro que es algo muy costoso
Aún así ese trozo de código de la Class genera memoria sin cesar, aunque se use un Timer que tickee cada 10 segundos que llame a la función Bind, la cantidad de memoria en ese caso será minima pero al fin y al cabo incesante, hay algo que está mal en ese code.
Cita de: Novlucker en 7 Agosto 2013, 17:55 PM¿No hay un evento en esa clase para detectar el cambio de estado?
En cuanto al proceso por ejemplo. Lo obtienes todas las veces y seteas EnableRaisingEvents en true, lo que hay que hacer es obtener el proceso, setear EnableRaisingEvents en true, suscribirse al evento Exited, y dejar de buscar el proceso. Cuando se dispara el evento Exited, entonces volvemos a buscar.
la Class del Winamp son 2000 o 3000 lineas de código, no me lo he mirado todo, eso es lo próximo que haré y ya te cuento.
Pero en principio te puedes fijar si tiene un evento, solo es poner punto en el nombre de instancia de clase y buscar el que tiene un "rayito" :xD
Saludos
Cita de: Novlucker en 7 Agosto 2013, 19:39 PMPero en principio te puedes fijar si tiene un evento, solo es poner punto en el nombre de instancia de clase y buscar el que tiene un "rayito" :xD
Si pero si el evento fuese privado por cualquier motivo pues no me iba a salir en el intellisense, es que en la class hay muchos "Tests" como cosas de prueba y cosas por mejorar, no está del todo perfeccionado así que tenía que usar el buscador de la IDE para examinar la class a fondo xD.
Citar¿No hay un evento en esa clase para detectar el cambio de estado?
No, ninguno, hay que crear el evento o detectar el estado a lo cutre, mediante condiciones (if's).
En fín Novlucker he seguido tus indicaciones para lo del proceso:
En la class del winamp:
Private Sub waProcess_Exited(ByVal sender As Object, ByVal e As EventArgs) Handles waProcess.Exited
RaiseEvent WinampExited()
End Sub
En la class de mi form:
Private Sub Exited() Handles Winamp.WinampExited
Winamp_IsRunning = False
End Sub
Private Sub Monitor_Timer_Tick(sender As Object, e As EventArgs) Handles Monitor_Timer.Tick
If Not Winamp_IsRunning Then Winamp_IsRunning = Winamp.Bind()
End sub
Se nota mucho el cambio, gracias, aunque como ya digo la función Bind de esa class tiene algún problema así que por mucho que yo intente perfeccionar estas minucias no va a dejar de subir la RAM xD, aunque ahora he conseguido que suba reálmente muy poco a poco, el consumo de ram hace subidas y bajadas que no me gustan nada, pero bueno parece estar equilibrado, sube un poco y al rato como que el GC hace su trabajo, lo libera y vuelve a bajar el consumo, y vuelve a subir, en fin xD. (Todo esto dejando el proceso en "StandBy")
Saludos
Yo creo que el problema está no en la función, sino en ejecutar esa función constantemente tan rápido. Se crean más datos de los que se alcanzan a liberar.
Te pongo un ejemplo,
using System;
using System.Diagnostics;
using System.Timers;
namespace ConsoleApplication6
{
class Program
{
static void Main(string[] args)
{
Timer timer = new Timer(50); //Para una aplicacion de consola toca usar este Timer
//http://msdn.microsoft.com/en-us/magazine/cc164015.aspx
timer.Elapsed += new ElapsedEventHandler(Run);
timer.Start();
Console.ReadLine();
}
static void Run(object sender, EventArgs e)
{
Console.WriteLine(DateTime.Now);
for (int i = 0; i < 2000; i++)
{
Process.GetProcesses();
}
}
}
}
El código es muy simple, pero si lo ejecutas verás como la RAM va aumentando progresivamente :P
Saludos
Cita de: Novlucker en 7 Agosto 2013, 20:31 PMYo creo que el problema está no en la función, sino en ejecutar esa función constantemente tan rápido. Se crean más datos de los que se alcanzan a liberar.
Lo que dices es muy lógico, es lo primero que se llega a pensar, puede que si que quizás en 50 ms se generen más datos de los que se pueden liberar, pero además de eso estoy seguro de que hay una fuga en esa función, y la prueba definitiva la di al principio, con este code:
Public Class Form1
Dim isbinded As Boolean = False
Dim Winamp As clsWACC = New clsWACC
Dim WithEvents mytimer As New Timer With {.Interval = 50, .Enabled = True}
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles mytimer.Tick
isbinded = Winamp.Bind()
' Label1.Text = isbinded
' isbinded = Nothing
End Sub
End Class
Símplemente el Test consiste en crear una APP con ese código (y adjuntar la dll de la función Bind claro xD).
Repito que si uso un Timer de 10
segundos (o 20, o los que sean) la RAM sigue subiendo progresívamente, solo que se generarán los bytes mucho más lento porque el timer es más lento así que hay que dejar la app un buen rato corriendo para apreciar el aumentado del consumo de RAM, pero en fín yo creo que en 10-20 segundos el GC tiene tiempo de liberar...
Saludos!
Bueno creo que puedo dar este tema por solucionado...
me ha costado mucho tiempo corregir cada parte del código de la aplicación que tenían fugas de memória pero al final he estabilizado el consumo y ya no aumenta progresívamente, de los 12 MB no pasa nunca el consumo ahora,
ya puedo dejar de colectar manuálmente con el GC y de usar API's para flushear el consumo de memória de la APP xD
Gracias por tu tiempo Novlucker.
Lo único que me gustaría mejorar sobre el tema de la memória es lo que comento aquí: Tema: Experimento de consumo de memória... ver para creer! (Leído 58 veces)
(http://foro.elhacker.net/net/experimento_de_consumo_de_memoria_ver_para_creer-t396464.0.html)
Saludos!