Author Topic: [Solved] VB6 Ide crash using BASS_StreamUserCreate  (Read 366 times)

jpf

  • Posts: 53
I started writing a module to move/rename a file while it's being played by Bass. I thought of using BASS_StreamUserCreate. If there are better workarounds I'd like to learn them.

This is the module so far (no moving/renaming so far, just playing):
Code: [Select]
Option Explicit

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, Source As Any, ByVal Length As Long)

' User file stream callback functions (BASS_FILEPROCS)
Public Sub mFileClose(ByVal User As Long)
    Close User
End Sub

Public Function mFileLen(ByVal User As Long) As Currency ' ???
    mFileLen = LOF(User) / 10000#
End Function

Public Function mFileRead(ByVal Buffer As Long, ByVal Length As Long, ByVal User As Long) As Long
    Dim mBuff() As Byte
   
    If Length > LOF(User) - Loc(User) Then
        Length = LOF(User) - Loc(User)
    End If
   
    ReDim mBuff(Length - 1)
    mBuff = InputB(Length, User)
   
    CopyMemory Buffer, mBuff(0), Length
    mFileRead = Length
End Function

Public Function mFileSeek(ByVal Offset As Long, ByVal OffsetHigh As Long, ByVal User As Long) As Long
    On Error Resume Next
    Seek User, Offset
    If Err = 0 Then mFileSeek = BASSTRUE
End Function

Private Function GetAddr(ByVal Addr As Long) As Long
    'packing AddressOf
    GetAddr = Addr
End Function

Public Function BASS_StreamUserCreate(ByVal FileName As String, ByVal Flags As Long) As Long
    Dim nFile As Long
    Dim BASS_FileProcs(3) As Long
   
    On Error Resume Next
    nFile = FreeFile
    If Err <> 0 Then Exit Function
    Open FileName For Binary Access Read Shared As nFile
    If Err <> 0 Then Exit Function

    'typedef struct {
    '    FILECLOSEPROC *close;
    '    FILELENPROC *length;
    '    FILEREADPROC *read;
    '    FILESEEKPROC *seek;
    '} BASS_FILEPROCS;
    BASS_FileProcs(0) = GetAddr(AddressOf mFileClose)
    BASS_FileProcs(1) = GetAddr(AddressOf mFileLen)
    BASS_FileProcs(2) = GetAddr(AddressOf mFileRead)
    BASS_FileProcs(3) = GetAddr(AddressOf mFileSeek)

    'HSTREAM BASS_StreamCreateFileUser(
    '    DWORD system,
    '    DWORD flags,
    '    BASS_FILEPROCS *procs,
    '    void *user
    ');
    BASS_StreamUserCreate = BASS_StreamCreateFileUser(STREAMFILE_BUFFER, Flags, VarPtr(BASS_FileProcs(0)), nFile)

End Function

For testing I used a slightly modified vb\BASStest example from the bass 2.4.13 package.

The modification is to use BASS_StreamUserCreate from my module instead of BASS_StreamCreateFile in the sub cmdStreamAdd_Click():
Code: [Select]
Private Sub cmdStreamAdd_Click()
   ...
    'StreamHandle = BASS_StreamCreateFile(BASSFALSE, StrPtr(DLG.FileName), 0, 0, 0)
    StreamHandle = BASS_StreamUserCreate(DLG.FileName, 0)
   ...
End Sub

The code seems to work well in the Ide. It does until I close the form. Then the Ide crashes with "Access violation" error on VB6.exe module.

This doesn't happen if I close the window without having Add'd any files to the playlist.

If I compile the application and run it, it works as expected, and on closing the form it exits without error.

Any hints to avoid the Ide crash?
« Last Edit: 19 Jul '18 - 18:07 by jpf »

Guest

  • Guest
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #1 on: 3 Feb '18 - 20:28 »
if your use opendialog before?
if so then i think the Trouble come from CoInitialize, CoUninitialize.

open the file without opendialog control..
test it with OpenDialog over Win32API instead.

jpf

  • Posts: 53
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #2 on: 4 Feb '18 - 18:17 »
if your use opendialog before?
if so then i think the Trouble come from CoInitialize, CoUninitialize.

open the file without opendialog control..
test it with OpenDialog over Win32API instead.

Thanks for your hint.

