Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Using the Asterisk IAXClient library in C#

4.90/5 (35 votes)
13 Nov 2006CPOL12 min read 1   4.7K  
A C# wrapper for the Asterisk IAXClient library.

Article

Introduction

Asterisk 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:

  • The main functions and structures used in IAXClient.
  • How to use it in C# using P/Invoke.
  • How to compile it.

IAXFunctions class

IAXFunctions

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 parameters

The IAXFunction class has the most important functions converted to C# types. Some functions have primitive types, and can be converted directly. For example, the function iaxc_initialize accepts two integer parameters and returns an integer value:

EXPORT int iaxc_initialize(int audType, int nCalls);

In C#, we use the [DLLImport] attribute (DllImportName is a constant having the DLL path):

C#
[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 iaxc_register accepts two char pointer parameters and returns an integer value:

EXPORT int iaxc_register(char *user, char *pass, char *host);

In C#, this kind of a parameter can be converted to string but you need to set the MarshalAs attribute:

C#
[DllImport(DllImportName)]
public static extern int iaxc_register(
[MarshalAs(UnmanagedType.LPStr)] string user, 
[MarshalAs(UnmanagedType.LPStr)] string pass,
[MarshalAs(UnmanagedType.LPStr)] string host);

The MarshalAs tells C# how to marshal the data to the unmanaged function, and you need to set it according to the parameter type. In this case, the UnmanagedType enumeration used is LPStr, indicating that we have a null-terminated ANSI character string from unmanaged code.

Struct parameters

You can pass and receive structure parameters using the MarshalAs attribute with the UnmanagedType.LPStruct enumeration, but in the function iaxc_audio_devices_get, we need special care. Look at the function signature:

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 **). C# doesn't have a UnmanagedType option to use with this kind of a parameter, and we need a workaround here:

