1

I have a DLL that was originally written for Windows Forms. It creates 2 window forms one for Viewing a Live Video Feed from a BlackMagics Card, and one window for Static Images captured from Video.

The DLL is all Static Classes, called by including the Reference to the dll (VideoCapture.dll) and in the program I Start the Capture Screen by calling

VideoCapture.RSVideoControler.Start();

In a Windows Form Test Program I've got a Button that calls this command only. the video window opens , and leaves the Main Window free to function normally. CPU Usage is about (Task Manager 50) and displays the Video feed as expected. Closing the Main Program shuts everything down smoothly.

Now the Same DLL & Code to start, but put in a WPF Application. Before Pressing the Button the app is running at 0 CPU (Task manager) and you can move the window normally. But after running the Start Command the app runs at 99 CPU (Task Manager) and becomes unresponsive.

What is WPF Doing in starting this thread differently than Windows Forms ?

ETA: RSVidoControler

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using VideoCapture.Classes;

namespace VideoCapture
{
    public class RSVideoControler
    {
        public static Bitmap LastImage = new Bitmap(1920, 1080,     PixelFormat.Format16bppRgb565);
        private static VidCardGlobal VideoInterface;
    private static IntPtr m_ip = IntPtr.Zero;
    //[STAThread]
    public static void Start() {VideoInterface = new VidCardGlobal();}
    public static Bitmap GrabFrame()
    {
        if (m_ip != IntPtr.Zero)
        {
            Marshal.FreeCoTaskMem(m_ip);
            m_ip = IntPtr.Zero;
        }
        m_ip = VideoInterface.Click();
        using (Bitmap b = new Bitmap(VideoInterface.Width, VideoInterface.Height, VideoInterface.Stride, PixelFormat.Format16bppRgb565, m_ip))
        {
            if (b != null)
            {
                b.RotateFlip(RotateFlipType.RotateNoneFlipY);
                LastImage.Dispose();
                LastImage = new Bitmap(b);
                b.Dispose();
            }
        }
        return LastImage;
    }
    public static void VideoVisable(bool Visibility)
    {
        VidCardGlobal.VideoViewer.Visible = Visibility;
    }
    public static void ShutDownVideo()
    {
        VideoInterface.Dispose();       // Shuts down Image Capturing interface
        VidCardGlobal.MediaControl.Stop(); // Shuts down Video Viewing Screen
    }
}
}

VidCardGlobal

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Threading;
using System.Windows.Forms;
using DirectShowLib;
using VideoCapture.Forms;

namespace VideoCapture.Classes
{
internal class VidCardGlobal : ISampleGrabberCB, IDisposable
{
    //private object syncLock = new object();
    #region Varables for SingleFrameCapture
    private ManualResetEvent m_PictureReady = null;
    private bool m_WantOne = false;
    private int m_videoWidth;
    private int m_videoHeight;
    private int m_stride;
    private IntPtr m_ipBuffer = IntPtr.Zero;
    private IAMVideoControl m_VidControl = null;
    private IPin m_pinStill = null;
    #endregion
    public int Width { get { return m_videoWidth; } }
    public int Height { get { return m_videoHeight; } }
    public int Stride { get { return m_stride; } }
    public static VideoFeed VideoViewer = new VideoFeed();
    #region DirectShow Stuff
    public MemoryStream stream { get; private set; }
    public static IGraphBuilder graph;
    public static IVMRWindowlessControl9 WindowlessControl = null;
    public static IBaseFilter pinfilter;
    public static bool VideoHandlersAdded = false;
    public static IMediaControl MediaControl;
    public static ISampleGrabber SampleGrabberFilter;