I guess you mean test it using "GetOpenFileNameA" from "comdlg32.dll" instead of the comdlg32.ocx control.

Well, I followed your advice and did it. To make sure that the ocx control won't be loaded anyway I deleted the control from the form and commented out the lines referencing it.

Unfortunately the crash still happens just like before.

I went a step further and removed the references to comdlg32.dll as well, and hardcoded a filename to make up for the missing file dialogue:
Code: [Select]
Private Sub cmdStreamAdd_Click()
    ...
    StreamHandle = BASS_StreamUserCreate("E:\Audio\A revisar\Mp3\Chorus - Your Zowie Face.mp3", 0)
    ...
End Sub
No change; the crash still happens just like before.

However I'd like to learn what would be the mechanism by which CoInitialize and CoUninitialize together with BASS_StreamUserCreate & related stuff would lead to the crash. Will you please give me a detailed description of the process? Mind that I'm mostly ignorant of the internals of the VB6 Ide (and the VB6 runtime as well).
Maybe this would help me grasp what's the problem here.

Thanks !

Guest

  • Guest
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #3 on: 4 Feb '18 - 19:07 »
deaktivate Bass_Free for testing before Close your File.

Guest

  • Guest
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #4 on: 4 Feb '18 - 19:11 »
StrPtr(DLG.FileName) ?? missing on  BASS_StreamUserCreate
or is not needed.

Guest

  • Guest
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #5 on: 4 Feb '18 - 19:19 »
and not found BASS_StreamUserCreate but BASS_StreamCreateFileUser

Ian @ un4seen

  • Administrator
  • Posts: 21017
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #6 on: 5 Feb '18 - 13:43 »
VB6 doesn't really support multi-threading, so using VB6 functions in a callback function can cause problems. Can you try using the Win32 file functions instead, eg. CreateFile and ReadFile?

jpf

  • Posts: 53
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #7 on: 5 Feb '18 - 15:21 »
VB6 doesn't really support multi-threading, so using VB6 functions in a callback function can cause problems. Can you try using the Win32 file functions instead, eg. CreateFile and ReadFile?

Thanks, Ian, I'll try this.

and not found BASS_StreamUserCreate but BASS_StreamCreateFileUser
It's defined in my module.

StrPtr(DLG.FileName) ?? missing on  BASS_StreamUserCreate
or is not needed.
My function accepts "ByVal FileName As String" as parameter.

deaktivate Bass_Free for testing before Close your File.
But Ian's example in the help file said to close it inside the FILECLOSEPROC (my module is just a translation of Ian's example). I'll try Ian's suggestion first, because yours will make my module useless: if I free bass at the end of each playing file all the other files playing will be killed as well.

Anyway I'll keep your basic idea in mind. Maybe running the "Close User" statement asyncronously outside the FILECLOSEPROC callback, which probably runs in a different thread as the Open statement, can make a difference. I should then do a PostMessage to the form, subclass it, and close the files inside the WinProc callback, which supposedly is in the same thread as the Open statement. However this still leaves the LOC(), EOF() and Seek() file functions inside callback procedures, so it's not completelly unsyncing the file access interface. Ian's suggestion is way cleaner, so I'll try it first.

jpf

  • Posts: 53
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #8 on: 18 Jul '18 - 21:43 »
Update:
I tried using Win32 file access functions instead of those native of VB6 and I still got crashes.

So I decided to move all the multithreaded stuff inside a VC++ Win32 dll, and call it from my VB6 app as needed. This got rid of the crashes.

So I went ahead and coded the stuff to move/rename a file while it's being played by Bass. Basically I lock (BASS_ChannelLock) the channels playing the file, close the file (fclose), rename it, open it, seek to the next read position, and unlock the channels.

I did very little testing so far. It seems to work but I'm stuck with one problem: the read position after unlocking the channels seems to be a bit behind of what it's really needed, so there's some replay of the last seconds.

I'm not a C user, so I'm full of doubts about the coding, let alone debugging it! This is the part where the file position seek takes place:
Code: [Select]
QWORD offset=BASS_StreamGetFilePosition(hCh, BASS_FILEPOS_CURRENT);
if(offset!=-1){ //don't seek invalid pos
errno=0;
fseek(file, (long)offset, SEEK_SET);
}