C#
[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 IntPtr type is a specific type that represents a pointer, and we can use it to represent the 'a pointer' part of the parameter. The first part, 'a pointer to' can be achieved using the ref parameter attribute, specifying that the parameter is a reference parameter used normally in C# code. So, ref IntPtr means a pointer (ref) to a pointer (IntPtr).

MarshalEx class

MarshalEx

Even using the ref IntPtr, we cannot access the structure values because the IntPtr has only a handle to it. To access it, we need to use the PtrToStructure method from the .NET Marshal class. This method takes an IntPtr parameter and a structure type parameter, and returns (casts) the structure object:

C#
//----- 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 IntPtr parameters with IntPtr.Zero and call the function. The pdevs parameter has the pointer to the iaxc_audio_device value, and using the PtrToStructure method, we can cast the pointer to the structure. So far so good.

But, the function has a nDevs parameter that indicates the number of devices pointed in the pdevs parameter. If the nDevs parameter returns 2, the pdevs parameter points to two iaxc_audio_device structures. Remember that the pdevs parameter is a pointer to a pointer, and in this case, it's pointing to the first structure. The next structures returned are pointed using an array (something like iaxc_audio_device[]).

To loop through all the devices, we need to use the MarshalEx class, calling the PtrToStructureArray method that walks in the pdevs parameter:

C#
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 pointer parameter address memory using the size of the structure. The code gets the first structure value, and puts it in the array. After that, the code adds the structure size to the address memory of pointer. Now, pointer points to the next structure value in memory. The process is repeated until all structures, indicated by the len parameter, are saved in the array.

Using this method, we can now loop through all the devices:

C#
//----- 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 events

The IAXClient has a message mechanism to fire events using Windows procedures. To receive events, you need to call the iaxc_set_event_callpost function, passing the window handle that will be used inside IAXClient to post event messages and the message ID that will be used in the window procedure to indicate that the message comes from IAXClient:

EXPORT int iaxc_set_event_callpost(void *handle, int id);

In C# we use the IntPtr type to pass the window handle:

C#
[DllImport(DllImportName)]
public static extern int iaxc_set_event_callpost(
    IntPtr handle, 
    int id);

NativeWindowEx class

NativeWindowEx

To receive the message events, you need to create a message procedure from a custom window. You could do it using the Windows API CreateWindowEx, DefWindowProc, CallWindowProc, etc. Instead, you can use the .NET NativeWindow class which creates the window, keeps the window handle, and creates a overridable window procedure in which you can receive messages. So, we can inherit the NativeWindow class and override the WndProc method:

C#
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 NativeWindowEx class, we can now pass the window handle to the iaxc_set_event_callpost:

C#
...

//----- 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 NativeWindowEx class and pass the handle from this class to the iaxc_set_event_callpost function. When the iaxc_start_processing_thread function is called, the messages will begin to come in FWindow_OnMessage.

GCHandle and pinned

Note the GCHandle class used in NativeWindowEx. This class is needed because every time you need to pass a managed object to the unmanaged world, you need to pin the class. Remember that when Garbage Collector runs, it tries to reach the objects. If an object is not reached, the object is collected by GC, and, to remove the free space from the collected objects, it moves reached objects to the free spaces, removing heap gaps and invalidating memory address. Using pinned objects, you can prevent the GC to move the object in the heap while the unmanaged application holds the reference to it. Without the pinned object, the object's memory can be moved in managed heap, making the object's address invalid in the unmanaged code. You can read more about GC here.

Event structures

iax_event

IAXClient sends information events using the following structure:

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 union clause is used in the structure indicating that the ev field value depends on the type field. So, if you receive a etIAXC_EVENT_LEVELS value in the type field, the ev field points to it. To convert this structure to C#, we use the FieldOffset attribute (only the main structures converted):

C#
[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;

}

FieldOffset is used with the StructLayout(LayoutKind.Explicit) clause to indicate the fields's offset in the structure. Using StructLayout(LayoutKind.Explicit), all fields must have a FieldOffset attribute and, to simulate the union clause, we need to put those fields in the same offset. In this case, we're using [FieldOffset(8)] in the union fields because the next and type fields use four bytes each in the structure's memory.

TypeLoadException

After converting the iaxc_event structure and running the application, the following TypeLoadException is thrown:

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 FieldOffset attribute. The following iaxc_ev_text structure is used in iaxc_event:

...
#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 MarshalAs attribute, indicating the correct UnmanagedType enumeration:

C#
internal struct iaxc_ev_text
{
    public TextType type;
    public int callNo;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]
    public string message;
}

The UnmanagedType.ByValTStr enumeration is used to represent the char array, and the size is defined with the SizeConst clause. As we are using the string type in the message field, C# cannot handle the structure, and throws the exception. If the structures used in the same FieldOffset attribute have only value types, the exception isn't thrown.

Event message callback

iax_event2

To solve the TypeLoadException problem, we need to use the .NET Marshal class and make some changes in the iaxc_event structure:

C#
internal struct iaxc_event
{
    public IntPtr next;
    public EventType type;
}

In the new iaxc_event structure, the union field is removed and is referenced in NativeWindowEx's message callback procedure:

C#
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 FWindow_OnMessage event receives a System.Windows.Forms.Message posted by the IAXClient library. After checking if the message is a FMessageId, we need to cast the message's LParam property to the iaxc_event structure using the Marshal.PtrToStructure method. At this point, e.type points to the EventType enumeration, but, where's the event data? The event data is there, but we need to walk through the LParam address to find it. Remember that the event data field is the third field in the IAXClient structure. So, we create a new IntPtr object pointing to 8 bytes (or two 32 bit fields) after the LParam memory address. The new IntPtr object now points to the event data field in the memory address, and now it can be cast to the respective event structure using a simple case statement. After using the event structure, we need to call the iaxc_free_event function to free the event data.

The IAXClientClass

iaxclientclass.JPG