    //public static AMMediaType DefaultMediaType = new AMMediaType();
    //private static IMediaControl mediaControl = null;
    public int SampleCB(double Unknown1, IMediaSample MySample) { return 0; }
    public int BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen)
    {
        Debug.Assert(BufferLen == Math.Abs(m_stride) * m_videoHeight, "Incorrect buffer length");
        if (m_WantOne)
        {
            m_WantOne = false;
            Debug.Assert(m_ipBuffer != IntPtr.Zero, "Unitialized buffer");
            CopyMemory(m_ipBuffer, pBuffer, BufferLen);
            m_PictureReady.Set();
        }

        return 0;
    }
    #region IDisposable
    public void Dispose(bool disposing)
    {
        if (disposing)
        {
            //if (stream != null)
            {
                //stream.Dispose();
                //stream = null;
            }
        }
    }
    public void Dispose()
    {
        Dispose(true);
    }
    #endregion
    public static IBaseFilter CreateFilterbyName(string filterName, Guid category)
    {
        int hr = 0;
        DsDevice[] Devices = DsDevice.GetDevicesOfCat(category);
        foreach (DsDevice dev in Devices)
            if (dev.Name == filterName)
            {
                IBaseFilter filter = null;
                IBindCtx bindCtx = null;
                try
                {
                    hr = CreateBindCtx(0, out bindCtx);
                    DsError.ThrowExceptionForHR(hr);
                    Guid guid = typeof(IBaseFilter).GUID;
                    object obj;
                    dev.Mon.BindToObject(bindCtx, null, ref guid, out obj);
                    filter = (IBaseFilter)obj;
                }
                finally
                {
                    if (bindCtx != null) Marshal.ReleaseComObject(bindCtx);
                }
                return filter;
            }
        return null;
    }
    [DllImport("ole32.dll")]
    public static extern int CreateBindCtx(int reserved, out IBindCtx ppcb);
    [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]
    private static extern void CopyMemory(IntPtr Destination, IntPtr Source, [MarshalAs(UnmanagedType.U4)] int Length);

