Domanda

Sto tentando di creare un plugin di visualizzazione di Windows Media Player (WMP) in C #. Sono abbastanza nuovo per esporre C # per COM e forse ho perso qualcosa di fondamentale. Ho insistito con questo per 3 giorni (circa 20 ore) e non ha ottenuto oltre il singolo problema che descriverò di seguito.

Per chi non lo sapesse, visualizzazioni WMP sono le belle immagini che mostrano in media player mentre si ascolta la musica.

In poche parole :. WMP chiamerà alcuni metodi su mia interfaccia # COM C, ma non altri

Ho installato WMP 11

Ho scaricato il recente SDK di Windows che contiene un plug-in C ++ wizard per compilare un campione visualizzazione di lavoro. Questo campione registri e funziona senza problemi in WMP.

Il kit dev contiene un file di intestazione C ++ chiamato effects.h che contiene 2 interfacce che devono essere attuati per ottenere il plugin per lavorare con WMP. Non sembra molto più complicato di così.

Sono qui

MIDL_INTERFACE("D3984C13-C3CB-48e2-8BE5-5168340B4F35")
IWMPEffects : public IUnknown
{
public:
    virtual /* [helpstring][local] */ HRESULT STDMETHODCALLTYPE Render( 
        /* [in] */ TimedLevel *pLevels,
        /* [in] */ HDC hdc,
        /* [in] */ RECT *prc) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE MediaInfo( 
        /* [in] */ LONG lChannelCount,
        /* [in] */ LONG lSampleRate,
        /* [in] */ BSTR bstrTitle) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetCapabilities( 
        /* [out] */ DWORD *pdwCapabilities) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetTitle( 
        /* [out] */ BSTR *bstrTitle) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetPresetTitle( 
        /* [in] */ LONG nPreset,
        /* [out] */ BSTR *bstrPresetTitle) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetPresetCount( 
        /* [out] */ LONG *pnPresetCount) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SetCurrentPreset( 
        /* [in] */ LONG nPreset) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetCurrentPreset( 
        /* [out] */ LONG *pnPreset) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE DisplayPropertyPage( 
        /* [in] */ HWND hwndOwner) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GoFullscreen( 
        /* [in] */ BOOL fFullScreen) = 0;

    virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE RenderFullScreen( 
        /* [in] */ TimedLevel *pLevels) = 0;

};

MIDL_INTERFACE("695386EC-AA3C-4618-A5E1-DD9A8B987632")
IWMPEffects2 : public IWMPEffects
{
public:
    virtual HRESULT STDMETHODCALLTYPE SetCore( 
        /* [in] */ IWMPCore *pPlayer) = 0;

    virtual HRESULT STDMETHODCALLTYPE Create( 
        /* [in] */ HWND hwndParent) = 0;

    virtual HRESULT STDMETHODCALLTYPE Destroy( void) = 0;

    virtual HRESULT STDMETHODCALLTYPE NotifyNewMedia( 
        /* [in] */ IWMPMedia *pMedia) = 0;

    virtual HRESULT STDMETHODCALLTYPE OnWindowMessage( 
        /* [in] */ UINT msg,
        /* [in] */ WPARAM WParam,
        /* [in] */ LPARAM LParam,
        /* [in] */ LRESULT *plResultParam) = 0;

    virtual HRESULT STDMETHODCALLTYPE RenderWindowed( 
        /* [in] */ TimedLevel *pData,
        /* [in] */ BOOL fRequiredRender) = 0;

};

Come ho detto, la mia conoscenza com Il mio non essere il migliore. Questo è quello che ho fatto per crearlo in C #.

