-
[RESOLVED] App Crashes Only If Shelled
I'm working on this sound application that uses waveIn/waveOut to capture sound and display a sine wave in real time. If I run the app as a stand-alone it works perfectly but if I shell it then it will crash. I have narrowed it down to where the crash takes place.
Code:
'
'
Public Type WAVEHDR
lpData As Long
dwBufferLength As Long
dwBytesRecorded As Long
dwUser As Long
dwFlags As Long
dwLoops As Long
lpNext As Long
reserved As Long
End Type
Private Type WaveInBuffer
hdr As WAVEHDR
intBuffer() As Integer
pMem As Long
End Type
Public Function StartRecord(ByVal samplerate As Long, ByVal Channels As Integer) As Boolean
Dim udtWFX As WAVEFORMATEX
Dim res As Long
Dim i As Long
Dim j As Long
ReDim udtBuffers(lngBufCnt - 1) As WaveInBuffer
With udtWFX
.wFormatTag = WAVE_FORMAT_PCM
.nSamplesPerSec = samplerate
.nChannels = Channels
.wBitsPerSample = 16
.nBlockAlign = Channels * (.wBitsPerSample / 8)
.nAvgBytesPerSec = .nBlockAlign * .nSamplesPerSec
.cbSize = 0
End With
Const CALLBACK_FUNCTION = &H30000
res = waveInOpen(hWaveIn, lngCurDev, udtWFX, AddressOf waveInProc, 0, CALLBACK_FUNCTION)
If res <> MMSYSERR_NOERROR Then
Exit Function
End If
' prepare headers/buffers
For i = 0 To lngBufCnt - 1
With udtBuffers(i)
ReDim .intBuffer(lngBufSize / 2 - 1) As Integer
.pMem = VarPtr(.intBuffer(0))
.hdr.dwBufferLength = lngBufSize
.hdr.lpData = .pMem
.hdr.dwUser = i
res = waveInPrepareHeader(hWaveIn, .hdr, Len(.hdr))
If res <> MMSYSERR_NOERROR Then
' on error unprepare all prepared headers
For j = (i - 1) To 0 Step -1
waveInUnprepareHeader hWaveIn, .hdr, Len(.hdr)
Next
waveInClose hWaveIn
hWaveIn = 0
Exit Function
End If
End With
Next
res = waveInStart(hWaveIn)
If res <> MMSYSERR_NOERROR Then
For i = 0 To lngBufCnt - 1
waveInUnprepareHeader hWaveIn, udtBuffers(i).hdr, Len(udtBuffers(i).hdr)
Next
waveInClose hWaveIn
hWaveIn = 0
Exit Function
End If
For i = 0 To lngBufCnt - 1
'
' THIS IS WHAT CAUSES APP TO CRASH. IF I COMMENT OUT THEN
' APP WILL NOT CRASH. IF I EXECUTE BELOW APP CRASHES
'
' NOTE: THIS ONLY OCCURS IF THIS APP IS SHELLED BY ANOTHER APP
'
waveInAddBuffer hWaveIn, udtBuffers(i).hdr, Len(udtBuffers(i).hdr)
Next
StartRecord = True
End Function
Private Sub waveInProc(ByVal hwi As Long, _
ByVal uMsg As Integer, _
ByVal dwInstance As Long, _
ByVal dwParam1 As Long, _
ByVal dwParam2 As Long)
Dim udtHdr As WAVEHDR
Select Case uMsg
Case MM_WIM_OPEN
Case MM_WIM_DATA
If IsBadReadPtr(ByVal dwParam1, Len(udtHdr)) = 0 Then
CpyMem udtHdr, ByVal dwParam1, Len(udtHdr)
GotData udtBuffers(udtHdr.dwUser).intBuffer, udtHdr.dwBytesRecorded
' place the buffer in the waveIn queue again
waveInAddBuffer hWaveIn, udtBuffers(udtHdr.dwUser), Len(udtHdr)
Else
End If
Case MM_WIM_CLOSE
End Select
End Sub
I'd like to also point out that if I do not comment out the failing statement above but I disallow the callback proc from being entered the app will not crash. If I do comment out the failing statement and allow subclassing also app will not crash. So, the problem only occurs if all I do is shell this app otherwise it runs perfectly
-
Re: App Crashes Only If Shelled
Does it crash while loading or after the app has been running?
-
Re: App Crashes Only If Shelled
When the app is shelled nothing takes place until the Start Recording button is clicked. On click it immediately enters Public Function StartRecord which runs to the point I indicated in red.
1) Run App, click on Start Recording, App runs without any problems
2) Shell App, click on Start Recording, App crashes at indicated statement
Here's something interesting. I have two versions of this App. This one, which crashes if Shelled and another which does not crash if Shelled. The difference between the two Apps is this:
1) App that crashes uses waveInOpen(hWaveIn, lngCurDev, udtWFX, AddressOf waveInProc, 0, CALLBACK_FUNCTION) and the waveInProc
2) App that does not crash uses waveInOpen(hWaveIn, lngCurDev, udtWFX, lngHwnd, 0, CALLBACK_WINDOW) and Paul Caton's In-Class 'SelfSub' subclassing code
In either case the code in the callback sub is identical, only the method used to establish the callback differs.
It isn't that I can't use the second non-crashing App but I would prefer to use the one with waveInProc callback.
-
Re: App Crashes Only If Shelled
When you ran your app as a "stand-alone" program, you probably opened it via Windows Explorer. If so, then your app was "shelled" by explorer.exe. I fail to see why shelling your app with your other app would cause it to crash. Have you confirmed that the waveInAddBuffer function in your For loop that crashes always returns success (MMSYSERR_NOERROR)? What else can you describe about your app that might offer some clue?
-
Re: App Crashes Only If Shelled
waveInProc's uMsg parameter should be declared As Long.
-
Re: App Crashes Only If Shelled
OK, I just now discovered something. When I run app as stand-alone it is from the IDE and it never crashes but if I run it from the EXE (I double click on the EXE file) it will crash. So, that now eliminates the Shelling since it crashes when run as an executable instead of from the IDE.
I changed Integer to Long for uMsg
I put error checking in loop, returns success on all counts.
I put blocker switch in waveInProc to disallow entry into proc like this:
Code:
Private Sub waveInProc(ByVal hwi As Long, _
ByVal uMsg As Integer, _
ByVal dwInstance As Long, _
ByVal dwParam1 As Long, _
ByVal dwParam2 As Long)
Dim udtHdr As WAVEHDR
If NoEntry = True Then Exit Sub
Select Case uMsg
Case MM_WIM_OPEN
Case MM_WIM_DATA
If IsBadReadPtr(ByVal dwParam1, Len(udtHdr)) = 0 Then
CpyMem udtHdr, ByVal dwParam1, Len(udtHdr)
GotData udtBuffers(udtHdr.dwUser).intBuffer, udtHdr.dwBytesRecorded
' place the buffer in the waveIn queue again
waveInAddBuffer hWaveIn, udtBuffers(udtHdr.dwUser), Len(udtHdr)
Else
End If
Case MM_WIM_CLOSE
End Select
End Sub
Now it will pass through the entire StartRecord sub without crashing. Then from a button on the Form that triggers the blocker switch I click on the button which sets NoEntry = False thus allowing entry into waveInProc the app then crashes. However this just re-confirms a statement I made in my OP...
"I'd like to also point out that if I do not comment out the failing statement above but I disallow the callback proc from being entered the app will not crash."
-
Re: App Crashes Only If Shelled
I suspect that your problem has to do with how threading for IDE runs differs from threading in a compiled application.
Your callback function is probably being run on a thread created within Winmm.dll, and when it runs COM and most of the VB environment has not been initialized for that thread. This means you fail on your CpyMem call, which won't work if you defined it using Declare Sub.
The workaround would be to define CpyMem using a reference to a typelib you create. This is a much more direct way of making API calls than via the slow, clunky, non-threadsafe Declare.
Of course if your callback routine contains much of anything else besides simple VB statements you'd still be doomed. Then you'll have to use CALLBACK_WINDOW instead.
-
Re: App Crashes Only If Shelled
OK, so how do I create a typelib and then reference it? I have never done anything like that.
-
Re: App Crashes Only If Shelled
Writing Type Libraries covers the bare bones of the process. But I'm not aware of any "for dummies" tutorial or cookbook one might follow in order to create a "one off" TLB without some investment in study.
There are a few well-known TLBs available for free download that happen to cover RtlMoveMemory but they may not have the function signature you are using in your program.
Standalone typelibs are typically written in either Microsoft IDL (MIDL) or in ODL (quite similar). These are "compiled" into type libraries using the old MkTypLib.exe compiler or the newer Midl.exe compiler which supports the mktyplib203 compatibility switch for accepting ODL source. See:
Differences Between MIDL and MkTypLib
Be very careful when writing or creating derivitive IDL/ODL from someone else's. There are GUIDs in there which you should never simply copy but instad generate and use fresh ones!
Here is an example of one I used in a webcam capture Project for reasons very like those you are facing here:
RCCopyMemory.odl
Code:
[
uuid(18BAF3E7-5618-4c70-80EF-0E98A003BF0D),
helpstring("RemCam CopyMemory Type Library")
]
library RCCopyMemory
{
[
helpstring("Kernal32 RtlMoveMemory Call"),
dllname("KERNEL32")
]
module CopyMemoryMod
{
[
helpstring("Copy the contents of one buffer to another."),
entry("RtlMoveMemory")
]
void _stdcall CopyMemory
(
[in] void __unaligned *Destination,
[in] const void __unaligned *Source,
[in] long Length
);
};
};
This one defines a "Sub CopyMemory" that accepts Destination and Source as "ByVal As Long" pointer values. With some changes you can rejigger these to "ByRef As Byte" or even "ByRef As Any" instead, but this is more straightforward and it is easy enough to use VarPtr(), StrPtr(), etc. where required.
It can be compiled into a TLB using the MIDL compiler via a RunMidl.cmd file like:
Code:
midl.exe /mktyplib203 RCCopyMemory.odl >log.txt
However you first have to obtain midl.exe, several DLLs it uses, and several "base type" .IDL files that the MIDL compiler auto-includes. All of these and more can be obtained by installing a recent Windows SDK. Note that there is no XP version of the Windows SDK available online any more as WinXP is being phased out of support.
-
Re: App Crashes Only If Shelled
BTW:
Once created you should place the resulting .TLB file into a "private" folder under:
C:\Program Files\Common Files
You want to do this so you don't risk registry contamination or registration orphaning as you work on projects. You can register the TLB from within the VB6 IDE simply by browsing to the file from the Project|References... dialog.
There are also some old Microsoft command line utilites for registering and unregistering TLBs as well as some useful 3rd party GUI-based tools.
Once you have added such a reference to a given VB6 Project and saved it you can even unregister the TLB. VB6 will still find it (as long as you don't move the file) based on info it stores in the .VBP file.
Of course once you unregister it no new Project will be able to find it so you have to go through the process again. For this reason you may be better off just using one of the ready-made generic TLBs, put it in its permanent spot, register it once, and leave it there forever.
Unlike unused Declare statements, unused TLB-based declarations don't add to the size of your compiled program. Declare results in run-time overhead while a TLB is just used by the compiler. So a "giant" TLB with tons of things defined in it doesn't cost you anything more to use it.
Look for Edanmo's OLELIB.TLB, something every VB6 programmer ought to have on hand. It has RtlMoveMemory defined as "MoveMemory" using "As Any" for source and dest arguments.
-
Re: App Crashes Only If Shelled
OK, thanks dilettante, that's all new to me but I will give it a try and see how well I do. Although I figured out a way to make my current app work I don't think it's the best way to do it but it does work. What I did was add a Timer control to Form1 and set it's interval to 1. Now in the waveInProc I simply place the value of dwParam1 in a Public variable, set Timer1.Enabled = True and exit sub. Timer1_Timer() grabs the value from the Public variable and runs the exact code that was in the waveInProc.
Oh, BTW, it isn't just the CpyMem API that causes App to crash; it's any one of the three APIs:
IsBadReadPtr, CpyMem and waveInAddBuffer
-
Re: App Crashes Only If Shelled
Quote:
Originally Posted by
jmsrickland
Oh, BTW, it isn't just the CpyMem API that causes App to crash; it's any one of the three APIs:
IsBadReadPtr, CpyMem and waveInAddBuffer
I sort of expected this from what you are doing in this program. So you'd need a custom type library or those Declare calls will crash within your callback procedure.
Also see:
waveInProc callback function
Quote:
Remarks
Applications should not call any system-defined functions from inside a callback function, except for EnterCriticalSection, LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, OutputDebugString, PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime, timeGetTime, timeKillEvent, and timeSetEvent. Calling other wave functions will cause deadlock.
-
Re: [RESOLVED] App Crashes Only If Shelled
I read that remark from MS. That's what then made me think about the other APIs I was using.
I have a C written app that also uses waveInProc and in that Proc are the CopyMemory and some other waveIn APIs. It works. Why?
-
Re: [RESOLVED] App Crashes Only If Shelled
Most likely because the C runtime isn't COM-based and the C compiler doesn't generate calls into the VB6 runtime - so code running on a callback thread doesn't need COM and the VB6 runtime initialized for that thread.
The API calls aren't what is failing in your program: you never get that far. What fails is the use of Declare which results in a call into the VB6 runtime to interpretively resolve the reference. When you use Declare it just causes the compiler to put data into a table to be used at runtime for finding the API. Using a type library gives the compiler "trusted" information it needs to compile a direct call to the API.
Sort of like the difference between early and late binding of COM objects, but somewhat different. This is why API calls using Declare can be far slower as well. The performance difference doesn't always matter but when it does it can be a killer.
-
Re: App Crashes Only If Shelled
Quote:
Originally Posted by
dilettante
Look for
Edanmo's OLELIB.TLB, something every VB6 programmer ought to have on hand. It has RtlMoveMemory defined as "MoveMemory" using "As Any" for source and dest arguments.
I see where it has MoveMemory in it. Does this mean that if I reference that TBL in any VB project I do not have to Declare RtlMoveMemory in my project
-
Re: App Crashes Only If Shelled
Quote:
Originally Posted by
dilettante
It can be compiled into a TLB using the MIDL compiler via a RunMidl.cmd file like:
Code:
midl.exe /mktyplib203 RCCopyMemory.odl >log.txt
However you first have to obtain midl.exe, several DLLs it uses, and several "base type" .IDL files that the MIDL compiler auto-includes. All of these and more can be obtained by installing a recent Windows SDK. Note that there is no XP version of the Windows SDK available online any more as WinXP is being phased out of support.
I found MIDL.EXE along with MIDLES.H on my system in a VC98 folder. So is this one you are referring to
-
Re: [RESOLVED] App Crashes Only If Shelled
I copied you ODL source, used MIDL.EXE and compiled it into a TBL. I opened up a new VB project and gave reference to that TBL. In a Sub I used it like this:
Code:
Private Sub Command1_Click()
Dim Dest As String * 100
Dim Src As String
Src = "THIS IS A TEST OF COPYMEMORY"
CopyMemory ByVal Dest, ByVal Src, 28
End Sub
It worked.
Now, all I have to do is to learn how to make these ODL files.
-
Re: [RESOLVED] App Crashes Only If Shelled
Yes, that ancient MIDL compiler should still work - though it isn't guaranteed to on a post-XP version of Windows (none of VC 6.0 is supported post-XP).
Glad to hear it helps. Good luck.
-
Re: [RESOLVED] App Crashes Only If Shelled
@dilettante: Have you ever thought of setting up a Web Page with all your 'pearls of Wisdom'? It'd save me hours searching these Forums for gems of knowledge (perhaps 'dilettane-pidea') :)
-
Re: [RESOLVED] App Crashes Only If Shelled
Quote:
Originally Posted by
Doogle
@dilettante: Have you ever thought of setting up a Web Page with all your 'pearls of Wisdom'?
He already have.
-
Re: [RESOLVED] App Crashes Only If Shelled
Yeah, but that site is very old and very stale. Worst part is I'd probably have to request my "lost password" to even update it now!
Keeping a web site up to date is a lot of work. It is hard just to keep updating the "style" of a site to make it more useful to people (navigation, decent text layout, etc.) as you gain more appreciation for how other people do things.
-
Re: [RESOLVED] App Crashes Only If Shelled
I kind of thought that myself
-
Re: [RESOLVED] App Crashes Only If Shelled
I read where I need uuid.exe to generate the uuid I need for making ODL files. I found uuidgen.exe on my pc but not uuid.exe. Does uuidgen.exe do the same as uuid.exe
-
Re: [RESOLVED] App Crashes Only If Shelled
@dilettante
Still crashes.
As I said in post #17 it worked - no problem....but
...when I put the exact same code in the waveInProc the app crashed. This was the only code I used as I commented out everything else in the proc and only the code I used in post #17 was in the proc.
Also, The CopyMemory API was commented out
-
Re: [RESOLVED] App Crashes Only If Shelled
Well you can't say you didn't try hard.
Maybe the problem is related to those cautions about the only calls that are safe from within a waveInProc callback?
-
Re: [RESOLVED] App Crashes Only If Shelled
It's not just yours. I made another typelib using an MS example and that too causes crash. Looks like that sort of kills your theory about all that COM and TypeLib stuff.
Kind of defeats the purpose of having a callback
-
Re: [RESOLVED] App Crashes Only If Shelled
If the app only crashes when you call
vb Code:
Private Sub waveInProc(ByVal hwi As Long, _
ByVal uMsg As Integer, _
ByVal dwInstance As Long, _
ByVal dwParam1 As Long, _
ByVal dwParam2 As Long)
Dim udtHdr As WAVEHDR
Select Case uMsg
Case MM_WIM_OPEN
Case MM_WIM_DATA
If IsBadReadPtr(ByVal dwParam1, Len(udtHdr)) = 0 Then
CpyMem udtHdr, ByVal dwParam1, Len(udtHdr)
GotData udtBuffers(udtHdr.dwUser).intBuffer, udtHdr.dwBytesRecorded
' place the buffer in the waveIn queue again
waveInAddBuffer hWaveIn, udtBuffers(udtHdr.dwUser), Len(udtHdr)
Else
End If
Case MM_WIM_CLOSE
End Select
End Sub
Maybe there is something within the sub causing the problem?
-
Re: [RESOLVED] App Crashes Only If Shelled
Quote:
Originally Posted by
jmsrickland
Looks like that sort of kills your theory about all that COM and TypeLib stuff.
Maybe. Maybe not. It's a well known issue though and I've seen it myself with API callbacks that run on their own threads time after time.
This isn't just about RtlMoveMemory() calls, it is every API call used during the callback that you defined using Declare statements that will fail. All of them must be defined through typelibs. And you can't do anything else during that time such as touch any COM object's methods, properties, or events (including any of your program's Forms).
You seem to be making 3 different API calls plus whatever "GotData" is. None of these can be called through a Declare or through COM.
But if you've covered those bases and it still fails then it must be something else.
-
Re: [RESOLVED] App Crashes Only If Shelled
Well, here is the strange thing.
Yesterday I put your CopyMemory in the waveInProc and it was the only thing in that proc. Case _OPEN and Case _CLOSE had no code at all. Case _DATA only had your CopyMemory in it. Every time I ran the app it crashed running as an EXE. I would go back and comment out the CopyMemory, recompile, run as EXE. Didn't crash (App didn't do anything either since no code in proc but I'm only testing for crash). Again I uncommented CopyMemory, recompile, and run. Crashed every time. I went back to code and commented out CopyMemory and put back the other statement from the TypeLib I made from copying a MS example. Ran app. Crashed as I thought it would. I did this so many times with same results I just got tired of messing with it. Eventually I just went back to the way I had it before where Case _DATA only did a Timer1.Enabled = True (the old proc code is now in a Timer() event). This would work every time so I just left it that way.
Now, this morning I decided to go back and do some more testing. I went into the waveInProc and commented out the Timer statement. I put your CopyMemory back in. Compiled and ran the app as an EXE. IT DIDN'T CRASH! What! I said to me. What's going on here? Did I see that right? Am I just imagining this? I went back to code, put back the statement of the other TypeLib and now I have both statements; yours and the one I made. Compiled app, ran it and again it didn't crash. I'm really messed up in my head now. Why yesterday it crashed every time and today it doesn't. OK, so now I start putting back each of the other statements I had in the original code one statement at a time. (I don't need the If statement so I didn't put it back). I compile and run as EXE and each time it worked. I go back put in the next statement. Same thing again, it works. OK, now I put in the final statement; the waveInAddBuffer, compile app and run as EXE. Sure enough it crashed. I go back and comment out this statement leaving all others uncommented, compile and run. Again it worked.
I have no explanation as to what was going on yesterday when it crashed every time and now today it doesn't. I'm not making this up and I am stating exactly what took place yesterday.
OK, I withdraw my statement I made in post #26. It now appears that using the typelib stuff does work.
Now the only statement that causes app to crash is the waveInAddBuffer. To make the app run I removed this statement from proc and put it back in the Timer() event; all other statements are in the proc.
Now my only problem is how to make a TypeLib that emulates the waveInAddBuffer. Any ideas?
P.S. I feel bad about making that statement. Sorry.
-
Re: [RESOLVED] App Crashes Only If Shelled
Quick question for you JMS, did you try to restart your PC at all yesterday when it kept crashing? It might of left some API code unclosed, and then you kept trying to use it while it was not closed, making it crash.
Not sure though, as I did not test you project whatsoever. Something with those Proc events if not closes properly it will cause crash every time you try and run the same project (code) again. Like when you SubClass (or hook) a form, if you do not close the hook properly it makes you project crash every time you try to subclass/hook it again.
Don't have to take my word for this, but sometimes a quick restart will get rid of that problem.
Seems like you went to bed (shut down PC) and tried again today and it worked.
-
Re: [RESOLVED] App Crashes Only If Shelled
Quote:
Originally Posted by
jmsrickland
Now my only problem is how to make a TypeLib that emulates the waveInAddBuffer. Any ideas?
Can you post the Declare you are using for it?
You can also have one typelib covering multiple DLLs as far as I know by including multiple "modules" in the ODL/IDL source. Most people won't do that though unless they need a typelib for just a specific class of programs, so they prefer one per DLL to make them more reusable.
Quote:
Originally Posted by
jmsrickland
P.S. I feel bad about making that statement. Sorry.
Well I'm not too concerned about anything but getting to a working solution.
-
Re: [RESOLVED] App Crashes Only If Shelled
@Max187Boucher
Yesterday I did not turn off my PC until I stopped messing around with that problem. Last night I turned off PC then next morning (this morning) I went back to do more testing and that's when things started to work.
Here is the complete BAS Module
Code:
Option Explicit
'
' Note: You must give reference to RemCam CopyMemory Type Library for the CopyMemory in waveInProc to work
'
Public Declare Function waveInAddBuffer Lib "winmm" (ByVal hwi As Long, pwh As Any, ByVal cbwh As Long) As Long
Public Declare Function waveInClose Lib "winmm" (ByVal hwi As Long) As Long
Public Declare Function waveInGetDevCaps Lib "winmm" Alias "waveInGetDevCapsA" _
(ByVal hwi As Long, _
pwic As Any, _
ByVal cbwic As Long) As Long
Public Declare Function waveInGetErrorText Lib "winmm" Alias "waveInGetErrorTextA" _
(ByVal mmrError As Long, _
ByVal pszText As String, _
ByVal cchText As Long) As Long
Public Declare Function waveInGetID Lib "winmm" (ByVal hwi As Long, puDeviceID As Long) As Long
Public Declare Function waveInGetNumDevs Lib "winmm" () As Long
Public Declare Function waveInGetPosition Lib "winmm" (ByVal hwi As Long, pmmt As Any, ByVal cbmmt As Long) As Long
Public Declare Function waveInMessage Lib "winmm" _
(ByVal deviceid As Long, _
ByVal uMsg As Long, _
ByVal dwParam1 As Long, _
ByVal dwParam2 As Long) As Long
Public Declare Function waveInOpen Lib "winmm" _
(phwi As Long, _
ByVal uDeviceID As Long, _
pwfx As Any, _
ByVal dwCallback As Long, _
ByVal dwCallbackInstance As Long, _
ByVal fdwOpen As Long) As Long
Public Declare Function waveInPrepareHeader Lib "winmm" (ByVal hwi As Long, pwh As Any, ByVal cbwh As Long) As Long
Public Declare Function waveInUnprepareHeader Lib "winmm.dll" _
(ByVal hWaveIn As Long, _
lpWaveInHdr As Any, _
ByVal uSize As Long) As Long
Public Declare Function waveInReset Lib "winmm" (ByVal hwi As Long) As Long
Public Declare Function waveInStart Lib "winmm" (ByVal hwi As Long) As Long
Public Declare Function waveInStop Lib "winmm" (ByVal hwi As Long) As Long
Private Const WAVE_FORMAT_PCM As Long = 1&
Public Const MM_WIM_OPEN As Long = &H3BE
Public Const MM_WIM_CLOSE As Long = &H3BF
Public Const MM_WIM_DATA As Long = &H3C0
Private Const MMSYSERR_NOERROR As Long = 0&
Public Type WAVEHDR
lpData As Long
dwBufferLength As Long
dwBytesRecorded As Long
dwUser As Long
dwFlags As Long 'NOT USED
dwLoops As Long 'NOT USED
lpNext As Long 'NOT USED
reserved As Long 'NOT USED
End Type
Private Type WaveInBuffer
hdr As WAVEHDR
intBuffer() As Integer
pMem As Long
End Type
Public udtBuffers() As WaveInBuffer
'NOT USED
Private Type WAVEFORMAT
wFormatTag As Integer
nChannels As Integer
nSamplesPerSec As Long
nAvgBytesPerSec As Long
nBlockAlign As Integer
End Type
Private Type WAVEFORMATEX
wFormatTag As Integer
nChannels As Integer
nSamplesPerSec As Long
nAvgBytesPerSec As Long
nBlockAlign As Integer
wBitsPerSample As Integer
cbSize As Integer
End Type
Private lngPrevProc As Long
Private lngHwnd As Long
Public hWaveIn As Long
Public intSamples() As Integer
Public lngMSEncoded As Long
Public lngBytesPerSec As Long
Private lngCurDev As Long
Private lngBufSize As Long
Private lngBufCnt As Long
Public waveInProc_dwParam1 As Long
Public RecordingStopped As Boolean
Public Function IsRecording() As Boolean
IsRecording = hWaveIn <> 0
End Function
Public Function StopRecord() As Boolean
Dim i As Long
Dim res As Long
RecordingStopped = True
If hWaveIn <> 0 Then
' make sure all buffers are returned to us
res = waveInStop(hWaveIn)
If res <> 0 Then Debug.Print "waveInStop: " & res
res = waveInReset(hWaveIn)
If res <> 0 Then Debug.Print "waveInReset: " & res
' destroy all buffers
For i = 0 To lngBufCnt - 1
res = waveInUnprepareHeader(hWaveIn, udtBuffers(i).hdr, Len(udtBuffers(i).hdr))
If res <> 0 Then Debug.Print "waveInUnprep " & i & ": " & res
Next
res = waveInClose(hWaveIn)
If res <> 0 Then Debug.Print "waveInClose: " & res
hWaveIn = 0
End If
StopRecord = True
End Function
Public Function StartRecord(ByVal samplerate As Long, ByVal Channels As Integer) As Boolean
Dim udtWFX As WAVEFORMATEX
Dim res As Long
Dim i As Long
Dim j As Long
ReDim udtBuffers(lngBufCnt - 1) As WaveInBuffer
With udtWFX
.wFormatTag = WAVE_FORMAT_PCM
.nSamplesPerSec = samplerate
.nChannels = Channels
.wBitsPerSample = 16
.nBlockAlign = Channels * (.wBitsPerSample / 8)
.nAvgBytesPerSec = .nBlockAlign * .nSamplesPerSec
.cbSize = 0
End With
Const CALLBACK_FUNCTION = &H30000
res = waveInOpen(hWaveIn, lngCurDev, udtWFX, AddressOf waveInProc, 0, CALLBACK_FUNCTION)
If res <> MMSYSERR_NOERROR Then
Exit Function
End If
' prepare headers/buffers
For i = 0 To lngBufCnt - 1
With udtBuffers(i)
ReDim .intBuffer(lngBufSize / 2 - 1) As Integer
.pMem = VarPtr(.intBuffer(0))
.hdr.dwBufferLength = lngBufSize
.hdr.lpData = .pMem
.hdr.dwUser = i
res = waveInPrepareHeader(hWaveIn, .hdr, Len(.hdr))
If res <> MMSYSERR_NOERROR Then
' on error unprepare all prepared headers
For j = (i - 1) To 0 Step -1
waveInUnprepareHeader hWaveIn, .hdr, Len(.hdr)
Next
waveInClose hWaveIn
hWaveIn = 0
Exit Function
End If
End With
Next
' Add buffers to the recording device
For i = 0 To lngBufCnt - 1
res = waveInAddBuffer(hWaveIn, udtBuffers(i).hdr, Len(udtBuffers(i).hdr))
If res <> MMSYSERR_NOERROR Then
MsgBox "Error in AddBuffer loop, Error = " & res
Exit Function
End If
Next
res = waveInStart(hWaveIn)
If res <> MMSYSERR_NOERROR Then
For i = 0 To lngBufCnt - 1
waveInUnprepareHeader hWaveIn, udtBuffers(i).hdr, Len(udtBuffers(i).hdr)
Next
waveInClose hWaveIn
hWaveIn = 0
Exit Function
End If
StartRecord = True
End Function
Public Sub WaveInRecorderInitialize()
lngBufCnt = 5
lngBufSize = 10& * 1024&
lngCurDev = -1
End Sub
Public Sub WaveInRecorderTerminate()
If IsRecording Then
StopRecord
End If
End Sub
Private Sub waveInProc(ByVal hwi As Long, _
ByVal uMsg As Long, _
ByVal dwInstance As Long, _
ByVal dwParam1 As Long, _
ByVal dwParam2 As Long)
Dim udtHdr As WAVEHDR
Select Case uMsg
Case MM_WIM_DATA
'
' Note. This is not the regular CopyMemory MS API. It is a type lib function made by Dilettante
'
' http://www.vbforums.com/showthread.php?724691-RESOLVED-App-Crashes-Only-If-Shelled
'
CopyMemory udtHdr, ByVal dwParam1, ByVal Len(udtHdr)
intSamples = udtBuffers(udtHdr.dwUser).intBuffer
lngMSEncoded = lngMSEncoded + ((udtHdr.dwBytesRecorded / lngBytesPerSec) * 1000)
' Start timer. The waveInAddBuffer API is in the Timer
' event because App will crash if I put it here
' waveInAddBuffer ByVal hWaveIn, udtBuffers(udtHdr.dwUser), ByVal Len(udtHdr)
Form1.Timer1.Enabled = True
End Select
End Sub
Form Code
Code:
Option Explicit
Private Declare Function SendMessageByString Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As String) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Const WM_SETTEXT = &HC
Private Const BM_CLICK = &HF5
Private Const WM_SETFOCUS = &H7
Private Const FFT_SAMPLES As Long = 1024
Private Const SAMPLE_RATE As Long = 8000
Private App1TextboxHWND As Long
Public Sub cmdStart_Click()
lngBytesPerSec = SAMPLE_RATE * 2
' Once called then IsRecording = True and the tmrVis timer event is entered at the If statement
If Not StartRecord(SAMPLE_RATE, 1) Then
MsgBox "Could not start recording!", vbExclamation
End If
cmdStart.Enabled = False
cmdStop.Enabled = True
End Sub
Private Sub cmdStop_Click()
If Not StopRecord Then
MsgBox "Could not stop recording!", vbExclamation
End If
lngMSEncoded = 0
lngBytesPerSec = 0
cmdStart.Enabled = True
cmdStop.Enabled = False
End Sub
Private Sub Form_Load()
Dim s As String
cmdStop.Enabled = False
WaveInRecorderInitialize
ReDim intSamples(FFT_SAMPLES - 1) As Integer
s = Command$()
If s <> "" Then
Dim A() As String
A = Split(s, ":")
App1_Picture1HWND = Val(A(0))
App1TextboxHWND = Val(A(1))
SendMessageByString App1TextboxHWND, WM_SETTEXT, 0, cmdStart.hwnd & ":" & cmdStop.hwnd & ":" & txtMessages.hwnd
End If
End Sub
Private Sub Form_Unload(Cancel As Integer)
If IsRecording Then
cmdStop_Click
WaveInRecorderTerminate
End If
End Sub
Private Sub txtMessages_Change()
Select Case txtMessages.Text
Case "Unload"
Unload Me
End Select
End Sub
Private Sub tmrVis_Timer()
If IsRecording Then
DrawAmplitudes intSamples, picAmpl
sbar.Panels(1).Text = "Length: " & FmtTime(lngMSEncoded)
Else
sbar.Panels(1).Text = "Length: 0:00"
End If
End Sub
Private Function FmtTime(ByVal lngMS As Long) As String
' USED BY tmrVis_Timer
Dim lngMin As Long
Dim lngSec As Long
lngSec = lngMS / 1000
lngMin = lngSec \ 60
lngSec = lngSec Mod 60
FmtTime = lngMin & ":" & Format(lngSec, "00")
End Function
Private Sub Timer1_Timer()
Timer1.Enabled = False
Dim udtHdr As WAVEHDR
' place the buffer in the waveIn queue again
waveInAddBuffer ByVal hWaveIn, udtBuffers(udtHdr.dwUser), ByVal Len(udtHdr)
End Sub
-
Re: [RESOLVED] App Crashes Only If Shelled
Using Dilettante's example as posted in #9 I modified it to make an ODL file for the waveInAddBuffer.
Code:
[
uuid(b030018c-4fe9-4a1b-bd38-0a7a4537b80b),
helpstring("WaveAddBuffer Type Library")
]
library WaveAddBuffer
{
[
helpstring("winmm waveInAddBuffer Call"),
dllname("winmm")
]
module WaveAddBufferMod
{
[
helpstring("Add buffer to wave in device."),
entry("waveInAddBuffer")
]
long _stdcall waveInAddBuffer
(
[in] long hwi,
[in] const void __unaligned *pwh,
[in] long cbwh
);
};
};
I used midl.exe /mktyplib203 MyWaveInAddBuffer.odl >log.txt to compile it to a .tbl file.
Now, in the source code for my project I commented out the Public Declare waveInAddBuffer statement. I added a reference to the new type lib I just made. In the waveInProc I re-added the waveInAddBuffer statement and removed the Timer1 statement.
Code:
Private Sub waveInProc(ByVal hwi As Long, _
ByVal uMsg As Long, _
ByVal dwInstance As Long, _
ByVal dwParam1 As Long, _
ByVal dwParam2 As Long)
Dim udtHdr As WAVEHDR
Select Case uMsg
Case MM_WIM_DATA
'
' Note. This is not the regular CopyMemory MS API. It is a type lib function made by Dilettante
'
' http://www.vbforums.com/showthread.php?724691-RESOLVED-App-Crashes-Only-If-Shelled
'
CopyMemory udtHdr, ByVal dwParam1, ByVal Len(udtHdr)
intSamples = udtBuffers(udtHdr.dwUser).intBuffer
lngMSEncoded = lngMSEncoded + ((udtHdr.dwBytesRecorded / lngBytesPerSec) * 1000)
'
' Note. This is not the regular waveInAddBuffer MS API. It is a type lib function made by JMS
'
waveInAddBuffer hWaveIn, udtBuffers(udtHdr.dwUser), Len(udtHdr)
End Select
End Sub
I compiled the project to an EXE and ran it. It worked but......
.....although the app ran without any problems when I went to push on the Stop button the app froze up. I couldn't do anything except click on the X button. Then the App crashed.
I noticed that the original API Arg1 and Arg3 are declared as ByVal but my version did not compile as ByVal for these arguments (I didn't know how to do that) because when I looked at the Object Browser for my type lib it read as this:
Function waveInAddBuffer(hwi As Long, pwh As Any, cbwh As Long) As Long
and the original is:
Function waveInAddBuffer Lib "winmm" (ByVal hwi As Long, pwh As Any, ByVal cbwh As Long) As Long
Question: Could this be what is causing the final crash problem?
How do I make arguments ByVal in the ODL file?
-
Re: [RESOLVED] App Crashes Only If Shelled
As far as I know the ByRef/ByVal concept as such is a VB thing. While there is clearly some way to "decorate" arguments in ODL/IDL to report this (since we see it for COM typelibs) the VB6 compiler doesn't need or expect it for non-COM DLLs.
So the Object Browser won't give you the "full story" in VB terms.
Basically eveything is "ByVal" to a C DLL. Whether the actual datum passed is the pointer to a variable or the value "inside" the variable is determined by the calling code's compiler. VB6 looks for the "*" to mean "pass the pointer" and no "*" to mean "pass the value."
Code:
[in] long hwi,
[in] const void __unaligned *pwh,
[in] long cbwh
And to repeat, this doesn't show up as such in the Object Browser.
It would be nice to see some accurate and comprehensive treatment of the subject of typelibs for DLL calling from VB6. I've never seen one though.
-
Re: [RESOLVED] App Crashes Only If Shelled
Well I narrowed it down to the waveInReset API causing the hangup. I read on the net that this appears to be a common problem however it didn't cause any problems before I put the waveInAddBuffer in the Type Lib. Don't know why or what putting waveInAddBuffer in the Type Lib would have any effect on whether waveInReset functions correctly or not but I commented it out and it didn't appear to have any affect on the app as a whole. So, I would rather have waveInAddBuffer in the type lib as it is now than to worry about waveInReset. I am going to keep it commented out because now the entire app runs good and no further problems have arisen.
I combined your ODL code and mine into a single Type Library rather than using two Type Libs as before.
So, I guess this can finally come to a close.
Thanks for all your help, highly appreciated.
-
Re: [RESOLVED] App Crashes Only If Shelled
Problem solved.
All I have to do is to put a variable switch in the waveInProc to disallow entry before the waveInReset is called.