none
Need an expert consultation RRS feed

  • Question

  • I have some Win32 code (class), it works with unsafe memory (grabbing sound from WaveIn device), and works just fine in Console App :)
    Of course, it uses unmanaged memory location for data buffering... everything is correct, but under
     WinForms App it crashes each time.

    The cause is Garbage Collection, just quite sure its it. Why? Just no idea.. intuition. I spent lot of time investigating this problem.

    O instantiate in inside a separate Thread with realtime priority + STA mode. It crashes..Now I am planning to use it as hidden runtime loading Console App, and control it through Pipes command protocol.

    Please, ;et me know if there is a way to run a code under WinForms and as it works in Console App?
    I mean may be I should use unmanaged Threading? How can I say CLR to leave along that Thread and let it work in STA mode like a Comsole App code but without the Console?
    Managed Thread doesn't resolve my problem..

    Friday, May 17, 2019 12:42 AM

Answers

  • The Answer is:  

    IDisposable Interface
    Provides a mechanism for releasing unmanaged resources.

    You should add IDisposable to your class where you're realizing WaveInAudio WaveInProc..
    Also, the buffer and the structure - they should be pinned.

    And the release of the unmanaged resources you do inside your Dispose method.

    In my case I have create a separate class waveInBuffer

        class WaveInBuffer : IDisposable
        {
            private IntPtr Me;
            public Wave.WAVEHDR header;
            private readonly byte[] buffer;
            private GCHandle hHeader;
            private GCHandle hBuffer;
            public WaveInBuffer(int szBuffer)
            {
                Me = (IntPtr) GCHandle.Alloc(this);
                this.buffer = new byte[szBuffer];
                this.hBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
                header = new Wave.WAVEHDR();
                hHeader = GCHandle.Alloc(header, GCHandleType.Pinned);
                header.lpData = hBuffer.AddrOfPinnedObject();
                header.dwBufferLength = (uint)szBuffer;
            }
    
            public void Dispose()
            {
                hHeader.Free();
                hBuffer.Free();
                GCHandle.FromIntPtr(Me).Free();
                GC.SuppressFinalize(this);
            }
            ~WaveInBuffer() { Dispose(); }
        }

    By doing that, GC will not touch your unmanaged resources... and to add buffer and prepare just address to the WAVEHDR as 

    WaveInBuffer buffer = new WaveInBuffer(your size);
    // and then just 
    WaveInAddBuffer(..... buffer.header....)
    
    // In you waveInProc you'll receive WAVEHDR that will be already protected from GC by IDisposable.. 
    waveInproc(....... Wave.WAVEHDR header ...)
    // Now do what you have to, according to MS WaverAudio guidelines.
    
    // and when you stop WaveInStop or Reset then just do not forget to
    buffer.Dispose();
    
    // Also you should consider to use at least 2 buffers..


    So, afterall I was absolutely correct blaming my problem in my case to GC.

    You need my assistance - just skype to: robbkirk

    UPD: Testing that code during long period of time, I have got once the GPF error.
    So, therefore, to make your (based on WaveAudio) App more robust - better use Console App and remote control from WinForm.. or if you're like - use other techs..





    • Marked as answer by RobbKirk Tuesday, May 21, 2019 10:51 PM
    • Edited by RobbKirk Saturday, May 25, 2019 5:29 AM
    Tuesday, May 21, 2019 10:50 PM