    private static IPin GetPin(IBaseFilter filter, string pinname)
    {
        IEnumPins epins;
        bool FoundPin = false;
        IPin ValidPin = null;
        int hr = filter.EnumPins(out epins);
        checkHR(hr, "Can't enumerate pins");
        IntPtr Fetched = Marshal.AllocCoTaskMem(4);
        IPin[] Pins = new IPin[1];
        while (epins.Next(1, Pins, Fetched) == 0)
        {
            PinInfo pinfo;
            Pins[0].QueryPinInfo(out pinfo);
            pinfilter = pinfo.filter;
            bool found = (pinfo.name == pinname);
            DsUtils.FreePinInfo(pinfo);
            if (found)
            {
                ValidPin = Pins[0];
                FoundPin = true;
            }
            //return Pins[0];
        }
        if (!FoundPin) checkHR(-1, "Pin not found");
        return ValidPin;
    }
    private static IPin GetVPin(IBaseFilter filter, string pinname)
    {
        IEnumPins epins;
        bool FoundPin = false;
        IPin ValidPin = null;
        int hr = filter.EnumPins(out epins);
        checkHR(hr, "Can't enumerate pins");
        IntPtr Fetched = Marshal.AllocCoTaskMem(4);
        IPin[] Pins = new IPin[1];
        while (epins.Next(1, Pins, Fetched) == 0)
        {
            PinInfo pinfo;
            Pins[0].QueryPinInfo(out pinfo);
            pinfilter = pinfo.filter;
            bool found = (pinfo.name == pinname);
            DsUtils.FreePinInfo(pinfo);
            if (found)
            {
                ValidPin = Pins[0];
                FoundPin = true;
            }
            //return Pins[0];
        }
        if (!FoundPin) checkHR(-1, "Pin not found");

        return ValidPin;
    }
    private static IPin FindPinIterface(IBaseFilter filter, Guid pFormat, PinDirection PinDir, IIPDVDec riid)
    {
        IPin FoundPin = null;
        //hr = GetPin(pFilter, pFormat, PinDir, &FoundPin);
        return FoundPin;
    }
    #endregion
    public VidCardGlobal()
    {

        graph = (IGraphBuilder)new FilterGraph();
        SampleGrabberFilter = (ISampleGrabber)new SampleGrabber();
        MediaControl = (IMediaControl)graph;
        BuildGraph(graph);  //Builds Graph And Connects camera, ect to graph
        m_PictureReady = new ManualResetEvent(false);
        MediaControl.Run();
        VideoViewer.VideoFeed_ResizeMove(null, null);
    }
    ~VidCardGlobal()
    {
        Dispose();
    }
    private static void checkHR(int HR, string ErrorMessage)
    {
        if (HR < 0)
        {
            MessageBox.Show(ErrorMessage, "CheckER Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            try
            {
                DsError.ThrowExceptionForHR(HR);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
                //DsError.ThrowExceptionForHR(HR);
            }

        }
    }
    public void BuildGraph(IGraphBuilder pGraph)
    {

        int hr = 0;
        int DVC = 0;
        int DVCi = 0;
        #region Find Video Capture Card
        DsDevice[] CardFinder = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
        foreach (DsDevice Check in CardFinder)
        {
            if (Check.Name == "Decklink Video Capture") DVC = DVCi;
            DVCi++;
        }
        // Magics Card should be CardFinder[0]
        // graph builder
        ICaptureGraphBuilder2 pBuilder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
        hr = pBuilder.SetFiltergraph(pGraph);
        checkHR(hr, "Can't SetFiltergraph");
        #endregion
        #region Create Connection to Capture Card
        //Guid HDYC = new Guid(0x43594448, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);

        IBaseFilter pDecklinkVideoCature;// = CreateFilterbyName(@"Decklink Video Capture", CLSID_VideoCaptureSource);
        IFilterGraph2 TempFilter = (IFilterGraph2)new FilterGraph();
        hr = TempFilter.AddSourceFilterForMoniker(CardFinder[DVC].Mon, null, CardFinder[DVC].Name, out pDecklinkVideoCature);
        checkHR(hr, "Can't Find Camera");
        hr = pGraph.AddFilter(pDecklinkVideoCature, "Decklink Video Capture");
        checkHR(hr, "Can't add Decklink video Capture to graph");
        // Configure pDecklinkVideoCature for 1080i @ 59.94fps somehow
        IPin VideoOutPin = GetVPin(pDecklinkVideoCature, "Capture");
        //AMMediaType VidMed = new AMMediaType();
        //VidMed.majorType = MediaType.Video;
        //VidMed.formatType = FormatType.VideoInfo;
        //VidMed.subType = MediaSubType.RGB24;
        //hr = VideoOutPin.ConnectionMediaType(VidMed);
        IAMStreamConfig streamConfig = (IAMStreamConfig)VideoOutPin;
        AMMediaType searchmedia;
        AMMediaType CorectvidFormat = new AMMediaType();
        IntPtr ptr;

        int piCount, piSize;
        hr = streamConfig.GetNumberOfCapabilities(out piCount, out piSize);
        ptr = Marshal.AllocCoTaskMem(piSize);
        for (int i = 0; i < piCount; i++)
        {
            hr = streamConfig.GetStreamCaps(i, out searchmedia, ptr);
            VideoInfoHeader v = new VideoInfoHeader();
            Marshal.PtrToStructure(searchmedia.formatPtr, v);
            VideoInfoHeader TestInfoHeader = (VideoInfoHeader)Marshal.PtrToStructure(searchmedia.formatPtr, typeof(VideoInfoHeader));
            int TestWidth = TestInfoHeader.BmiHeader.Width;
            int TestHeight = TestInfoHeader.BmiHeader.Height;
            int TestCompression = TestInfoHeader.BmiHeader.Compression;
            long TestTime = TestInfoHeader.AvgTimePerFrame;
            int TestError = TestInfoHeader.BitErrorRate;
            if ((TestWidth == 1920) & (TestHeight == 1080) & (TestTime == 333667))
            {
                CorectvidFormat = searchmedia;
            }
        }
        VideoInfoHeader videoInfoHeader = (VideoInfoHeader)Marshal.PtrToStructure(CorectvidFormat.formatPtr, typeof(VideoInfoHeader));
        m_videoWidth = videoInfoHeader.BmiHeader.Width;
        m_videoHeight = videoInfoHeader.BmiHeader.Height;
        m_stride = m_videoWidth * (videoInfoHeader.BmiHeader.BitCount / 8);

        hr = streamConfig.SetFormat(CorectvidFormat);
        checkHR(hr, "Can't Get Pin Query");
        #endregion
        #region Create Color Space Converter filter for Sample Grabber (I Hope)
        IBaseFilter pAVIDecompressor2 = (IBaseFilter)new AVIDec();
        hr = pGraph.AddFilter(pAVIDecompressor2, "AVI Decompressor");
        checkHR(hr, "Can't add AVI Decompressor to graph");
        // No Configuration needed for the AVI Decompresser that I know of
        IPin AVIDecompressor2OutPin = GetPin(pAVIDecompressor2, "XForm Out");
        IPin AVIDecompressor2InPin = GetPin(pAVIDecompressor2, "XForm In");

        //IBaseFilter ColorSpaceConverterFilter = new IBaseFilter;
        //hr = pGraph.AddFilter(ColorSpaceConverterFilter, "Color Space Converter");
        //checkHR(hr, "Can't Create Color Space Converter");
        //IPin CSCFIn = GetPin(ColorSpaceConverterFilter, "Input0");
        #endregion

        #region Create Sample Grabber
        AMMediaType SampleMediaType = new AMMediaType();
        SampleMediaType.majorType = MediaType.Video;
        SampleMediaType.subType = MediaSubType.RGB565;
        //SampleMediaType.subType = MediaSubType.UYVY;
        SampleMediaType.formatType = FormatType.VideoInfo;

        //hr = SampleGrabberFilter.SetMediaType(CorectvidFormat);
        hr = SampleGrabberFilter.SetMediaType(SampleMediaType);
        checkHR(hr, "Can't Set Sample Grabber Media Type");
        //ISampleGrabberCB pcallback;
        SampleGrabberFilter.SetCallback(this, 1);
        hr = SampleGrabberFilter.SetBufferSamples(false);
        hr = SampleGrabberFilter.SetOneShot(false);
        hr = pGraph.AddFilter((IBaseFilter)SampleGrabberFilter, "Sample Grabber");
        checkHR(hr, "Can't Add Sample Grabber");
        IPin SGFIn = GetPin((IBaseFilter)SampleGrabberFilter, "Input");
        IPin SGFOut = GetPin((IBaseFilter)SampleGrabberFilter, "Output");
        #endregion
        #region Create Null Renderer
        IBaseFilter VideoNullRenderer = (IBaseFilter)new NullRenderer();
        hr = pGraph.AddFilter(VideoNullRenderer, "Null Renderer");
        checkHR(hr, "Can't Create Null Renderer");
        IPin NullIn = GetPin(VideoNullRenderer, "In");
        #endregion
        #region Create Infinite Pin Tree Filter
        IBaseFilter InfinitePinTreeFilter = (IBaseFilter)new InfTee();
        hr = pGraph.AddFilter(InfinitePinTreeFilter, "Infinite Pin Tee");
        checkHR(hr, "Can't Add Pin Tee");
        IPin IPTIn = GetPin(InfinitePinTreeFilter, "Input");
        IPin IPTOut1 = GetPin(InfinitePinTreeFilter, "Output1");
        #region Connect IPTOut -> Sample Grabber -> Null renderer
        //hr = pGraph.ConnectDirect(VideoOutPin, IPTIn, CorectvidFormat);
        hr = pGraph.Connect(VideoOutPin, IPTIn);
        checkHR(hr, "Can't Connect Camera to Infinite Pin Tee");
        //hr = pGraph.ConnectDirect(IPTOut1, AVIDecompressor2InPin, CorectvidFormat);
        //checkHR(hr, "Can't IPT1 to AVIDecompressor 2");
        //hr = pGraph.ConnectDirect(AVIDecompressor2OutPin, SGFIn, SampleMediaType);
        //checkHR(hr, "Can't AVIDecompressor 2 to Sample In");
        hr = pGraph.Connect(IPTOut1, SGFIn);
        checkHR(hr, "Can't IPTOut1 to Sample In");
        hr = pGraph.Connect(SGFOut, NullIn);
        checkHR(hr, "Can't Sample Out To Null In");
        #endregion
        IPin IPTOut2 = GetPin(InfinitePinTreeFilter, "Output2");
        #endregion
        #region Add Video Mixing Renderer9
        //IBaseFilter pVideoMixingRenderer9 = (IBaseFilter)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_VideoMixingRenderer9));
        IBaseFilter pVideoMixingRenderer9 = (IBaseFilter)new VideoMixingRenderer9();
        // Setup Video Renderer to go to Windows Form at this Point??
        #region Setup WindowlessMode
        IVMRFilterConfig9 MixingRendererFilterConfig = (IVMRFilterConfig9)pVideoMixingRenderer9;
        hr = MixingRendererFilterConfig.SetNumberOfStreams(1);
        checkHR(hr, "Problem Setting Streams to 1");

        hr = MixingRendererFilterConfig.SetRenderingMode(VMR9Mode.Windowless);
        checkHR(hr, "Can't Set Mode to Windowed"); // Doesn't work Windowless?

        WindowlessControl = (IVMRWindowlessControl9)pVideoMixingRenderer9;
        hr = WindowlessControl.SetVideoClippingWindow(VideoViewer.VideoPanel.Handle);
        checkHR(hr, "Problem Setting Clipping Window to VideoPanel");

        hr = WindowlessControl.SetAspectRatioMode(VMR9AspectRatioMode.LetterBox);
        checkHR(hr, "Problem Setting Aspect Ration");
        VideoViewer.AddHandlers();
        VideoViewer.Visible = true;

        #endregion
        hr = pGraph.AddFilter(pVideoMixingRenderer9, "Video Mixing Renderer 9");
        checkHR(hr, "Can't Create Video Mixing Renderer 9");
        IPin VideoFormPin = GetPin(pVideoMixingRenderer9, "VMR Input0");
        #endregion
        #region Create AVI Decompressor
        IBaseFilter pAVIDecompressor = (IBaseFilter)new AVIDec();
        AMMediaType AVIInMedia = new AMMediaType();
        AMMediaType AVIOutMedia = new AMMediaType();
        AVIOutMedia.majorType = MediaType.Video;
        AVIOutMedia.subType = MediaSubType.UYVY;
        hr = pGraph.AddFilter(pAVIDecompressor, "AVI Decompressor");
        checkHR(hr, "Can't add AVI Decompressor to graph");
        // No Configuration needed for the AVI Decompresser that I know of
        IPin AVIDecompressorOutPin = GetPin(pAVIDecompressor, "XForm Out");
        IPin AVIDecompressorInPin = GetPin(pAVIDecompressor, "XForm In");
        #endregion
        #region Connect Infinite Pin Out2 to Input of AVI Decompressor
        //hr = pGraph.ConnectDirect(IPTOut2, AVIDecompressorInPin, null);
        hr = pGraph.Connect(IPTOut2, AVIDecompressorInPin);
        checkHR(hr, "Can't Connect Infinite Pin 2 to AVI Decompressor");
        #endregion
        #region Connect Output of AVI Decompressor to Video Mixing Renderer9
        //hr = pGraph.ConnectDirect(AVIDecompressorOutPin, VideoFormPin, null);
        hr = pGraph.Connect(AVIDecompressorOutPin, VideoFormPin);
        checkHR(hr, "Can't Connect AVI Out to Renderer");
        #endregion
        DsUtils.FreeAMMediaType(SampleMediaType);
        DsUtils.FreeAMMediaType(CorectvidFormat);
        SampleMediaType = null;
        CorectvidFormat = null;
        VideoViewer.Activate();
    }
    public IntPtr Click()
    {
        int hr;
        // get ready to wait for new image
        m_PictureReady.Reset();
        m_ipBuffer = Marshal.AllocCoTaskMem(Math.Abs(m_stride) * m_videoHeight);
        try
        {
            m_WantOne = true;
            // If we are using a still pin, ask for a picture
            if (m_VidControl != null)
            {
                // Tell the camera to send an image
                hr = m_VidControl.SetMode(m_pinStill, VideoControlFlags.Trigger);
                checkHR(hr, "Can't SetMode");
                DsError.ThrowExceptionForHR(hr);
            }

            // Start waiting
            if (!m_PictureReady.WaitOne(9000, false))
            {
                throw new Exception("Timeout waiting to get picture");
            }
        }
        catch
        {
            Marshal.FreeCoTaskMem(m_ipBuffer);
            m_ipBuffer = IntPtr.Zero;
            throw;
        }

        // Got one
        return m_ipBuffer;
    }
}
}