ho convertito le interfacce a seguire

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace WmpTestPlugin
{

    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("D3984C13-C3CB-48e2-8BE5-5168340B4F35")]
    public interface IWmpEffects
    {
        int Render(ref TimedLevel pLevels, IntPtr Hdc, ref RECT pRC);
        int MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle);
        int GetCapabilities(ref int pdwCapabilities);
        int GetTitle(ref string bstrTitle);
        int GetPresetTitle([In] int nPreset, [MarshalAs(UnmanagedType.BStr)] ref string bstrPresetTitle);
        int GetPresetCount(ref int count);
        int SetCurrentPreset(int currentpreset);
        int GetCurrentPreset(ref int currentpreset);
        int DisplayPropertyPage(IntPtr hwndOwner);
        int GoFullScreen(bool fFullscreen);
        int RenderFullScreen(ref TimedLevel pLevels);
    }

    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("695386EC-AA3C-4618-A5E1-DD9A8B987632")]
    public interface IWmpEffects2 : IWmpEffects
    {
        int SetCore(IntPtr pPlayer);
        int Create(IntPtr hwndParent);
        int Destroy();
        int NotifyNewMedia(IntPtr pMedia);
        int OnWindowMessage(int Msg, int WParam, int LParam, ref int plResultParam);
        int RenderWindowed(ref TimedLevel pData, bool fRequiredRender);
    }

    [ComVisible(true)]
    [StructLayout(LayoutKind.Sequential)]
    public struct Data
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
        public byte[] Data0;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
        public byte[] Data1;
    }

    [ComVisible(true)]
    public enum PlayerState
    {
        Stop_State,
        Pause_State,
        Play_State
    }

    [ComVisible(true)]
    [StructLayout(LayoutKind.Sequential)]
    public struct TimedLevel
    {
        public Data Frequency;
        public Data Waveform;
        public PlayerState State;
        public long TimeStamp;
    }

    [ComVisible(true)]
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
}

