Oki here comes a little example on how to create "Mixed Mode" assemblies:
Preliminary:
To write a "Mixed Mode" assembly you need to create a Visual C++ assembly (a dynamic link libraray, dll)!
The trick is to write managed C++ code (.Net code) and unmanged C++ code (native).
The managed code can then be accessed by any other .Net language, like C# or VB.Net.
a) You create a new Visual C++ project as a new class library (dll)
- make sure to set the "Common Language Runtime Support" (/clr) switch in the general configuration properties
- so this will be your mixed mode assembly
b) add the BASS header file(s) to your new Visual C++ project (just drag & drop e.g. the bass.h file to the 'Header Files' section of your new project'
c) Now you create two .cpp files: e.g. "BassManagedWrapper.cpp" and "BassUnmanaged.cpp"
- the content of these files will be shown later (see below)
d) compile everything and you are done
- now you can use this new library as a normal assembly within your standard .Net application
- just add it as a new reference
Now to the content of the two files mentioned:
1) BassUnmanaged.cpp
#pragma once
#include "Stdafx.h"
#pragma unmanaged
#include <math.h>
#include <malloc.h>
#include <stdio.h>
#include <process.h>
#include "bass.h"
#using <mscorlib.dll>
#ifndef UNMANAGED
#define UNMANAGED
class EasyRecord
{
private:
HRECORD _recordHandle;
HSTREAM _streamCopy;
/* The actual recording callback */
static BOOL CALLBACK RecordingCallback(HRECORD handle, const void *buffer, DWORD length, void *user)
{
EasyRecord* easy = (EasyRecord*)user;
if (easy->_streamCopy != 0 && length > 0)
BASS_StreamPutData(easy->_streamCopy, buffer, length);
// always continue recording
return TRUE;
}
public:
/* Constructor */
EasyRecord(void)
{
this->_recordHandle = 0;
this->_streamCopy = 0;
}
/* Gets the recording handle */
DWORD GetRecordHandle(void)
{
return this->_recordHandle;
}
/* Gets the stream copy handle */
DWORD GetStreamCopy(void)
{
return this->_streamCopy;
}
/* Sets the stream copy handle */
void GetStreamCopy(HSTREAM streamCopy)
{
this->_streamCopy = streamCopy;
}
/* Clears the recording buffer */
void ClearRecordingBuffer(void)
{
BASS_ChannelGetData(this->_recordHandle, NULL, BASS_ChannelGetData(this->_recordHandle, NULL, BASS_DATA_AVAILABLE));
}
/* Creates a recording clone */
void CreateClone(DWORD flags)
{
BASS_CHANNELINFO info;
BASS_ChannelGetInfo(this->_recordHandle, &info);
flags |= info.flags;
flags &= ~BASS_STREAM_AUTOFREE;
this->_streamCopy = BASS_StreamCreate(info.freq, info.chans, flags, STREAMPROC_PUSH, 0);
if (this->_streamCopy != 0)
{
BASS_ChannelPause(this->_recordHandle);
this->ClearRecordingBuffer();
BASS_ChannelPlay(this->_recordHandle, TRUE);
}
}
/* Removes a recording clone */
void RemoveClone()
{
if (this->_streamCopy != 0)
{
BASS_StreamFree(this->_streamCopy);
this->_streamCopy = 0;
}
}
void Start(DWORD freq, DWORD chans, DWORD flags, DWORD period, BOOL decoding)
{
if (period > 0)
flags = MAKELONG(flags, period);
if (decoding)
this->_recordHandle = BASS_RecordStart(freq, chans, flags, NULL, this);
else
this->_recordHandle = BASS_RecordStart(freq, chans, flags, &EasyRecord::RecordingCallback, this);
}
void Stop(void)
{
this->RemoveClone();
BASS_ChannelStop(this->_recordHandle);
BASS_StreamFree(this->_recordHandle);
this->_recordHandle = 0;
}
};
As you can see this simple unmanaged C++ class has a static callback (to access the related instance members the 'user' param of the callback is used.
The callback itself will be used, if you call the 'Start' method of the class.
Also note the "#pragma unmanaged" line at the top of the file. This switch tells the compiler, that you are now coding in native, unmanaged code!
As this class can not directly be used by any .Net language (as it is native code), we need to write a .Net wrapper class.
This is what we do in the next file...
2) BassManagedWrapper.cpp
#include "Stdafx.h"
#include "BassUnmanaged.cpp"
#pragma managed
using namespace System;
using namespace System::Runtime::InteropServices;
namespace MyBASS
{
public ref class EasyRecording : public IDisposable
{
private:
/* pInner is used to invoke the exposed methods in the unmanaged class. */
EasyRecord* pInner;
public:
// Constructor
EasyRecording()
{
/* define the pInner object */
pInner = new EasyRecord();
}
//destructor
~EasyRecording()
{
//call finalizer (don't wait for GC)
this->!EasyRecording();
}
//finalizer
!EasyRecording()
{
delete pInner;
pInner = NULL;
}
// Gets the recording handle
property int RecordHandle
{
int get(void) { return pInner->GetRecordHandle(); }
}
// Gets a stream copy handle
property int StreamCopy
{
int get(void) { return pInner->GetStreamCopy(); }
}
// Clears the recording buffer
void ClearRecordingBuffer(void)
{
pInner->ClearRecordingBuffer();
}
// Creates a stream clone
void CreateClone(int flags)
{
pInner->CreateClone(flags);
}
// Remove a stream clone
void RemoveClone(void)
{
pInner->RemoveClone();
}
// Starts the Recording
void Start(int freq, int chans, int flags, int period, bool decoding)
{
pInner->Start(freq, chans, flags, period, decoding);
}
// Stops the recording
void Stop(void)
{
pInner->Stop();
}
};
}
The "#pragma managed" line at the top of the file tells the compiler, that we now code in managed C++ (.Net code)!
We use a member called "pInner" which is of the type of our unmanaged class to access the native unmanaged code.
As you can see, we create an instance of it in the constructor and make sure, that we delete it in the finalizer.
This is nessesary, because any instance of an unmanaged class will not be seen by the Gargabe Collector and thus we have to make sure to free any resources here.
For the rest all is easy. We simply invoke the "pInner" members in our wrapper methods.
As a result, we can assess the managed class "EasyRecording" from any .Net language.
So if you add the finally build libraray (as shown above) to your .Net application as a new reference you can use it like this:
private MyBASS.EasyRecording _recordHandler = null;
private int _recordStream = 0;
...
Bass.BASS_RecordSetDevice(0);
for (int i = 0; Bass.BASS_RecordSetInput(i, BASSInput.BASS_INPUT_OFF, -1f); i++); // 1st disable all inputs, then...
Bass.BASS_RecordSetInput(0, BASSInput.BASS_INPUT_ON, -1f); // enable the selected
_recordHandler = new MyBASS.EasyRecording();
_recordHandler.Start(44100, 2, BASSFlag.BASS_SAMPLE_FLOAT, 100, false);
_recordStream = _recordHandler.RecordHandle;
...
// from here on you use the '_recordStream' like normal
...
// to stop recording just call
_recordHandler.Stop();
_recordHandler.Dispose();
_recordHandler = null;
...