VideoFeed (Windows Form)

using System;
using System.Windows.Forms;
using DirectShowLib;
using Microsoft.Win32;
using VideoCapture.Classes;

namespace VideoCapture.Forms
{
public partial class VideoFeed : Form
{
    public VideoFeed()
    {
        InitializeComponent();
        this.Visible = true;
    }
    public void AddHandlers()
    {
        this.Paint += new PaintEventHandler(VideoFeed_Paint);
        this.Resize += new EventHandler(VideoFeed_ResizeMove);
        this.Move += new EventHandler(VideoFeed_ResizeMove);
        SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
        VidCardGlobal.VideoHandlersAdded = true;
    }
    public void RemoveHandlers()
    {
        this.Paint -= new PaintEventHandler(VideoFeed_Paint);
        this.Resize -= new EventHandler(VideoFeed_ResizeMove);
        this.Move -= new EventHandler(VideoFeed_ResizeMove);
        SystemEvents.DisplaySettingsChanged -= new EventHandler(SystemEvents_DisplaySettingsChanged);
        VidCardGlobal.VideoHandlersAdded = false;
    }
    public void VideoFeed_ResizeMove(object sender, EventArgs e)
    {
        if (VidCardGlobal.WindowlessControl != null)
        {
            int hr = VidCardGlobal.WindowlessControl.SetVideoPosition(null, DsRect.FromRectangle(this.VideoPanel.ClientRectangle));
        }
    }
    private void VideoFeed_Paint(object sender, PaintEventArgs e)
    {
        if (VidCardGlobal.WindowlessControl != null)
        {
            IntPtr hdc = e.Graphics.GetHdc();
            int hr = VidCardGlobal.WindowlessControl.RepaintVideo(this.VideoPanel.Handle, hdc);
            e.Graphics.ReleaseHdc(hdc);
        }
    }
    private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
    {
        if (VidCardGlobal.WindowlessControl != null)
        {
            int hr = VidCardGlobal.WindowlessControl.DisplayModeChanged();
        }
    }
}
}