Il codice della classe implementando le interfacce è la seguente

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using WmpTestPlugin;
using System.IO;
using System.Windows.Forms;



    [ComVisible(true)]
    [Guid("C476FF24-5E5C-419d-9110-05EC2EED8511")]
    //[ProgId("WmpTestPlugin.WmpTest")]
    [ClassInterface(ClassInterfaceType.None)]
    public class TestPlugin : IWmpEffects2
    {
        [DllImport("user32.dll", EntryPoint = "GetClientRect")]
        private static extern bool getClientRect(IntPtr windowHandle, ref IntPtr rectangle);

        private const int EFFECT_CANGOFULLSCREEN = 1;
        private const int EFFECT_HASPROPERTYPAGE = 2;

        private const int S_OK = 0;
        private const int S_FALSE = 1;
        private const int E_ABORT = unchecked((int)0x80004004);
        private const int E_ACCESSDENIED = unchecked((int)0x80070005);
        private const int E_FAIL = unchecked((int)0x80004005);
        private const int E_HANDLE = unchecked((int)0x80070006);
        private const int E_INVALIDARG = unchecked((int)0x80070057);
        private const int E_NOINTERFACE = unchecked((int)0x80004002);
        private const int E_NOTIMPL = unchecked((int)0x80004001);
        private const int E_OUTOFMEMORY = unchecked((int)0x8007000E);
        private const int E_POINTER = unchecked((int)0x80004003);
        private const int E_UNEXPECTED = unchecked((int)0x8000FFFF);


        public TestPlugin()
        {
            _parentHwnd = IntPtr.Zero;
            _preset = 0;
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Construct{1}", DateTime.Now.ToString(), Environment.NewLine));

        }

        ~TestPlugin()
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Deconstruct{1}", DateTime.Now.ToString(), Environment.NewLine));
        }

        #region IWmpEffects2 Members

        /// <summary>
        /// Set WMP core interface
        /// </summary>
        /// <param name="pPlayer"></param>
        /// <returns></returns>
        public int SetCore(IntPtr pPlayer)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : SetCore{1}", DateTime.Now.ToString(), Environment.NewLine));

            // release any existing WMP core interfaces
            //ReleaseCore();

            if (pPlayer == IntPtr.Zero)
                return S_OK;

            _playerCore = pPlayer;

            //connect up any events
            return S_OK;
        }

        /// <summary>
        /// Invoked when the visualization should be initialized.
        ///
        /// If hwndParent != NULL, RenderWindowed() will be called and the visualization
        /// should draw into the window specified by hwndParent. This will be the
        /// behavior when the visualization is hosted in a window.
        ///
        /// If hwndParent == NULL, Render() will be called and the visualization
        /// should draw into the DC passed to Render(). This will be the behavior when
        /// the visualization is hosted windowless (like in a skin for example).
        /// </summary>
        /// <param name="hwndParent"></param>
        /// <returns></returns>
        public int Create(IntPtr hwndParent)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Create{1}", DateTime.Now.ToString(), Environment.NewLine));

            _parentHwnd = hwndParent;
            return S_OK;
        }

        /// <summary>
        /// Invoked when the visualization should be released.
        /// Any resources allocated for rendering should be released.
        /// </summary>
        /// <returns></returns>
        public int Destroy()
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Destroy{1}", DateTime.Now.ToString(), Environment.NewLine));
            _parentHwnd = IntPtr.Zero;
            return S_OK;
        }

        /// <summary>
        /// Invoked when a new media stream begins playing.
        /// The visualization can inspect this object for properties (like name or artist)
        /// that might be interesting for visualization.
        /// </summary>
        /// <param name="pMedia"></param>
        /// <returns></returns>
        public int NotifyNewMedia(IntPtr pMedia)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : NotifyNewMedia{1}", DateTime.Now.ToString(), Environment.NewLine));
            return S_OK;
        }

        /// <summary>
        /// Window messages sent to the parent window.
        /// </summary>
        /// <param name="Msg"></param>
        /// <param name="WParam"></param>
        /// <param name="LParam"></param>
        /// <param name="plResultParam"></param>
        /// <returns></returns>
        public int OnWindowMessage(int Msg, int WParam, int LParam, ref int plResultParam)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : OnWindowMessage{1}", DateTime.Now.ToString(), Environment.NewLine));
            // return S_OK only if the plugin has handled the window message
            // return S_FALSE to let the defWindowProc handle the message

            //if (_parentHwnd == IntPtr.Zero)
                //m_NonWindowedRenderer.OnWindowMessage(&m_RenderContext, msg, WParam, LParam, plResultParam);
            //else
            //    m_WindowedRenderer.OnWindowMessage(&m_RenderContext, msg, WParam, LParam, plResultParam);

            return S_FALSE;
        }

        /// <summary>
        /// Called when an effect should render itself to the screen.
        /// The fRequiredRender flag specifies if an update is required, otherwise the
        /// update is optional. This allows visualizations that are fairly static (for example,
        /// album art visualizations) to only render when the parent window requires it,
        /// instead of n times a second for dynamic visualizations.
        /// </summary>
        /// <param name="pData"></param>
        /// <param name="fRequiredRender"></param>
        /// <returns></returns>
        public int RenderWindowed(ref TimedLevel pData, bool fRequiredRender)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : RenderWindowed{1}", DateTime.Now.ToString(), Environment.NewLine));
            //windowed

            // NULL parent window should not happen 
            if (_parentHwnd == IntPtr.Zero)
                return E_UNEXPECTED;

            //RECT rect = new RECT();

            //TestPlugin.getClientRect(_parentHwnd, ref rect);

            //do render//

            return S_OK;
        }

        #endregion

        #region IWmpEffects Members

        /// <summary>
        /// Called when an effect should render itself to the screen.
        /// </summary>
        /// <param name="pLevels"></param>
        /// <param name="Hdc"></param>
        /// <param name="pRC"></param>
        /// <returns></returns>
        public int Render(ref TimedLevel pLevels, IntPtr Hdc, ref RECT pRC)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Render{1}", DateTime.Now.ToString(), Environment.NewLine));
            //not windowed


            //do render
            return S_OK;
        }

        /// <summary>
        /// Everytime new media is loaded, this method is called to pass the
        /// number of channels (mono/stereo), the sample rate of the media, and the
        /// title of the media
        /// </summary>
        /// <param name="lChannelCount"></param>
        /// <param name="lSampleRate"></param>
        /// <param name="bstrTitle"></param>
        /// <returns></returns>
        public int MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : MediaInfo{1}", DateTime.Now.ToString(), Environment.NewLine));
            return S_OK;
        }

        /// <summary>
        /// Returns the capabilities of this effect. Flags that can be returned are:
        /// EFFECT_CANGOFULLSCREEN  -- effect supports full-screen rendering
        /// EFFECT_HASPROPERTYPAGE  -- effect supports a property page
        /// </summary>
        /// <param name="pdwCapabilities"></param>
        /// <returns></returns>
        public int GetCapabilities(ref int pdwCapabilities)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetCapabilities{1}", DateTime.Now.ToString(), Environment.NewLine));
            //no capabilities
            pdwCapabilities = EFFECT_HASPROPERTYPAGE;

            return S_OK;
        }

        /// <summary>
        /// Invoked when a host wants to obtain the title of the effect
        /// </summary>
        /// <param name="bstrTitle"></param>
        /// <returns></returns>
        public int GetTitle(ref string bstrTitle)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetTitle{1}", DateTime.Now.ToString(), Environment.NewLine));
            bstrTitle = "Test Wmp C# Plugin";
            return S_OK;
        }

        /// <summary>
        /// Invoked when a host wants to obtain the title of the given preset
        /// </summary>
        /// <param name="nPreset"></param>
        /// <param name="bstrPresetTitle"></param>
        /// <returns></returns>
        public int GetPresetTitle(int nPreset, ref string bstrPresetTitle)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetPresetTitle{1}", DateTime.Now.ToString(), Environment.NewLine));
            //bstrPresetTitle = "Default";
            return S_OK;
        }

        /// <summary>
        /// Invoked when a host wants to obtain the number of supported presets
        /// </summary>
        /// <param name="count"></param>
        /// <returns></returns>
        public int GetPresetCount(ref int count)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetPresetCount{1}", DateTime.Now.ToString(), Environment.NewLine));
            count = 1;
            return S_OK;
        }



        /// <summary>
        /// Invoked when a host wants to obtain the index of the current preset
        /// </summary>
        /// <param name="currentpreset"></param>
        /// <returns></returns>
        public int SetCurrentPreset(int currentpreset)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : SetCurrentPreset{1}", DateTime.Now.ToString(), Environment.NewLine));
            _preset = currentpreset;
            return S_OK;
        }

        /// <summary>
        /// Invoked when a host wants to obtain the index of the current preset
        /// </summary>
        /// <param name="currentpreset"></param>
        /// <returns></returns>
        public int GetCurrentPreset(ref int currentpreset)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetCurrentPreset{1}", DateTime.Now.ToString(), Environment.NewLine));
            currentpreset = _preset;
            return S_OK;
        }

        /// <summary>
        /// Invoked when a host wants to display the property page for the effect
        /// </summary>
        /// <param name="hwndOwner"></param>
        /// <returns></returns>
        public int DisplayPropertyPage(IntPtr hwndOwner)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : DisplayPropertyPage, Owner={1}{2}", DateTime.Now.ToString(), hwndOwner.ToString(), Environment.NewLine));
            MessageBox.Show("Hello Me!");
            return S_OK;
        }

        public int GoFullScreen(bool fFullscreen)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GoFullScreen{1}", DateTime.Now.ToString(), Environment.NewLine));
            return S_OK;
        }

        public int RenderFullScreen(ref TimedLevel pLevels)
        {
            File.AppendAllText("C:\\wmp.txt", string.Format("{0} : RenderFullScreen{1}", DateTime.Now.ToString(), Environment.NewLine));
            return S_OK;
        }

        #endregion

        private IntPtr _parentHwnd;
        private int _preset;

        private IntPtr _playerCore;

    }