All replies

  • If you suspect it's related to GC, then you're not allocating memory with Marshal.AllocHGlobal() and its friends. If you use them, then you're allocating unmanaged memory which GC will not even try to manage, and GC will not cause problem to you. (Note: You must do cleanup in your finalizer yourself or there will be memory leak / open handles even after your program is closed)

    Check NAudio which is a wrapper for those WinMM APIs to see how they do it, they even got example on how to use it to do wavein recordings.


    Friday, May 17, 2019 2:10 AM
    Answerer
  • Thank you Cheong00,

    I've seen that Lib before, but I haven't noticed they published sources..
    I think they do wrong in Wave In mode.

    this condition 

    if (buffer == null) return;

    On row #137 at this listing shouldn't be happen.

    Ignoring this case, means they lost one of the inputs buffers.

    That means they would not be able to record longer than 1 minute.. sometimes even less.

    If they check it, it means they were also unable to resolve that problem.

    Under Console mode this condition never happen to me for about 1 week in

    non stop recording mode.





    • Edited by RobbKirk Friday, May 17, 2019 8:28 AM
    Friday, May 17, 2019 8:23 AM
  • See the PInvokeLibrary from MS (15 years old...) that I had posted with C# and VB.NET sources to record wav (class WaveIn)
    • Proposed as answer by phil chelis Saturday, May 18, 2019 8:16 AM
    • Unproposed as answer by RobbKirk Sunday, May 19, 2019 1:52 AM
    • Proposed as answer by phil chelis Sunday, May 19, 2019 6:40 AM
    • Unproposed as answer by RobbKirk Tuesday, May 21, 2019 9:50 PM
    Friday, May 17, 2019 8:29 AM
  • I don't see what's the problem here. If GCHandle.Target is null, then there's no data. What else can be done other then returning? It's just proper guarding of null pointer when GCHandle is used.

    And I don't see why they cannot record longer than 1 minute, because the callback will be invoked multiple times when receiving WIM_DATA. As long as they keep the device open, when WinMM received enough data it'll send the message again.



    Friday, May 17, 2019 9:56 AM
    Answerer
  • I don't see what's the problem here. If GCHandle.Target is null, then there's no data. What else can be done other then returning? 

    I see.. because it should pass continuously data.. Buffer would not be zeroed.. even because sample has zero value = 127 in case of 8 bits. So even if it's quiet, you'll still be receiving data..
    Zero buffer when you have 2 buffers - is something which is abnormal.
    • Edited by RobbKirk Friday, May 17, 2019 1:00 PM
    Friday, May 17, 2019 12:59 PM
  • You see, as the WaveInBuffer is instantiated, the unmanaged buffer and structures are pinned. You'll want to use as little unmanaged memory object as possible, or when your program runs for a long time, it can have memory fragmentation.

    On DataAvailable event you should have copied data elsewhere or written it to file. The multiple buffer is just kind of safety measure that you may not have done with the data when new data has returned.

    Friday, May 17, 2019 2:56 PM
    Answerer
  • Thank you for your efforts to help..

    Sorry, but you trying to explain an obvious.. theoretically it is always easy..

    I do my own task, and I see what's going on, so.. the question is still open - why it works perfectly in Console App but not as a separate Thread with STA under WinForms?

    Also, pls, consider me as the man with 30+ years in IT )) If that would be a question about Pinvoke I wouldn't be even asking it.

    What I see is.. in NAudio they use different strategy for callback - "Window strategy".. I use Function, and I tried "Window" with the same poor result.

    The character sign of GC bad work under multithreading is - when you have built your App once and run = it works, but GC gathers statistics of usages, and at the second time and further it uses that statistic and acts more surely to do what it wants to do. When it gathers statistics, it doesn't touch sensitive parts in unmanged area, and code works somehow, but the second time it takes that stats and clean whatever it decided to be cleaned - and the App crushes much earlier, than at first run after Rebuild.

    Haven't you been noticed that? .. and I know it takes some efforts to find where your unmanaged area becomes useless from the point of view of GC.

    Same is now. When I use winMM it creates extra UNMANAGED thread, that uses memory, that I pinned by GC  inside managed area and passed to that Win32 Thread just like to some Black Box.

    So, when under single thread Console App everything is visible and workable, but when it's put inside WinForms, multithreaded by nature App, then it crushes, and it's hard to get the answer Why.. to me..
    I can't see the full picture.

    As I said NAudio will not work properly in my case:
    Look at this:

            /// <summary>
            /// Called when we get a new buffer of recorded data
            /// </summary>
            private void Callback(IntPtr waveInHandle, WaveInterop.WaveMessage message, IntPtr userData, WaveHeader waveHeader, IntPtr reserved)
            {
                if (message == WaveInterop.WaveMessage.WaveInData)
                {
                    if (recording)
                    {
                        var hBuffer = (GCHandle)waveHeader.userData;
                        var buffer = (WaveInBuffer)hBuffer.Target;
                        if (buffer == null) return;
    
                        lastReturnedBufferIndex = Array.IndexOf(buffers, buffer);
                        RaiseDataAvailable(buffer);
                        try
                        {
                            buffer.Reuse();
                        }
                        catch (Exception e)
                        {
                            recording = false;
                            RaiseRecordingStopped(e);
                        }
                    }
    
                }
            }

    The WinInAudio takes as many buffers as you pass.. Then, during recording, in gives you each buffer back, like a yield with recorded data.. and EACH BUFFER YOU PASSED AND ONE BY ONE.
    Then. after you received the yield and have data gotten, you have to REUSE that buffer and send in back to reader. They do it with 

    buffer.Reuse();
    Now pls follow their logic.. if 
    if (buffer == null) return;
    Then this buffer will not be added.. means they just lost one buffer.. So, if you have 2 or 3 buffers only, then this loop will degrade your potential volume of recorded data.
    You'll be out of harvest.





    • Edited by RobbKirk Sunday, May 19, 2019 3:46 AM
    Sunday, May 19, 2019 2:12 AM
  • Also, pls, consider me as the man with 30+ years in IT )) If that would be a question about Pinvoke I wouldn't be even asking it.

    It has nothing to do with PInvoke

    The answer is about the PInvokeLibrary , a Microsoft class showing how to record WAV with WaveIn apis

    It has been copied/adapted everywhere for nearly 20 years

    Why are you trying to re-invent the wheel when there is well-known working code from Microsoft ?!




    Sunday, May 19, 2019 6:46 AM
  • It has nothing to do with PInvoke

    The answer is about the PInvokeLibrary , a Microsoft class showing how to record WAV with WaveIn apis


    Exactly, it is a Namespace :

        /// Encapsulates Waveform Audio Interface recording functions and provides a simple
        /// interface for recording audio.

    and it shows how to use buffers
    • Edited by valat Sunday, May 19, 2019 7:36 AM
    Sunday, May 19, 2019 7:12 AM
  • Then to me it's normal to reinvent something.. I always do it when have to.

    I can't find that Lib in Google, so pls, try to give me the reference. 

    Also, the functionality of the Wave Audio is quite simple to me, and if I've expected, that following its instructions I will come to result, then why SHOULD I search for a third party  library? :)

    I just reported, that under WinForms it doesn't work, but as a Console it works..
    And I do exactly what they (MicroSoft) guide me to do.
    And I think I will completely understand not WaveAudio, but CLR and its ability to work with WaveAudio.

    P.S. By the way, if you say 20 years ago.. then it was without Windows 7 yet. I do the code under Windows 7, so.. maybe it has some influence to the result.
    I remember most of the codes between WinAPI and Windows XP were operable.
    • Edited by RobbKirk Sunday, May 19, 2019 10:15 AM
    Sunday, May 19, 2019 10:03 AM
  • .NET Compact Framework Sample: P/Invoke Library

    mainly WaveIn.cs

    I used it (some adaptations in VB) and it worked fine on Windows 10

    But more recent methods are with WASAPI (like RecordAudioStream function that I also tested in VB)


    Sunday, May 19, 2019 10:52 AM
  • The character sign of GC bad work under multithreading is - when you have built your App once and run = it works, but GC gathers statistics of usages, and at the second time and further it uses that statistic and acts more surely to do what it wants to do. When it gathers statistics, it doesn't touch sensitive parts in unmanged area, and code works somehow, but the second time it takes that stats and clean whatever it decided to be cleaned - and the App crushes much earlier, than at first run after Rebuild.

    No. Remember what I told you? "Note: You must do cleanup in your finalizer yourself or there will be memory leak / open handles even after your program is closed"

    If you're not freeing the memory you allocated yourself, then no matter what approach you try to use, the situation will not improve.

    Then this buffer will not be added.. means they just lost one buffer

    No. That technically will not be run, but if you don't add that checking some static code analysis application will scream.

    And btw if there aren't any buffer pointed by GCHandle, that pass contains no data, and therefore no data would be loss. And because there is no buffer structure it points to, no reinitialization need to be done.

    Also, pls, consider me as the man with 30+ years in IT )) If that would be a question about Pinvoke I wouldn't be even asking it.

    I wonder, have you done code tracing with Windows Form (not necessarily .NET framework specific) before? The question you asked should be able to solve by yourself if you had some experience of message loop handling in Windows. Afterall there is only like 5 source files involved, and they're not exceptionally long one.


    Sunday, May 19, 2019 11:09 AM
    Answerer
  • And btw if there aren't any buffer pointed by GCHandle, that pass contains no data, 
    and therefore no data would be loss.
    And because there is no buffer structure it points to, no reinitialization need to be done.

    They do not loose data. they loose the carrier for data. Do you understand that?
    Pls, do not "troll" me anymore, just open MS WaveInAudio guide and read.. otherwise what you saying is just the useless smoke..:)

    Thank you anyway for your effort to help, we just speak different languages.. sorry, mate.

    Sunday, May 19, 2019 5:43 PM
  • Thank you.. but I prefer to finish with MY OWN CODE.. I do not care about some other codes. 
    Well, I didn't find anything new to me in NAUDIO besides they use different callback strategy..

    All other parts are very similar..

    I wrote a lot of code, and Win32 API were never too hard to me.. but this time I said - I would accept it as the Console App with remote control through pipes.

    Sunday, May 19, 2019 5:55 PM
  • Still not clear what you mean by "loose the carrier for data".

    It's using buffers[] to allocate and hold all those carriers, and they're selected into use by:

    index = (n + lastReturnedBufferIndex + 1) % buffers.Length

    There's no way for any of these buffers be lost on the way.


    Monday, May 20, 2019 1:19 AM
    Answerer
  • It works like that..

    You create WAVEHDR structures, where you describe size of buffer and pointer to data BUFFER.
    So, it you play with double buffering, then you pass 2 structs; if 3 - 3.
    To pass it you use schema - waveInPrepareBuffer (UnprepareBuffer) and waveInAddBuffer.

    Once you passed 2 of them, you're ready to run the sampler.

    When data received, it fills the buffer 1, then pass with that callback the WAVEHDR struct.
    You take that data and copy to your safe location to proceed with them further.

    And after that that WAVEHDR is useless.. the sampler released it to you.. so if at he start there were 2 buffers in the queue, then getting back that header to you, the sampler keeps only one buffer.

    While it fills data into that last, you have to re-Prepare and re-Add the same STRUCT in its queue..

    Example - there is an open tap with water and you fill your 2 jars with water.. in cycle.. one by one..
    But you place you jar inside a cartridge, that carries your jar..

    So, you received your cartridge with the empty jar and skip that you have to "re-arm the drum of your gun".  Because once you received in with callback, it is released..

    You can add a new cartridge, but this situation with empty buffer is abnormal by itself.. it is wrong behavior.. It never happens to me in Console mode. It means that inside the sampler Thread something is wrong with timing or with pointers to that carriers.
    I thought it is because I didn't pin area for that WAVEHDRs, but even pinning it + pinning buffers themselves didn't resolve the conflict, and system during sampling is degrading and stops... after several seconds. depending on the size of the requested buffers... the bigger buffer the longer sampling timing.

    Monday, May 20, 2019 4:47 AM
  • I thought I've mentioned that this code is added to satisfy those "static code analysis" checkers, or you personally seen that happens when running their sample code?
    Tuesday, May 21, 2019 1:11 AM
    Answerer
  • .NET Compact Framework Sample: P/Invoke Library

    mainly WaveIn.cs

    I used it (some adaptations in VB) and it worked fine on Windows 10

    But more recent methods are with WASAPI (like RecordAudioStream function that I also tested in VB)

    Thank you mate for your recommendation. I think WASAPI was written using the same waveAudio but under COM tech.
    To me it's equal the same as if I will write my code as Console App or even WinService and will get it controlled through Pipes... or if I will use COM tech also..
    As I said, WaveAudio is very stable under Console STA App..
    Tuesday, May 21, 2019 1:38 AM
  • The Answer is:  

    IDisposable Interface
    Provides a mechanism for releasing unmanaged resources.

    You should add IDisposable to your class where you're realizing WaveInAudio WaveInProc..
    Also, the buffer and the structure - they should be pinned.

    And the release of the unmanaged resources you do inside your Dispose method.

    In my case I have create a separate class waveInBuffer

        class WaveInBuffer : IDisposable
        {
            private IntPtr Me;
            public Wave.WAVEHDR header;
            private readonly byte[] buffer;
            private GCHandle hHeader;
            private GCHandle hBuffer;
            public WaveInBuffer(int szBuffer)
            {
                Me = (IntPtr) GCHandle.Alloc(this);
                this.buffer = new byte[szBuffer];
                this.hBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
                header = new Wave.WAVEHDR();
                hHeader = GCHandle.Alloc(header, GCHandleType.Pinned);
                header.lpData = hBuffer.AddrOfPinnedObject();
                header.dwBufferLength = (uint)szBuffer;
            }
    
            public void Dispose()
            {
                hHeader.Free();
                hBuffer.Free();
                GCHandle.FromIntPtr(Me).Free();
                GC.SuppressFinalize(this);
            }
            ~WaveInBuffer() { Dispose(); }
        }

    By doing that, GC will not touch your unmanaged resources... and to add buffer and prepare just address to the WAVEHDR as 

    WaveInBuffer buffer = new WaveInBuffer(your size);
    // and then just 
    WaveInAddBuffer(..... buffer.header....)
    
    // In you waveInProc you'll receive WAVEHDR that will be already protected from GC by IDisposable.. 
    waveInproc(....... Wave.WAVEHDR header ...)
    // Now do what you have to, according to MS WaverAudio guidelines.
    
    // and when you stop WaveInStop or Reset then just do not forget to
    buffer.Dispose();
    
    // Also you should consider to use at least 2 buffers..


    So, afterall I was absolutely correct blaming my problem in my case to GC.

    You need my assistance - just skype to: robbkirk

    UPD: Testing that code during long period of time, I have got once the GPF error.
    So, therefore, to make your (based on WaveAudio) App more robust - better use Console App and remote control from WinForm.. or if you're like - use other techs..





    • Marked as answer by RobbKirk Tuesday, May 21, 2019 10:51 PM
    • Edited by RobbKirk Saturday, May 25, 2019 5:29 AM
    Tuesday, May 21, 2019 10:50 PM
  • //Sigh

    Your problem is not related GC because you explicitly tell GC don't bother free it for you. But whatever...

    Wednesday, May 22, 2019 2:49 AM
    Answerer
  • You very like to argue, mate..
    As I said, I was pinning that areas as well, but I didn't encapsulate all unmanaged data in a separate object, and wasn't using its pointer to access to data, that's why GC, having no idea what's going on in unmanaged area, was releasing that memory by itself.. Again.. I WAS PINNING THAT AREAS AND STILL HAD THAT PROBLEM.
    Only after all buffers and headers were separated, and with IDisposable mechanism, since that GC stopped releasing that memory.

    And as I said before, even without all that tricky stuff, under Console App, it was working just fine..

    And finishing that, it is of course about GC.. only and literally about GC.

    Wednesday, May 22, 2019 10:14 PM
  • I *need* to add that comment because what you're said is total misconception of how GC works. In your cause GC is working exactly as how it's documented to be, just that you use it in a wrong way.

    Stop shift the blame to components when you're the one misusing it. It's one thing you don't know the correct way to use components because we all have such moments, but knowing the root cause is your responsibility and still blaming it is quite another.


    Thursday, May 23, 2019 1:42 AM
    Answerer