ETA: FilterGraph Definition

using System;
using System.Runtime.InteropServices;

namespace DirectShowLib
{
// CODE SNIPPED
#region COM Class Objects
/// <summary>
/// CLSID_FilterGraph
/// </summary>
[ComImport, Guid("e436ebb3-524f-11ce-9f53-0020af0ba770")]
public class FilterGraph
{
}
}
PGP_Protector
  • 218
  • 3
  • 12
  • 2
    Perhaps the library that you're using depends on the ApplicationIdle event. If so, use the answer to this question to raise the event: http://stackoverflow.com/questions/2344398/application-idle-event-not-firing-in-wpf-application – RQDQ Apr 04 '13 at 18:06
  • 1
    the Start command creates a new VidCardGlobal that used DirectShowLib, ISampleGrabberCB & IDsiposable. no Idle events that I csee. It Does use the IMediaControl though. – PGP_Protector Apr 04 '13 at 18:14
  • If that library depends on the ApplicationIdle event, you wouldn't see it in the interface. That would be internal to the code. – RQDQ Apr 04 '13 at 20:21
  • We have the code for the internal library. – PGP_Protector Apr 04 '13 at 22:04
  • 1
    Have you tried stepping through the code to see where its hanging up? It sounds almost like a runaway for loop somewhere. – rossisdead Apr 04 '13 at 22:42
  • It steps through and exits the library fine after creating the Filter, but I'm not seeing how to debug the IMediaControl itself { MediaControl.Run();} (IF I remove that line, no hang up, but no video either) – PGP_Protector Apr 04 '13 at 23:55
  • In VidCardGlobal - is FilterGraph a framework class or do you have code for it? – RQDQ Apr 05 '13 at 12:16
  • Filter Graph is from the DirectShowLib.dll (I think the code is Open Source, but I don't have it on hand) Definition is added to OP. – PGP_Protector Apr 05 '13 at 15:39

0 Answers0