Come si può vedere il mio codice è piuttosto vuoto, niente di più che stub davvero. Il mio debug è semplice, ma non il lavoro.

Una volta che questo è compilato con un nome sicuro può essere registrato con:

regasm assemblyname.dll / tlb

poi caduto nel GAC.

  

Apri regedit e aggiungere la seguente   info:

     

Sotto    HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ MediaPlayer \ Objects \ Effetti

     

Nuova chiave: WmpTestPlugin

     

In base alla nuova chiave di aggiungere una nuova stringa   Valore: Nome: Valore classid:   {C476FF24-5E5C-419d-9110-05EC2EED8511}

Così abbiamo costruito un plugin che è conforme alle interfacce, registrati in GAC e ha detto lettore multimediale che è lì.

Se si apre il lettore multimediale e fare clic destro sullo spazio di visualizzazione apparirà un menu. In quel menu sarà il nostro nuovo plugin. Quando si sposta il mouse verso il basso per la nuova voce WMP chiamerà GetPresetCount sul plug-in (questo registrerà nel file). Poi WMP dovrebbe chiamare GetPresetTitle , ma lo fa mai per me.

Se strumenti open \ Options dalla barra dei menu e selezionare la scheda Plugin è possibile selezionare il nuovo plugin. Se si fa clic su Proprietà WMP chiamerà GetCapabilities apparirà quindi DisplayPropertyPage e una finestra di messaggio dal plugin. WMP poi si blocca. Nella versione C ++ FinalConstruct () è chiamata su un Interfaccia CComCoClass - io non ho questo e non so cosa sia. Penso che possa essere di livello inferiore a quello che sto usando ??

