mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-29 21:21:12 +00:00
479 lines
21 KiB
C
479 lines
21 KiB
C
|
//------------------------------------------------------------------------------
|
||
|
// File: RenBase.h
|
||
|
//
|
||
|
// Desc: DirectShow base classes - defines a generic ActiveX base renderer
|
||
|
// class.
|
||
|
//
|
||
|
// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved.
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
#ifndef __RENBASE__
|
||
|
#define __RENBASE__
|
||
|
|
||
|
// Forward class declarations
|
||
|
|
||
|
class CBaseRenderer;
|
||
|
class CBaseVideoRenderer;
|
||
|
class CRendererInputPin;
|
||
|
|
||
|
// This is our input pin class that channels calls to the renderer
|
||
|
|
||
|
class CRendererInputPin : public CBaseInputPin
|
||
|
{
|
||
|
protected:
|
||
|
|
||
|
CBaseRenderer *m_pRenderer;
|
||
|
|
||
|
public:
|
||
|
|
||
|
CRendererInputPin(__inout CBaseRenderer *pRenderer,
|
||
|
__inout HRESULT *phr,
|
||
|
__in_opt LPCWSTR Name);
|
||
|
|
||
|
// Overriden from the base pin classes
|
||
|
|
||
|
HRESULT BreakConnect();
|
||
|
HRESULT CompleteConnect(IPin *pReceivePin);
|
||
|
HRESULT SetMediaType(const CMediaType *pmt);
|
||
|
HRESULT CheckMediaType(const CMediaType *pmt);
|
||
|
HRESULT Active();
|
||
|
HRESULT Inactive();
|
||
|
|
||
|
// Add rendering behaviour to interface functions
|
||
|
|
||
|
STDMETHODIMP QueryId(__deref_out LPWSTR *Id);
|
||
|
STDMETHODIMP EndOfStream();
|
||
|
STDMETHODIMP BeginFlush();
|
||
|
STDMETHODIMP EndFlush();
|
||
|
STDMETHODIMP Receive(IMediaSample *pMediaSample);
|
||
|
|
||
|
// Helper
|
||
|
IMemAllocator inline *Allocator() const
|
||
|
{
|
||
|
return m_pAllocator;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Main renderer class that handles synchronisation and state changes
|
||
|
|
||
|
class CBaseRenderer : public CBaseFilter
|
||
|
{
|
||
|
protected:
|
||
|
|
||
|
friend class CRendererInputPin;
|
||
|
|
||
|
friend void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier
|
||
|
UINT uMsg, // Not currently used
|
||
|
DWORD_PTR dwUser, // User information
|
||
|
DWORD_PTR dw1, // Windows reserved
|
||
|
DWORD_PTR dw2); // Is also reserved
|
||
|
|
||
|
CRendererPosPassThru *m_pPosition; // Media seeking pass by object
|
||
|
CAMEvent m_RenderEvent; // Used to signal timer events
|
||
|
CAMEvent m_ThreadSignal; // Signalled to release worker thread
|
||
|
CAMEvent m_evComplete; // Signalled when state complete
|
||
|
BOOL m_bAbort; // Stop us from rendering more data
|
||
|
BOOL m_bStreaming; // Are we currently streaming
|
||
|
DWORD_PTR m_dwAdvise; // Timer advise cookie
|
||
|
IMediaSample *m_pMediaSample; // Current image media sample
|
||
|
BOOL m_bEOS; // Any more samples in the stream
|
||
|
BOOL m_bEOSDelivered; // Have we delivered an EC_COMPLETE
|
||
|
CRendererInputPin *m_pInputPin; // Our renderer input pin object
|
||
|
CCritSec m_InterfaceLock; // Critical section for interfaces
|
||
|
CCritSec m_RendererLock; // Controls access to internals
|
||
|
IQualityControl * m_pQSink; // QualityControl sink
|
||
|
BOOL m_bRepaintStatus; // Can we signal an EC_REPAINT
|
||
|
// Avoid some deadlocks by tracking filter during stop
|
||
|
volatile BOOL m_bInReceive; // Inside Receive between PrepareReceive
|
||
|
// And actually processing the sample
|
||
|
REFERENCE_TIME m_SignalTime; // Time when we signal EC_COMPLETE
|
||
|
UINT m_EndOfStreamTimer; // Used to signal end of stream
|
||
|
CCritSec m_ObjectCreationLock; // This lock protects the creation and
|
||
|
// of m_pPosition and m_pInputPin. It
|
||
|
// ensures that two threads cannot create
|
||
|
// either object simultaneously.
|
||
|
|
||
|
public:
|
||
|
|
||
|
CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer
|
||
|
__in_opt LPCTSTR pName, // Debug ONLY description
|
||
|
__inout_opt LPUNKNOWN pUnk, // Aggregated owner object
|
||
|
__inout HRESULT *phr); // General OLE return code
|
||
|
|
||
|
~CBaseRenderer();
|
||
|
|
||
|
// Overriden to say what interfaces we support and where
|
||
|
|
||
|
virtual HRESULT GetMediaPositionInterface(REFIID riid, __deref_out void **ppv);
|
||
|
STDMETHODIMP NonDelegatingQueryInterface(REFIID, __deref_out void **);
|
||
|
|
||
|
virtual HRESULT SourceThreadCanWait(BOOL bCanWait);
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
// Debug only dump of the renderer state
|
||
|
void DisplayRendererState();
|
||
|
#endif
|
||
|
virtual HRESULT WaitForRenderTime();
|
||
|
virtual HRESULT CompleteStateChange(FILTER_STATE OldState);
|
||
|
|
||
|
// Return internal information about this filter
|
||
|
|
||
|
BOOL IsEndOfStream() { return m_bEOS; };
|
||
|
BOOL IsEndOfStreamDelivered() { return m_bEOSDelivered; };
|
||
|
BOOL IsStreaming() { return m_bStreaming; };
|
||
|
void SetAbortSignal(BOOL bAbort) { m_bAbort = bAbort; };
|
||
|
virtual void OnReceiveFirstSample(IMediaSample *pMediaSample) { };
|
||
|
CAMEvent *GetRenderEvent() { return &m_RenderEvent; };
|
||
|
|
||
|
// Permit access to the transition state
|
||
|
|
||
|
void Ready() { m_evComplete.Set(); };
|
||
|
void NotReady() { m_evComplete.Reset(); };
|
||
|
BOOL CheckReady() { return m_evComplete.Check(); };
|
||
|
|
||
|
virtual int GetPinCount();
|
||
|
virtual CBasePin *GetPin(int n);
|
||
|
FILTER_STATE GetRealState();
|
||
|
void SendRepaint();
|
||
|
void SendNotifyWindow(IPin *pPin,HWND hwnd);
|
||
|
BOOL OnDisplayChange();
|
||
|
void SetRepaintStatus(BOOL bRepaint);
|
||
|
|
||
|
// Override the filter and pin interface functions
|
||
|
|
||
|
STDMETHODIMP Stop();
|
||
|
STDMETHODIMP Pause();
|
||
|
STDMETHODIMP Run(REFERENCE_TIME StartTime);
|
||
|
STDMETHODIMP GetState(DWORD dwMSecs, __out FILTER_STATE *State);
|
||
|
STDMETHODIMP FindPin(LPCWSTR Id, __deref_out IPin **ppPin);
|
||
|
|
||
|
// These are available for a quality management implementation
|
||
|
|
||
|
virtual void OnRenderStart(IMediaSample *pMediaSample);
|
||
|
virtual void OnRenderEnd(IMediaSample *pMediaSample);
|
||
|
virtual HRESULT OnStartStreaming() { return NOERROR; };
|
||
|
virtual HRESULT OnStopStreaming() { return NOERROR; };
|
||
|
virtual void OnWaitStart() { };
|
||
|
virtual void OnWaitEnd() { };
|
||
|
virtual void PrepareRender() { };
|
||
|
|
||
|
#ifdef PERF
|
||
|
REFERENCE_TIME m_trRenderStart; // Just before we started drawing
|
||
|
// Set in OnRenderStart, Used in OnRenderEnd
|
||
|
int m_idBaseStamp; // MSR_id for frame time stamp
|
||
|
int m_idBaseRenderTime; // MSR_id for true wait time
|
||
|
int m_idBaseAccuracy; // MSR_id for time frame is late (int)
|
||
|
#endif
|
||
|
|
||
|
// Quality management implementation for scheduling rendering
|
||
|
|
||
|
virtual BOOL ScheduleSample(IMediaSample *pMediaSample);
|
||
|
virtual HRESULT GetSampleTimes(IMediaSample *pMediaSample,
|
||
|
__out REFERENCE_TIME *pStartTime,
|
||
|
__out REFERENCE_TIME *pEndTime);
|
||
|
|
||
|
virtual HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample,
|
||
|
__out REFERENCE_TIME *ptrStart,
|
||
|
__out REFERENCE_TIME *ptrEnd);
|
||
|
|
||
|
// Lots of end of stream complexities
|
||
|
|
||
|
void TimerCallback();
|
||
|
void ResetEndOfStreamTimer();
|
||
|
HRESULT NotifyEndOfStream();
|
||
|
virtual HRESULT SendEndOfStream();
|
||
|
virtual HRESULT ResetEndOfStream();
|
||
|
virtual HRESULT EndOfStream();
|
||
|
|
||
|
// Rendering is based around the clock
|
||
|
|
||
|
void SignalTimerFired();
|
||
|
virtual HRESULT CancelNotification();
|
||
|
virtual HRESULT ClearPendingSample();
|
||
|
|
||
|
// Called when the filter changes state
|
||
|
|
||
|
virtual HRESULT Active();
|
||
|
virtual HRESULT Inactive();
|
||
|
virtual HRESULT StartStreaming();
|
||
|
virtual HRESULT StopStreaming();
|
||
|
virtual HRESULT BeginFlush();
|
||
|
virtual HRESULT EndFlush();
|
||
|
|
||
|
// Deal with connections and type changes
|
||
|
|
||
|
virtual HRESULT BreakConnect();
|
||
|
virtual HRESULT SetMediaType(const CMediaType *pmt);
|
||
|
virtual HRESULT CompleteConnect(IPin *pReceivePin);
|
||
|
|
||
|
// These look after the handling of data samples
|
||
|
|
||
|
virtual HRESULT PrepareReceive(IMediaSample *pMediaSample);
|
||
|
virtual HRESULT Receive(IMediaSample *pMediaSample);
|
||
|
virtual BOOL HaveCurrentSample();
|
||
|
virtual IMediaSample *GetCurrentSample();
|
||
|
virtual HRESULT Render(IMediaSample *pMediaSample);
|
||
|
|
||
|
// Derived classes MUST override these
|
||
|
virtual HRESULT DoRenderSample(IMediaSample *pMediaSample) PURE;
|
||
|
virtual HRESULT CheckMediaType(const CMediaType *) PURE;
|
||
|
|
||
|
// Helper
|
||
|
void WaitForReceiveToComplete();
|
||
|
};
|
||
|
|
||
|
|
||
|
// CBaseVideoRenderer is a renderer class (see its ancestor class) and
|
||
|
// it handles scheduling of media samples so that they are drawn at the
|
||
|
// correct time by the reference clock. It implements a degradation
|
||
|
// strategy. Possible degradation modes are:
|
||
|
// Drop frames here (only useful if the drawing takes significant time)
|
||
|
// Signal supplier (upstream) to drop some frame(s) - i.e. one-off skip.
|
||
|
// Signal supplier to change the frame rate - i.e. ongoing skipping.
|
||
|
// Or any combination of the above.
|
||
|
// In order to determine what's useful to try we need to know what's going
|
||
|
// on. This is done by timing various operations (including the supplier).
|
||
|
// This timing is done by using timeGetTime as it is accurate enough and
|
||
|
// usually cheaper than calling the reference clock. It also tells the
|
||
|
// truth if there is an audio break and the reference clock stops.
|
||
|
// We provide a number of public entry points (named OnXxxStart, OnXxxEnd)
|
||
|
// which the rest of the renderer calls at significant moments. These do
|
||
|
// the timing.
|
||
|
|
||
|
// the number of frames that the sliding averages are averaged over.
|
||
|
// the rule is (1024*NewObservation + (AVGPERIOD-1) * PreviousAverage)/AVGPERIOD
|
||
|
#define AVGPERIOD 4
|
||
|
#define DO_MOVING_AVG(avg,obs) (avg = (1024*obs + (AVGPERIOD-1)*avg)/AVGPERIOD)
|
||
|
// Spot the bug in this macro - I can't. but it doesn't work!
|
||
|
|
||
|
class CBaseVideoRenderer : public CBaseRenderer, // Base renderer class
|
||
|
public IQualProp, // Property page guff
|
||
|
public IQualityControl // Allow throttling
|
||
|
{
|
||
|
protected:
|
||
|
|
||
|
// Hungarian:
|
||
|
// tFoo is the time Foo in mSec (beware m_tStart from filter.h)
|
||
|
// trBar is the time Bar by the reference clock
|
||
|
|
||
|
//******************************************************************
|
||
|
// State variables to control synchronisation
|
||
|
//******************************************************************
|
||
|
|
||
|
// Control of sending Quality messages. We need to know whether
|
||
|
// we are in trouble (e.g. frames being dropped) and where the time
|
||
|
// is being spent.
|
||
|
|
||
|
// When we drop a frame we play the next one early.
|
||
|
// The frame after that is likely to wait before drawing and counting this
|
||
|
// wait as spare time is unfair, so we count it as a zero wait.
|
||
|
// We therefore need to know whether we are playing frames early or not.
|
||
|
|
||
|
int m_nNormal; // The number of consecutive frames
|
||
|
// drawn at their normal time (not early)
|
||
|
// -1 means we just dropped a frame.
|
||
|
|
||
|
#ifdef PERF
|
||
|
BOOL m_bDrawLateFrames; // Don't drop any frames (debug and I'm
|
||
|
// not keen on people using it!)
|
||
|
#endif
|
||
|
|
||
|
BOOL m_bSupplierHandlingQuality;// The response to Quality messages says
|
||
|
// our supplier is handling things.
|
||
|
// We will allow things to go extra late
|
||
|
// before dropping frames. We will play
|
||
|
// very early after he has dropped one.
|
||
|
|
||
|
// Control of scheduling, frame dropping etc.
|
||
|
// We need to know where the time is being spent so as to tell whether
|
||
|
// we should be taking action here, signalling supplier or what.
|
||
|
// The variables are initialised to a mode of NOT dropping frames.
|
||
|
// They will tell the truth after a few frames.
|
||
|
// We typically record a start time for an event, later we get the time
|
||
|
// again and subtract to get the elapsed time, and we average this over
|
||
|
// a few frames. The average is used to tell what mode we are in.
|
||
|
|
||
|
// Although these are reference times (64 bit) they are all DIFFERENCES
|
||
|
// between times which are small. An int will go up to 214 secs before
|
||
|
// overflow. Avoiding 64 bit multiplications and divisions seems
|
||
|
// worth while.
|
||
|
|
||
|
|
||
|
|
||
|
// Audio-video throttling. If the user has turned up audio quality
|
||
|
// very high (in principle it could be any other stream, not just audio)
|
||
|
// then we can receive cries for help via the graph manager. In this case
|
||
|
// we put in a wait for some time after rendering each frame.
|
||
|
int m_trThrottle;
|
||
|
|
||
|
// The time taken to render (i.e. BitBlt) frames controls which component
|
||
|
// needs to degrade. If the blt is expensive, the renderer degrades.
|
||
|
// If the blt is cheap it's done anyway and the supplier degrades.
|
||
|
int m_trRenderAvg; // Time frames are taking to blt
|
||
|
int m_trRenderLast; // Time for last frame blt
|
||
|
int m_tRenderStart; // Just before we started drawing (mSec)
|
||
|
// derived from timeGetTime.
|
||
|
|
||
|
// When frames are dropped we will play the next frame as early as we can.
|
||
|
// If it was a false alarm and the machine is fast we slide gently back to
|
||
|
// normal timing. To do this, we record the offset showing just how early
|
||
|
// we really are. This will normally be negative meaning early or zero.
|
||
|
int m_trEarliness;
|
||
|
|
||
|
// Target provides slow long-term feedback to try to reduce the
|
||
|
// average sync offset to zero. Whenever a frame is actually rendered
|
||
|
// early we add a msec or two, whenever late we take off a few.
|
||
|
// We add or take off 1/32 of the error time.
|
||
|
// Eventually we should be hovering around zero. For a really bad case
|
||
|
// where we were (say) 300mSec off, it might take 100 odd frames to
|
||
|
// settle down. The rate of change of this is intended to be slower
|
||
|
// than any other mechanism in Quartz, thereby avoiding hunting.
|
||
|
int m_trTarget;
|
||
|
|
||
|
// The proportion of time spent waiting for the right moment to blt
|
||
|
// controls whether we bother to drop a frame or whether we reckon that
|
||
|
// we're doing well enough that we can stand a one-frame glitch.
|
||
|
int m_trWaitAvg; // Average of last few wait times
|
||
|
// (actually we just average how early
|
||
|
// we were). Negative here means LATE.
|
||
|
|
||
|
// The average inter-frame time.
|
||
|
// This is used to calculate the proportion of the time used by the
|
||
|
// three operations (supplying us, waiting, rendering)
|
||
|
int m_trFrameAvg; // Average inter-frame time
|
||
|
int m_trDuration; // duration of last frame.
|
||
|
|
||
|
#ifdef PERF
|
||
|
// Performance logging identifiers
|
||
|
int m_idTimeStamp; // MSR_id for frame time stamp
|
||
|
int m_idEarliness; // MSR_id for earliness fudge
|
||
|
int m_idTarget; // MSR_id for Target fudge
|
||
|
int m_idWaitReal; // MSR_id for true wait time
|
||
|
int m_idWait; // MSR_id for wait time recorded
|
||
|
int m_idFrameAccuracy; // MSR_id for time frame is late (int)
|
||
|
int m_idRenderAvg; // MSR_id for Render time recorded (int)
|
||
|
int m_idSchLateTime; // MSR_id for lateness at scheduler
|
||
|
int m_idQualityRate; // MSR_id for Quality rate requested
|
||
|
int m_idQualityTime; // MSR_id for Quality time requested
|
||
|
int m_idDecision; // MSR_id for decision code
|
||
|
int m_idDuration; // MSR_id for duration of a frame
|
||
|
int m_idThrottle; // MSR_id for audio-video throttling
|
||
|
//int m_idDebug; // MSR_id for trace style debugging
|
||
|
//int m_idSendQuality; // MSR_id for timing the notifications per se
|
||
|
#endif // PERF
|
||
|
REFERENCE_TIME m_trRememberStampForPerf; // original time stamp of frame
|
||
|
// with no earliness fudges etc.
|
||
|
#ifdef PERF
|
||
|
REFERENCE_TIME m_trRememberFrameForPerf; // time when previous frame rendered
|
||
|
|
||
|
// debug...
|
||
|
int m_idFrameAvg;
|
||
|
int m_idWaitAvg;
|
||
|
#endif
|
||
|
|
||
|
// PROPERTY PAGE
|
||
|
// This has edit fields that show the user what's happening
|
||
|
// These member variables hold these counts.
|
||
|
|
||
|
int m_cFramesDropped; // cumulative frames dropped IN THE RENDERER
|
||
|
int m_cFramesDrawn; // Frames since streaming started seen BY THE
|
||
|
// RENDERER (some may be dropped upstream)
|
||
|
|
||
|
// Next two support average sync offset and standard deviation of sync offset.
|
||
|
LONGLONG m_iTotAcc; // Sum of accuracies in mSec
|
||
|
LONGLONG m_iSumSqAcc; // Sum of squares of (accuracies in mSec)
|
||
|
|
||
|
// Next two allow jitter calculation. Jitter is std deviation of frame time.
|
||
|
REFERENCE_TIME m_trLastDraw; // Time of prev frame (for inter-frame times)
|
||
|
LONGLONG m_iSumSqFrameTime; // Sum of squares of (inter-frame time in mSec)
|
||
|
LONGLONG m_iSumFrameTime; // Sum of inter-frame times in mSec
|
||
|
|
||
|
// To get performance statistics on frame rate, jitter etc, we need
|
||
|
// to record the lateness and inter-frame time. What we actually need are the
|
||
|
// data above (sum, sum of squares and number of entries for each) but the data
|
||
|
// is generated just ahead of time and only later do we discover whether the
|
||
|
// frame was actually drawn or not. So we have to hang on to the data
|
||
|
int m_trLate; // hold onto frame lateness
|
||
|
int m_trFrame; // hold onto inter-frame time
|
||
|
|
||
|
int m_tStreamingStart; // if streaming then time streaming started
|
||
|
// else time of last streaming session
|
||
|
// used for property page statistics
|
||
|
#ifdef PERF
|
||
|
LONGLONG m_llTimeOffset; // timeGetTime()*10000+m_llTimeOffset==ref time
|
||
|
#endif
|
||
|
|
||
|
public:
|
||
|
|
||
|
|
||
|
CBaseVideoRenderer(REFCLSID RenderClass, // CLSID for this renderer
|
||
|
__in_opt LPCTSTR pName, // Debug ONLY description
|
||
|
__inout_opt LPUNKNOWN pUnk, // Aggregated owner object
|
||
|
__inout HRESULT *phr); // General OLE return code
|
||
|
|
||
|
~CBaseVideoRenderer();
|
||
|
|
||
|
// IQualityControl methods - Notify allows audio-video throttling
|
||
|
|
||
|
STDMETHODIMP SetSink( IQualityControl * piqc);
|
||
|
STDMETHODIMP Notify( IBaseFilter * pSelf, Quality q);
|
||
|
|
||
|
// These provide a full video quality management implementation
|
||
|
|
||
|
void OnRenderStart(IMediaSample *pMediaSample);
|
||
|
void OnRenderEnd(IMediaSample *pMediaSample);
|
||
|
void OnWaitStart();
|
||
|
void OnWaitEnd();
|
||
|
HRESULT OnStartStreaming();
|
||
|
HRESULT OnStopStreaming();
|
||
|
void ThrottleWait();
|
||
|
|
||
|
// Handle the statistics gathering for our quality management
|
||
|
|
||
|
void PreparePerformanceData(int trLate, int trFrame);
|
||
|
virtual void RecordFrameLateness(int trLate, int trFrame);
|
||
|
virtual void OnDirectRender(IMediaSample *pMediaSample);
|
||
|
virtual HRESULT ResetStreamingTimes();
|
||
|
BOOL ScheduleSample(IMediaSample *pMediaSample);
|
||
|
HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample,
|
||
|
__inout REFERENCE_TIME *ptrStart,
|
||
|
__inout REFERENCE_TIME *ptrEnd);
|
||
|
|
||
|
virtual HRESULT SendQuality(REFERENCE_TIME trLate, REFERENCE_TIME trRealStream);
|
||
|
STDMETHODIMP JoinFilterGraph(__inout_opt IFilterGraph * pGraph, __in_opt LPCWSTR pName);
|
||
|
|
||
|
//
|
||
|
// Do estimates for standard deviations for per-frame
|
||
|
// statistics
|
||
|
//
|
||
|
// *piResult = (llSumSq - iTot * iTot / m_cFramesDrawn - 1) /
|
||
|
// (m_cFramesDrawn - 2)
|
||
|
// or 0 if m_cFramesDrawn <= 3
|
||
|
//
|
||
|
HRESULT GetStdDev(
|
||
|
int nSamples,
|
||
|
__out int *piResult,
|
||
|
LONGLONG llSumSq,
|
||
|
LONGLONG iTot
|
||
|
);
|
||
|
public:
|
||
|
|
||
|
// IQualProp property page support
|
||
|
|
||
|
STDMETHODIMP get_FramesDroppedInRenderer(__out int *cFramesDropped);
|
||
|
STDMETHODIMP get_FramesDrawn(__out int *pcFramesDrawn);
|
||
|
STDMETHODIMP get_AvgFrameRate(__out int *piAvgFrameRate);
|
||
|
STDMETHODIMP get_Jitter(__out int *piJitter);
|
||
|
STDMETHODIMP get_AvgSyncOffset(__out int *piAvg);
|
||
|
STDMETHODIMP get_DevSyncOffset(__out int *piDev);
|
||
|
|
||
|
// Implement an IUnknown interface and expose IQualProp
|
||
|
|
||
|
DECLARE_IUNKNOWN
|
||
|
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,__deref_out VOID **ppv);
|
||
|
};
|
||
|
|
||
|
#endif // __RENBASE__
|
||
|
|