В общем, отлаживал библиотеку. решил поставить музыку которая идет в потоке на паузу, и отойти на минут 10. Пришел, и увидел нечто невероятное для меня... При продолжении воспроизведения, неуправляемая библиотека попыталась вызвать делегат, который я передавал ей, и при попытке продолжить воспроизведение потока, вылетело исключение о том, что неуправляемая библиотека попыталась вызвать делегат который был собран сборщиком мусора.
Как избежать сборки этого переданного делагата?
Код базового класса:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void FreeDelegate(IntPtr cObjectPtr);
internal abstract class CObject : ICObject
{
private FreeDelegate _free;
protected CObject(IntPtr cObjectPtr, FreeDelegate freeDelegate, Action afterObjectIsSet = null)
{
if (IntPtr.Zero == cObjectPtr)
{
throw new InvalidEnumArgumentException(nameof(cObjectPtr));
}
CObjectPtr = cObjectPtr;
_free = freeDelegate ??
throw new ArgumentNullException(nameof(freeDelegate), "freeDelegate cannot be NULL!");
afterObjectIsSet?.Invoke();
GC.KeepAlive(this);
}
public IntPtr CObjectPtr { get; protected set; }
public bool IsDisposed { get; private set; }
public bool IsInvalid => IntPtr.Zero == CObjectPtr;
public static implicit operator IntPtr(CObject currentObject)
{
return currentObject.CObjectPtr;
}
#region IDisposable Support
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
_free?.Invoke(CObjectPtr);
if (disposing)
{
_free = null;
CObjectPtr = IntPtr.Zero;
}
IsDisposed = true;
}
}
~CObject()
{
Dispose(false);
#if DEBUG
Debug.WriteLine("[{0}] -> ~CObject(), Collected by GC.", this);
#endif
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
Как видно, в конструкторе вызывается KeepAlive
.
Далее, представлю объект класса-наследника, метод которого передается в неуправляемый код:
[SuppressUnmanagedCodeSecurity]
internal sealed class Media : CObject, IMedia
{
private readonly IntPtr _mediaEventManager;
protected override void Dispose(bool disposing)
{
DetachEvents();
NativeMethods.Media.Release(this);
base.Dispose(disposing);
}
internal Media(CObject factory, string location, bool isLocal = false) :
this(factory, location.ToCString(), isLocal)
{
}
private Media(IntPtr instance, IntPtr pszMrl, bool isLocal) :
this(NativeMethods.Media.New(instance, pszMrl, isLocal), () => pszMrl.FreeCString())
{
}
internal Media(IntPtr mediaPtr, Action postAction = null) :
base(mediaPtr, NativeMethods.Media.Release, postAction)
{
_mediaEventManager = NativeMethods.Media.GetEventManager(this);
AttachEvents();
NativeMethods.Media.Retain(this);
}
private unsafe void AttachEvents()
{
for (EventType i = EventType.MediaSubItemTreeAdded; i >= EventType.MediaMetaChanged; i--)
{
NativeMethods.EventManager.Attach(_mediaEventManager, i, EventHandler);
}
}
private unsafe void DetachEvents()
{
for (EventType i = EventType.MediaSubItemTreeAdded; i >= EventType.MediaMetaChanged; i--)
{
NativeMethods.EventManager.Detach(_mediaEventManager, i, EventHandler);
}
}
private unsafe void EventHandler(Event* e, IntPtr userData)
{
switch (e->Type)
{
case EventType.MediaMetaChanged:
OnMetaChanged(new MediaMetaChangedEventArgs(e->Data.Media.MetaChanged.MetaType));
break;
case EventType.MediaSubItemAdded:
OnSubItemAdded(new MediaSubItemAddedEventArgs(new Media(e->Data.Media.SubItemAdded.NewChild)));
break;
case EventType.MediaDurationChanged:
OnDurationChanged(new MediaDurationChangedEventArgs(e->Data.Media.DurationChanged.NewDuration));
break;
case EventType.MediaParsedChanged:
OnParsedChanged(new MediaParsedChangedEventArgs(e->Data.Media.ParsedChanged.NewStatus != 0));
break;
case EventType.MediaFreed:
Console.WriteLine("Media {0} no more valid.", e->Data.Media.Freed.MediaPtr);
break;
case EventType.MediaStateChanged:
OnStateChanged(new MediaStateChangedEventArgs(e->Data.Media.StateChanged.NewState));
break;
case EventType.MediaSubItemTreeAdded:
OnSubItemTreeAdded(new MediaSubItemTreeAddedEventArgs(new Media(e->Data.Media.SubItemTreeAdded.NewItem)));
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public event EventHandler<MediaStateChangedEventArgs> StateChanged;
public event EventHandler<MediaMetaChangedEventArgs> MetaChanged;
public event EventHandler<MediaDurationChangedEventArgs> DurationChanged;
public event EventHandler<MediaParsedChangedEventArgs> ParsedChanged;
public event EventHandler<MediaSubItemAddedEventArgs> SubItemAdded;
public event EventHandler<MediaSubItemTreeAddedEventArgs> SubItemTreeAdded;
public Uri Location
{
get
{
unsafe
{
sbyte* mrl = NativeMethods.Media.GetMrl(this);
if (null == mrl)
{
return null;
}
sbyte* mrlCpy = mrl;
int size = 0;
while (*mrlCpy != 0)
{
mrlCpy++;
size++;
}
byte[] bytes = new byte[size];
Marshal.Copy((IntPtr) mrl, bytes, 0, size);
return new Uri(Encoding.UTF8.GetString(bytes));
}
}
}
public State State => NativeMethods.Media.GetState(this);
public void Parse()
{
NativeMethods.Media.Parse(this);
}
public void ParseAsync()
{
NativeMethods.Media.ParseAsync(this);
}
public bool GetStats(out IMediaStats stats)
{
// FIXME: here
throw new NotImplementedException();
}
public TimeSpan Duration => TimeSpan.FromMilliseconds(NativeMethods.Media.GetDuration(this));
public bool IsParsed => NativeMethods.Media.IsParsed(this) != 0;
public ICollection<IVideoTrack> VideoTracks
{
get
{
unsafe
{
if (!IsParsed)
{
Parse();
}
MediaTrack** tracks = null;
uint count = NativeMethods.Media.GetTracks(this, &tracks);
if (count == 0 || tracks == null)
{
return Array.Empty<IVideoTrack>();
}
LinkedList<IVideoTrack> videoTracks = new LinkedList<IVideoTrack>();
for (int i = 0; i < count; i++)
{
//TODO: please... do not edit next line... it is not possible to get NullReferenceException when we get any objects from C...
IVideoTrack currentTrack = (*(tracks + i))->ToVideoTrack();
if (currentTrack is null)
{
continue;
}
videoTracks.AddLast(currentTrack);
}
NativeMethods.Media.TracksRelease(tracks, count);
return videoTracks;
}
}
}
public ICollection<IAudioTrack> AudioTracks
{
get
{
unsafe
{
if (!IsParsed)
{
Parse();
}
MediaTrack** tracks = null;
uint count = NativeMethods.Media.GetTracks(this, &tracks);
if (count == 0 || tracks == null)
{
return Array.Empty<IAudioTrack>();
}
LinkedList<IAudioTrack> videoTracks = new LinkedList<IAudioTrack>();
for (int i = 0; i < count; i++)
{
//TODO: please... do not edit next line... it is not possible to get NullReferenceException when we get any objects from C...
IAudioTrack currentTrack = (*(tracks + i))->ToAudioTrack();
if (currentTrack is null)
{
continue;
}
videoTracks.AddLast(currentTrack);
}
NativeMethods.Media.TracksRelease(tracks, count);
return videoTracks;
}
}
}
public ICollection<ISubtitleTrack> SubtitleTracks
{
get
{
unsafe
{
if (!IsParsed)
{
Parse();
}
MediaTrack** tracks = null;
uint count = NativeMethods.Media.GetTracks(this, &tracks);
if (count == 0 || tracks == null)
{
return Array.Empty<ISubtitleTrack>();
}
LinkedList<ISubtitleTrack> videoTracks = new LinkedList<ISubtitleTrack>();
for (int i = 0; i < count; i++)
{
//TODO: please... do not edit next line... it is not possible to get NullReferenceException when we get any objects from C...
ISubtitleTrack currentTrack = (*(tracks + i))->ToSubtitleTrack();
if (currentTrack is null)
{
continue;
}
videoTracks.AddLast(currentTrack);
}
NativeMethods.Media.TracksRelease(tracks, count);
return videoTracks;
}
}
}
public IMedia Duplicate()
{
return new Media(NativeMethods.Media.Duplicate(this));
}
private void OnStateChanged(MediaStateChangedEventArgs e)
{
StateChanged?.Invoke(this, e);
}
private void OnMetaChanged(MediaMetaChangedEventArgs e)
{
MetaChanged?.Invoke(this, e);
}
private void OnDurationChanged(MediaDurationChangedEventArgs e)
{
DurationChanged?.Invoke(this, e);
}
private void OnParsedChanged(MediaParsedChangedEventArgs e)
{
ParsedChanged?.Invoke(this, e);
}
private void OnSubItemAdded(MediaSubItemAddedEventArgs e)
{
SubItemAdded?.Invoke(this, e);
}
private void OnSubItemTreeAdded(MediaSubItemTreeAddedEventArgs e)
{
SubItemTreeAdded?.Invoke(this, e);
}
}
В классе Media
, метод EventHandler
передается в неуправляемый код (неявно преобразованный в делегат). При этом, сам объект этого класса, на момент исключения, собран не был, т.к. его состояние было валидным, плюс к тому же, в окне отладки не было сообщения об уничтожении какого либо из объектов посредством вызова финализатора...
Но делегат был уничтожен. Я не понимаю что происходит.
Нужно сохранять ссылку на сам делегат (а не только на объект, чей метод он вызывает) в течении всего времени, пока метод может быть вызван. То есть, этот делегат не должен быть простой локальной переменной того метода, в котором он передаётся в неуправляемый код. Это должна быть внешняя (по отношению к этому методу) переменная, например поле в каком-нибудь объекте, который гарантированно просуществует нужное время.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Как средствами С# на Linux запустить консоль и выполнить в ней определённую команду (в моём случае это запуск/остановка/проверка статуса службы...