Ho provato molte molte cose per arrivare a questo lavoro ivi inclusi gli adeguamenti dichiarazioni di metodo. Per favore qualcuno può guardare e aiutare. Ho guardato tutto il web per una soluzione e ho trovato nulla.

Grazie per la lettura,   Nanook

È stato utile?

Soluzione

Dopo aver guardato un po 'di codice di interoperabilità shell di Windows ho scoperto che avrei dovuto usando [ComImport] e NON [ComVisible] per le interfacce. Per rendere le firme uguale a quello già registrato ho usato [PreserveSig]. L'ereditarietà di interfaccia necessaria anche un po 'di aiuto dal redeclaring l'interfaccia di base all'interno del genitore.

Spero che questo aiuta qualcuno.

Ecco la classe operaia

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace WmpTestPlugin
{
    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("695386EC-AA3C-4618-A5E1-DD9A8B987632")]
    public interface IWmpEffects2 : IWmpEffects
    {
        [PreserveSig]
        new int Render(ref TimedLevel pLevels, IntPtr Hdc, ref RECT pRC);
        [PreserveSig]
        new int MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle);
        [PreserveSig]
        new int GetCapabilities(ref int pdwCapabilities);
        [PreserveSig]
        new int GetTitle(ref string bstrTitle);
        [PreserveSig]
        new int GetPresetTitle(int nPreset, ref string bstrPresetTitle);
        [PreserveSig]
        new int GetPresetCount(ref int count);
        [PreserveSig]
        new int SetCurrentPreset(int currentpreset);
        [PreserveSig]
        new int GetCurrentPreset(ref int currentpreset);
        [PreserveSig]
        new int DisplayPropertyPage(IntPtr hwndOwner);
        [PreserveSig]
        new int GoFullScreen(bool fFullscreen);
        [PreserveSig]
        new int RenderFullScreen(ref TimedLevel pLevels);

        [PreserveSig]
        int SetCore(IntPtr pPlayer);
        [PreserveSig]
        int Create(IntPtr hwndParent);
        [PreserveSig]
        int Destroy();
        [PreserveSig]
        int NotifyNewMedia(IntPtr pMedia);
        [PreserveSig]
        int OnWindowMessage(int Msg, int WParam, int LParam, ref int plResultParam);
        [PreserveSig]
        int RenderWindowed(ref TimedLevel pData, bool fRequiredRender);
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("D3984C13-C3CB-48e2-8BE5-5168340B4F35")]
    public interface IWmpEffects
    {
        [PreserveSig]
        int Render(ref TimedLevel pLevels, IntPtr Hdc, ref RECT pRC);
        [PreserveSig]
        int MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle);
        [PreserveSig]
        int GetCapabilities(ref int pdwCapabilities);
        [PreserveSig]
        int GetTitle(ref string bstrTitle);
        [PreserveSig]
        int GetPresetTitle(int nPreset, ref string bstrPresetTitle);
        [PreserveSig]
        int GetPresetCount(ref int count);
        [PreserveSig]
        int SetCurrentPreset(int currentpreset);
        [PreserveSig]
        int GetCurrentPreset(ref int currentpreset);
        [PreserveSig]
        int DisplayPropertyPage(IntPtr hwndOwner);
        [PreserveSig]
        int GoFullScreen(bool fFullscreen);
        [PreserveSig]
        int RenderFullScreen(ref TimedLevel pLevels);
    }


    //[ComVisible(true)]
    [StructLayout(LayoutKind.Sequential)]
    public struct Data
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
        public byte[] Data0;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
        public byte[] Data1;
    }

    //[ComVisible(true)]
    public enum PlayerState
    {
        Stop_State,
        Pause_State,
        Play_State
    }

    //[ComVisible(true)]
    [StructLayout(LayoutKind.Sequential)]
    public struct TimedLevel
    {
        public Data Frequency;
        public Data Waveform;
        public PlayerState State;
        public long TimeStamp;
    }

    //[ComVisible(true)]
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
        public int Width { get { return this.Right - this.Left; } }
        public int Height { get { return this.Bottom - this.Top; } }
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top