The IAXClientClass is the main class to work with the IAXClient library. The IAXClientClass constructor initializes the the IAXClient library with the AudioType and the number of lines used, and gets the audio devices available:

C#
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 Initialize method sets the caller ID, silence threshold, audio formats, and initializes the message event process. The method adds to the IAXLines collection the number of IAXLine items requested and, if registration is required, it begins the registration procedure:

C#
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 Shutdown method terminates all active calls, unregisters the user, and stops the message event process:

C#
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 sounds

IAXClient library has some sounds functions called iaxc_play_sound and iaxc_stop_sound. iaxc_play_sound plays a raw wave buffer and returns a sound ticket. The ticket is used in iaxc_stop_sound to stop the sound playing. The iaxc_sound structure must be filled in order to play the sound.

iaxsound

The main fields in iaxc_sound is data and len. data is the raw wav buffer array to play and len defines the buffer array length. This structure is used in PlaySound method:

C#
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 resourceName parameter defines the embedded resource file name. The repeat parameters defines how many times the song will be played. The Id parameter defines the ticket returned from iaxc_play_sound.

Embedded resources

You can embed resource files in Assembly simply setting the resource's Build Action property to Embedded Resource:

embedded

Wav file header

The data field from iaxc_sound structure must be filled with wave raw data and we need to know where the raw data begins inside a wave file. Well, the answer is here. Wave files are divided in chunks and data chunk (raw data) begins at 44th byte in wave files. So, PlaySound method opens the embedded resource wave file, read it beginning from 44th byte till the end of file. After that, the iaxc_sound structure is filled with the pinned pointer of the raw data array. Then the sound is played using iaxc_play_sound function.

IAXAudioDevices

iaxaudiodevices

The IAXAudioDevices is a collection of IAXAudioDevices. Each IAXAudioDevice has ID, Name, and Capabilities properties. Using IAXAudioDevices methods, you can set the correct device for input, output, and ring:

C#
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();
}

IAXLines

iaxlines

The IAXLines is a collection of IAXLines. The SelectLine method selects the line to work with. To get the selected line, the SelectedLine property is used.

Each IAXLine has an ID, CallTime, and local and remote call information. The CallDirection property indicates if the call is an incoming or an outgoing call. The CallStatus property indicates the status of the call in the line. The MakeCall method is used to start a call on the selected line, and the DropCall method is used to terminate the call. The AnswerCall method answers a ringing line, and TransferCall transfers the call. To send digits in an active call, you can use the SendDTMF method.

C#
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 demo

The SharpIAX demo is a Windows Forms application that uses the IAXClientClass class. It uses two lines, and shows the call status of each line, shows IAX messages from the server, and sends DTMF digits:

commands

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:

configNet

configAudio

Compiling IAXCLient

The 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.

CygWin

First of all, you need to download CygWin.

Run the setup application, and choose to install from the internet.

cyg_internet

After that, select the root directory (installation directory):

cyg_root

the local package directory (downloaded packages directory):

cyg_root

and the download site.

cyg_site

When the Select Packages form appears, in addition to the default packages, select the gcc-mingw, gcc-mingw-core, gcc-mingw-g++

cyg_mingw

makefile,

cyg_make

and subversion packages located at the Devel category.

cyg_subv

After installation, run the cygwin.bat on the CygWin installation directory to open the Cygwin shell.

cyg_shell

Download IAXClient

To 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/ .

cyg_svn

Compiling IAXClient

After downloading it, locate the iaxlclient/lib directory and, inside this directory, type the make command to generate the DLL:

make shared

cyg_compile

If you get some errors, check the makefile file located in the lib directory. Otherwise, you've a new fresh iaxclient.dll.

Conclusion

I 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

  • 22 Aug, 2006: Initial version.
  • 11 Nov, 2006:
    1. CygWin screenshots added.
    2. New play methods do play DTMF tones, ring and busy sounds.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)