And this is the full code of the dll, in case the problem is somewhere else. These functions aren't yet tested at all:
jpBASS_StreamSwitchFile
jpBASS_StreamsRenameFile
Code: [Select]
// jpBassMoveFile.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "bass.h"
#include <stdio.h>
#include <stdlib.h> //errno
#include <sys\stat.h>
//#include "internal.h" //_fseeki64 ? --found at "C:\Archivos de programa\Microsoft Visual Studio\VC98\CRT\SRC"

void CALLBACK MyFileCloseProc(void *user)
{
if(*(FILE **)user){ //don't do it in case the file was switched and fopen failed
fclose(*(FILE **)user); // close the file
*(FILE **)user=NULL; //signal 'file free'd'
}
}

QWORD CALLBACK MyFileLenProc(void *user)
{
if(*(FILE **)user){
struct stat s;
fstat(fileno(*(FILE **)user), &s);
return s.st_size; // return the file length
}
else
return 0;
}

DWORD CALLBACK MyFileReadProc(void *buffer, DWORD length, void *user)
{
if(*(FILE **)user) //don't do it in case the file was switched and fopen failed
return fread(buffer, 1, length, *(FILE **)user); // read from file
else
return -1;
}

BOOL CALLBACK MyFileSeekProc(QWORD offset, void *user)
{
if(*(FILE **)user) //don't do it in case the file was switched and fopen failed
    return !fseek(*(FILE **)user, (long)offset, SEEK_SET); // seek to offset .._fseeki64 ?
else
return FALSE;
}

void CALLBACK MyFreeSyncProc(HSYNC handle, DWORD channel, DWORD data, void *user) //free resources
{
delete user;
}

DWORD __stdcall jpBASS_StreamCreateFileSwitchable(char *FileName, DWORD flags, DWORD pppFile)
//FILE *file=fopen("a_file.mp3", "rb"); // open the file
//stream=BASS_StreamCreateFileUser(STREAMFILE_NOBUFFER, 0, &fileprocs, file); // create the stream
//file is passed ByVal so it can't be modified
//so I'll allocate a FILE * in the heap, and dereference it in each fileproc
//and when needed I'll change its contents to the new FILE *
//I'll also pass its address to the caller for storing statically
//so the caller can pass it to jpBASS_StreamSwitchFile to change FILE *
//!!! free hCh before the caller's pppFile !!!
//Declare Function jpBASS_StreamCreateFileSwitchable Lib "jpBassMoveFile.dll" (ByVal FileName As String, ByVal Flags As Long, pppFile As Long) As Long
{
HSTREAM hCh=0;
FILE **pFile=NULL; //new returns a pointer
if(pFile=new FILE *){ //allocate a FILE *
*pFile=fopen(FileName, "rb"); // open the file
if(*pFile){
BASS_FILEPROCS fileprocs={MyFileCloseProc, MyFileLenProc, MyFileReadProc, MyFileSeekProc}; // callback table
hCh=BASS_StreamCreateFileUser(STREAMFILE_NOBUFFER, flags, &fileprocs, pFile); // create the stream
if(hCh){
*(FILE ***)pppFile=pFile; //pass it to the caller for storing statically
BASS_ChannelSetSync(hCh, BASS_SYNC_FREE, 0, MyFreeSyncProc, pFile); //delete pFile
}
else{
fclose(*pFile);
delete pFile;
}
}
else
delete pFile;
}
return (DWORD)hCh;
}


DWORD __stdcall jpBASS_StreamSwitchFile(HSTREAM hCh, char *FileName, DWORD pppFile)
//pppFile: [IN]: old file; [OUT]:new file
//Declare Function jpBASS_StreamSwitchFile Lib "jpBassMoveFile.dll" (ByVal hCh As Long, ByVal FileName As String, pppFile As Long) As Long
//return 0 if successful or bass error, errno on IO errors,
{
if(!**(FILE ***)pppFile) return 0; //no file used
errno=0;
FILE *file=fopen(FileName, "rb"); // open the file
if(file){
if(BASS_ChannelLock(hCh, TRUE)==TRUE){// fails if hCh was free'd during fopen
errno=0;
fclose(**(FILE ***)pppFile);//if it fails go on anyway and just report the error
**(FILE ***)pppFile=file;
QWORD offset=BASS_StreamGetFilePosition(hCh, BASS_FILEPOS_CURRENT);
if(offset!=-1){ //don't seek invalid pos
errno=0;
fseek(file, (long)offset, SEEK_SET);
}
BASS_ChannelLock(hCh, FALSE);
}
else{
fclose(file);
return 0; //BASS error
}
}
return (DWORD)errno; //no error or fopen error
}

