|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionAsterisk is a complete open source PBX software, originally written by Marl Spencer of Digium, Inc., and tested and improved by open-source coders around the world. The Inter-Asterisk eXchange (IAX) protocol, used in Asterisk, enables VoIP connections between Asterisk servers and clients. To interact with the IAX protocol, you can use a C++ portable client library called IAXClient that enables anyone who wants to communicate to Asterisk servers calling exported functions. These functions handle the telephony operations including call handling, network protocols, and audio encoding/decoding. The library is designed to be built in multiple platforms using CygWin shell and, to use it in Win32, you must create a DLL file using the GCC compiler and some CygWin tools. In this article, I'll show:
IAXFunctions class
To use IAXClient functions in C#, we need to use the Platform Invoke (P/Invoke) service. This service enables managed code calls to unmanaged functions in DLLs, and in C#, we need to create a static class that holds the main IAXClient functions. Simple parametersThe EXPORT int iaxc_initialize(int audType, int nCalls); In C#, we use the [DllImport(DllImportName, CallingConvention = CallingConvention.StdCall)]
public static extern int iaxc_initialize(int audType, int calls);
Other functions have character parameters that must be converted to C# types. For example, the function EXPORT int iaxc_register(char *user, char *pass, char *host); In C#, this kind of a parameter can be converted to [DllImport(DllImportName)]
public static extern int iaxc_register(
[MarshalAs(UnmanagedType.LPStr)] string user,
[MarshalAs(UnmanagedType.LPStr)] string pass,
[MarshalAs(UnmanagedType.LPStr)] string host);
The Struct parametersYou can pass and receive structure parameters using the EXPORT int iaxc_audio_devices_get(struct iaxc_audio_device **devs, int *nDevs, int *input, int *output, int *ring); The function needs a pointer to a pointer parameter (indicated by [DllImport(DllImportName, CallingConvention = CallingConvention.StdCall)]
public static extern int iaxc_audio_devices_get(
ref IntPtr devs,
ref int nDevs,
ref int input,
ref int output,
ref int ring);
The MarshalEx class
Even using the //----- Initialize!
IntPtr pdevs = IntPtr.Zero;
int nDevs = 0;
int input = 0;
int output = 0;
int ring = 0;
//----- Call function!
if (IAXFunctions.iaxc_audio_devices_get(ref pdevs,
ref nDevs, ref input, ref output, ref ring) != 0)
throw new IAXMethodException("Initialize error!");
//----- Cast the pointer!
iaxc_audio_device devs = (iaxc_audio_device)
Marshal.PtrToStructure(pdevs, typeof(iaxc_audio_device));
In this code, we initialize the But, the function has a To loop through all the devices, we need to use the public class MarshalEx
{
public static object[] PtrToStructureArray(IntPtr pointer,
Type structureType, int len)
{
//----- Creates the array!
object[] array = new object[len];
for (int i = 0; i < len; i++)
{
//----- Get the structure pointed from pointer parameter address!
array[i] = Marshal.PtrToStructure(pointer, structureType);
//----- Add to the pointer parameter address the size of structure!
//----- After that, pointer parameter
//----- points to another structure in memory!
pointer = (IntPtr) (pointer.ToInt32() + Marshal.SizeOf(array[i]));
}
return array;
}
}
The trick here is to walk in the Using this method, we can now loop through all the devices: //----- Initialize!
IntPtr pdevs = IntPtr.Zero;
int nDevs = 0;
int input = 0;
int output = 0;
int ring = 0;
//----- Call function!
if (IAXFunctions.iaxc_audio_devices_get(ref pdevs, ref nDevs,
ref input, ref output, ref ring) != 0)
throw new IAXMethodException("Initialize error!");
//----- Get the structure array!
object[] devs = MarshalEx.PtrToStructureArray(pdevs,
typeof(iaxc_audio_device), nDevs);
foreach (object o in devs)
{
iaxc_audio_device d = (iaxc_audio_device) o;
...
}
IAX eventsThe EXPORT int iaxc_set_event_callpost(void *handle, int id); In C# we use the [DllImport(DllImportName)]
public static extern int iaxc_set_event_callpost(
IntPtr handle,
int id);
NativeWindowEx class
To receive the message events, you need to create a message procedure from a custom window. You could do it using the Windows API public class NativeWindowEx : NativeWindow
{
#region Fields
//----- MessageEvent delegate!
private event OnMessageDelegate FOnMessageEvent;
//----- Pinned Handle!
private GCHandle FPinnedHandle;
#endregion
#region Constructor
public NativeWindowEx(CreateParams createParam, bool pinned)
{
//----- Create the windows with the parameters!
this.CreateHandle(createParam);
if (pinned)
{
//----- If need to pin create the GCHandle from window handle!
FPinnedHandle = GCHandle.Alloc(this.Handle, GCHandleType.Pinned);
}
}
public NativeWindowEx(IntPtr handle, bool pinned)
{
//----- Assign the handle to the class!
this.AssignHandle(handle);
if (pinned)
{
//----- If need to pin create the GCHandle from window handle!
FPinnedHandle = GCHandle.Alloc(handle, GCHandleType.Pinned);
}
}
#endregion
#region Methods
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
//----- Call the MessageEvent!
if (FOnMessageEvent != null)
FOnMessageEvent(ref m);
}
#endregion
#region Properties
public GCHandle PinnedHandle
{
get
{
return FPinnedHandle;
}
}
public event OnMessageDelegate OnMessage
{
add
{
FOnMessageEvent += value;
}
remove
{
FOnMessageEvent -= value;
}
}
#endregion
}
Using the ...
//----- Message Event!
//----- Magic number!
FMessageId = 123456;
FWindow = new NativeWindowEx(new CreateParams(), true);
FWindow.OnMessage += new OnMessageDelegate(FWindow_OnMessage);
if (IAXFunctions.iaxc_set_event_callpost(FWindow.Handle, FMessageId) != 0)
throw new IAXMethodException("Event handler initialize error!");
if (IAXFunctions.iaxc_start_processing_thread() != 0)
throw new IAXMethodException("Event handler start error!");
...
In this code, we first assign the message ID that will be passed to Windows message. After that, we create the GCHandle and pinnedNote the Event structures
typedef struct iaxc_event_struct { struct iaxc_event_struct *next; int type; union { struct iaxc_ev_levels levels; struct iaxc_ev_text text; struct iaxc_ev_call_state call; struct iaxc_ev_netstats netstats; struct iaxc_ev_url url; struct iaxc_ev_video video; struct iaxc_ev_registration reg; } ev; } iaxc_event; The [StructLayout(LayoutKind.Explicit)]
internal struct iaxc_event
{
[FieldOffset(0)]
public IntPtr next;
[FieldOffset(4)]
public EventType type;
[FieldOffset(8)]
public iaxc_ev_levels level;
[FieldOffset(8)]
public iaxc_ev_text text;
[FieldOffset(8)]
public iaxc_ev_call_state callState;
[FieldOffset(8)]
public iaxc_ev_registration registration;
}
TypeLoadExceptionAfter converting the System.TypeLoadException was unhandled
Message="Could not load type 'ALAZ.TelephonyEx.Voip.IAX.iaxc_event'
from assembly 'ALAZ.TelephonyEx, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null'
because it contains an object field at offset 8 that
is incorrectly aligned or overlapped by a non-object field."
Source="ALAZ.TelephonyEx"
TypeName="ALAZ.TelephonyEx.Voip.IAX.iaxc_event"
StackTrace:
at
...
The exception is thrown because C# doesn't handle value and reference types using the same ... #define IAXC_EVENT_BUFSIZ 256 ... struct iaxc_ev_text { int type; int callNo; char message[IAXC_EVENT_BUFSIZ]; }; To convert it to C#, we use the internal struct iaxc_ev_text
{
public TextType type;
public int callNo;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]
public string message;
}
The Event message callbackTo solve the internal struct iaxc_event
{
public IntPtr next;
public EventType type;
}
In the new private void FWindow_OnMessage(ref System.Windows.Forms.Message m)
{
if (!Disposed)
{
//----- Check messageId!
if (m.Msg == FMessageId)
{
//----- Get event data!
iaxc_event e = (iaxc_event)
Marshal.PtrToStructure(m.LParam, typeof(iaxc_event));
//----- Walks through the LParam memory
//----- address to find event data field!
IntPtr eventPointer = new IntPtr(m.LParam.ToInt32() + 8);
switch (e.type)
{
case EventType.etIAXC_EVENT_LEVELS:
{
//----- Meter Level!
iaxc_ev_levels ev = (iaxc_ev_levels)
Marshal.PtrToStructure(eventPointer,
typeof(iaxc_ev_levels));
...
case EventType.etIAXC_EVENT_REGISTRATION:
{
//----- Registration!
iaxc_ev_registration ev = (iaxc_ev_registration)
Marshal.PtrToStructure(eventPointer,
typeof(iaxc_ev_registration));
...
case EventType.etIAXC_EVENT_STATE:
{
//----- Call State!
iaxc_ev_call_state ev = (iaxc_ev_call_state)
Marshal.PtrToStructure(eventPointer,
typeof(iaxc_ev_call_state));
...
case EventType.etIAXC_EVENT_TEXT:
{
//----- Text!
iaxc_ev_text ev = (iaxc_ev_text)
Marshal.PtrToStructure(eventPointer,
typeof(iaxc_ev_text));
...
}
eventPointer = IntPtr.Zero;
IAXFunctions.iaxc_free_event(m.LParam);
}
}
}
The The IAXClientClassThe public IAXClientClass(AudioType audioType, int lines)
{
FCallerIdName = "SharpIAX";
FCallerIdNumber = "(Not Specified)";
FAudioType = audioType;
FLinesList = new List<IAXLine>();
FLinesCollection = new IAXLines(FLinesList, this);
FAudioDevicesList = new List<IAXAudioDevice>();
FAudioDevicesCollection = new IAXAudioDevices(FAudioDevicesList);
FSilenceThreshold = -99;
FRegistrationRequired = true;
FLines = lines;
//----- Initialize!
if (IAXFunctions.iaxc_initialize((int)FAudioType, FLines) != 0)
throw new IAXMethodException("Initialize error!");
//----- Devices
IntPtr pdevs = IntPtr.Zero;
int nDevs = 0;
int input = 0;
int output = 0;
int ring = 0;
if (IAXFunctions.iaxc_audio_devices_get(ref pdevs, ref nDevs,
ref input, ref output, ref ring) != 0)
throw new IAXMethodException("Initialize error!");
FAudioDevicesCollection.SelectedInputAudioDevice = input;
FAudioDevicesCollection.SelectedOutputAudioDevice = output;
FAudioDevicesCollection.SelectedRingAudioDevice = ring;
object[] devs = MarshalEx.PtrToStructureArray(pdevs,
typeof(iaxc_audio_device), nDevs);
foreach (object o in devs)
{
iaxc_audio_device d = (iaxc_audio_device)o;
IAXAudioDevice iaxd = new IAXAudioDevice(d.devID,
d.name, d.capabilities);
FAudioDevicesList.Add(iaxd);
}
}
The public void Initialize(string user, string password,
string server, bool register)
{
if (!Disposed)
{
//----- Account!
FUser = user;
FPass = password;
FServer = server;
FRegistrationRequired = register;
//---- Caller Id!
IAXFunctions.iaxc_set_callerid(FCallerIdName, FCallerIdNumber);
//----- Silence Threshold !
IAXFunctions.iaxc_set_silence_threshold(FSilenceThreshold);
//----- Formats!
FPreferredFormat = AudioFormat.afIAXC_FORMAT_GSM;
FAllowedFormats = FPreferredFormat;
IAXFunctions.iaxc_set_formats((int)FPreferredFormat,
(int)FAllowedFormats);
//----- Message Event!
//----- Magic number!
FMessageId = 123456;
FWindow = new NativeWindowEx(new CreateParams(), true);
FWindow.OnMessage += new OnMessageDelegate(FWindow_OnMessage);
if (IAXFunctions.iaxc_set_event_callpost(FWindow.Handle, FMessageId) != 0)
throw new IAXMethodException("Event handler initialize error!");
if (IAXFunctions.iaxc_start_processing_thread() != 0)
throw new IAXMethodException("Event handler start error!");
for (int i = 1; i <= FLines; i++)
{
FLinesList.Add(new IAXLine(FLinesCollection, i - 1));
}
FLinesCollection.SelectLine(0);
if (FRegistrationRequired)
{
Register();
}
}
}
The public void ShutDown()
{
if (!Disposed)
{
IAXFunctions.iaxc_dump_all_calls();
if (FRegistrationId > 0)
IAXFunctions.iaxc_unregister(FRegistrationId);
if (IAXFunctions.iaxc_stop_processing_thread() != 0)
throw new IAXMethodException("Event handler stop error!");
IAXFunctions.iaxc_shutdown();
}
}
Playing soundsIAXClient library has some sounds functions called
The main fields in private void PlaySound(string resourceName, int repeat, ref int Id)
{
Stream fs =
Assembly.GetExecutingAssembly().
GetManifestResourceStream(resourceName);
fs.Position = 44;
byte[] arr = new byte[fs.Length - fs.Position];
fs.Read(arr, 0, arr.Length);
fs.Close();
GCHandle h = GCHandle.Alloc(arr, GCHandleType.Pinned);
iaxc_sound s = new iaxc_sound();
s.len = arr.Length / 2;
s.data = h.AddrOfPinnedObject();
s.malloced = 0;
s.channel = 0;
s.repeat = repeat;
Id = IAXFunctions.iaxc_play_sound(ref s, 0);
h.Free();
}
The Embedded resourcesYou can embed resource files in Assembly simply setting the resource's
Wav file headerThe IAXAudioDevicesThe private void SetAudioDevices()
{
if (IAXFunctions.iaxc_audio_devices_set(FInputDevice,
FOutputDevice, FRingDevice) != 0)
throw new IAXMethodException("Set device error!");
}
public void SelectInputAudioDevice(int deviceId)
{
FInputDevice = deviceId;
SetAudioDevices();
}
public void SelectOutPutAudioDevice(int deviceId)
{
FOutputDevice = deviceId;
SetAudioDevices();
}
public void SelectRingAudioDevice(int deviceId)
{
FRingDevice = deviceId;
SetAudioDevices();
}
IAXLinesThe Each public void MakeCall(string number)
{
string callMessage = String.Format(CALLFORMAT,
FCollection.Host.User, FCollection.Host.Password,
FCollection.Host.Server, number);
IAXFunctions.iaxc_call(callMessage);
}
public void DropCall()
{
IAXFunctions.iaxc_dump_call();
}
public void AnswerCall()
{
IAXFunctions.iaxc_answer_call(FLineNumber);
}
public void SendDTMF(byte digit)
{
IAXFunctions.iaxc_send_dtmf(digit);
}
public void TransferCall(string number)
{
IAXFunctions.iaxc_blind_transfer_call(FLineNumber, number);
}
SharpIAX demoThe SharpIAX demo is a Windows Forms application that uses the In the Configuration->Network tab, you can set the server's address, username and password, caller information, and the audio formats. You can also set the audio devices used in input, output, and ring in the Configuration->Audio tab: Compiling IAXCLientThe SharpIAX demo binaries already uses a compiled version of IAXClient library but, if you wish to compile it, you need to download it from Sourceforge. CygWinFirst of all, you need to download CygWin. Run the setup application, and choose to install from the internet.
After that, select the root directory (installation directory):
the local package directory (downloaded packages directory):
and the download site.
When the Select Packages form appears, in addition to the default packages, select the gcc-mingw, gcc-mingw-core, gcc-mingw-g++
makefile,
and subversion packages located at the Devel category.
After installation, run the cygwin.bat on the CygWin installation directory to open the Cygwin shell.
Download IAXClientTo download IAXClient sources, you need to execute the svn command. Inside CygWin shell, create a new directory somewhere and, on the new directory, execute the following command (don't forget the dot at the end!): svn co https://svn.sourceforge.net/svnroot/iaxclient/trunk/ .
Compiling IAXClientAfter downloading it, locate the iaxlclient/lib directory and, inside this directory, type the make command to generate the DLL: make shared
If you get some errors, check the makefile file located in the lib directory. Otherwise, you've a new fresh iaxclient.dll. ConclusionI hope that this article helps you to get familiar with the IAXClient library and understand how P/Invoke can be utilized to work with unmanaged applications and structures. I'd like to thank Mr. Adelino Baena from Alcance Brasil for the Asterisk server installation, maintenance, support, and incentive to build this library. Any comments will be appreciated. History
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||