DWORD __stdcall jpBASS_StreamRenameFile(HSTREAM hCh, char *OldFileName, char *FileName, DWORD pppFile)
//pppFile: [IN]:old file; [OUT]:new file
//Declare Function jpBASS_StreamRenameFile Lib "jpBassMoveFile.dll" (ByVal hCh As Long, ByVal OldFileName As String, ByVal FileName As String, pppFile As Long) As Long
//return 0 if successful or bass error, errno on IO errors,
{
if(**(FILE ***)pppFile){ //only if it's still using a file
BASS_ChannelLock(hCh, TRUE);
fclose(**(FILE ***)pppFile);//if it fails go on anyway
}

DWORD Ret=0;
errno=0;

if(rename(OldFileName, FileName)) { //rename returns 0 if it is successful
Ret=(DWORD)errno;
FileName=OldFileName; //backtrack if rename tails
}

if(**(FILE ***)pppFile){ //only if it's still using a file
errno=0;
FILE *file=fopen(FileName, "rb"); // open the file
if(file){
**(FILE ***)pppFile=file;
QWORD offset=BASS_StreamGetFilePosition(hCh, BASS_FILEPOS_CURRENT);
if(offset!=-1){ //don't seek invalid pos
errno=0;
fseek(file, (long)offset, SEEK_SET);
}
}
else
**(FILE ***)(pppFile)=NULL;

BASS_ChannelLock(hCh, FALSE);
}
return Ret; //no error or rename error
}

DWORD __stdcall jpBASS_StreamsRenameFile(HSTREAM hCh[], DWORD ChCount, char *OldFileName, char *FileName, DWORD pppFile[])
//pppFile: [IN]:old file; [OUT]:new file
//Declare Function jpBASS_StreamRenameFile Lib "jpBassMoveFile.dll" (hCh As Long, ByVal ChCount As Long, ByVal OldFileName As String, ByVal FileName As String, pppFile As Long) As Long
//return 0 if successful or bass error, errno on IO errors,
{
int idx=0;

for(idx=0; idx<(int)ChCount; idx++){
if(**(FILE ***)(pppFile[idx])){ //only if it's still using a file
BASS_ChannelLock(hCh[idx], TRUE);
fclose(**(FILE ***)(pppFile[idx]));//if it fails go on anyway
}
}
DWORD Ret=0;
errno=0;

if(rename(OldFileName, FileName)) {
Ret=(DWORD)errno;
FileName=OldFileName; //backtrack if rename tails
}
for(idx=0; idx<(int)ChCount; idx++){
if(**(FILE ***)(pppFile[idx])){ //only if it's still using a file
errno=0;
FILE *file=fopen(FileName, "rb"); // open the file
if(file){
**(FILE ***)(pppFile[idx])=file;
QWORD offset=BASS_StreamGetFilePosition(hCh[idx], BASS_FILEPOS_CURRENT);
if(offset!=-1){ //don't seek invalid pos
errno=0;
fseek(file, (long)offset, SEEK_SET);
}
}
}
else
**(FILE ***)(pppFile[idx])=NULL; //failed; signal 'no file'
}
for(idx=0; idx<(int)ChCount; idx++) BASS_ChannelLock(hCh[idx], FALSE);
return Ret; //no error or rename error
}

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
)
{
    return TRUE;
}


Thanks in advance for your help!

Ian @ un4seen

  • Administrator
  • Posts: 21017
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #9 on: 19 Jul '18 - 15:37 »
Whenever possible, BASS_StreamGetFilePosition will take account of buffering to give the decoder's current position in the file, ie. it will deduct any amount that has been buffered from the file ahead of that. So it may not match the file's next read position. For your purpose, I would suggest trying ftell (just before the fclose) instead.

jpf

  • Posts: 53
Re: VB6 Ide crash using BASS_StreamUserCreate
« Reply #10 on: 19 Jul '18 - 18:05 »
Thanks, Ian ! It worked !