Wednesday, July 23, 2008

It's been a while since I updated the ATAPI assembly, I've fixed a couple of minor bugs that were reported and updated the samples to compile with VS2008.  I've also added two new events onto the TapiPhone class so you can see state changes and button presses - somehow I missed that when I added the phone support.

Finally, there's now online documentation available at

http://www.julmar.com/atapi_help/index.aspx

You can download the updated assembly, help file and samples from

http://www.julmar.com/tapi/atapinet.zip

Have fun!

posted on 7/23/2008 11:54:45 AM (Central Standard Time, UTC-06:00)  #   
 Friday, July 13, 2007

Sales of the TSP++ products have steadily fallen over the past 5 years, so much so that I decided two years ago to release the 2.x product into the open-source community, and now I am doing the same for the server edition. 

The download is available here and requires a key to install it - use 

J35M-3KXo-q60T

which will let you install the full product.  Documentation and samples are all part of the image so have fun!

posted on 7/13/2007 3:46:33 PM (Central Standard Time, UTC-06:00)  #   
 Friday, December 08, 2006
I've updated ATAPI.NET in the samples page http://www.julmar.com/samples/atapinet.zip with several fixes and support for conferences, full transfers and phone support. I think almost everything is now wrapped - if you find any missing functions that you want/need ping me and I'll see what I can do.
posted on 12/8/2006 12:11:22 PM (Central Standard Time, UTC-06:00)  #   
 Monday, June 19, 2006
Recording telephone conversations with TAPI
posted on 6/19/2006 9:41:45 AM (Central Standard Time, UTC-06:00)  #   
 Thursday, April 20, 2006

Minor, but breaking update for ATAPI.NET (version number has changed).  It was pointed out to me that the assembly wasn't very VB.NET friendly in that it didn't show any events at the TapiManager level and you couldn't use the UI to hook it all up.  That's fixed and all the line-level events are also exposed at the TapiManager level for those who want to use VB.NET with the 2.0 wrapper.

 

posted on 4/20/2006 8:01:49 AM (Central Standard Time, UTC-06:00)  #   
 Thursday, March 30, 2006

I have updated ITAPI3 to fix a couple of reported bugs -- the TE_FILETERMINAL event wasn't always being raised and the TCall.GenerateCustomTone didn't work properly.  Both of these issues are fixed in the latest drop.  Enjoy!

posted on 3/30/2006 5:16:09 PM (Central Standard Time, UTC-06:00)  #   
 Friday, March 17, 2006

I've posted a new version of ATAPI.NET which supports consultation transfers and a simple phone sample that shows off how to use the features.  In addtiion, this version of ATAPI.NET has a couple of bug fixes that were rolled in from a production project over the past couple of weeks to fix some weird startup/shutdown issues when a LINE_REINIT is reported by TAPI.  You can get the new code from our samples link -- http://www.julmar.com/samples.htm

 

posted on 3/17/2006 9:59:23 AM (Central Standard Time, UTC-06:00)  #   
 Wednesday, March 15, 2006

Forwarding lines with ATAPI.NET is simple and easy (assuming, of course, that the underlying TSP supports it).

The first step is knowing whether a given line device even supports forwarding.  This is trivial:

TapiManager mgr = new TapiManager("ForwardingTest");

foreach (TapiLine line in mgr.Lines)
{
   
if
(line.Capabilities.SupportsForwarding)
   {
      Console.WriteLine("Line {0} supports forwarding!"
, line.Name);
   }
}

Once we've identified a specific line, we can look at each address and get more information such as the types of forwarding supported.  For example, we might be able to forward to different numbers based on specific conditions such as whether the call goes unanswered vs. whether the address is in use and returning a busy signal.  We might also be able to forward specific inbound callers (very useful to get rid of your bosses calls).  We can get this information from the Capabilities of the TapiAddress object:

foreach (TapiAddress addr in line.Addresses)
{
   
Console.WriteLine("Forwarding modes supported on {0} are {1}"
, addr.Address, addr.Capabilities.SupportedForwardingModes);
}

We can also retrieve any existing forwarding information through the Status of the TapiAddress:

foreach (ForwardInfo fwd in addr.Status.ForwardingInformatioin)
   
Console.WriteLine("\t{0} to {1}:{2}"
, fwd.ForwardMode, fwd.DestinationAddressType, fwd.DestinationAddress);

This outputs: "Unconditional to PhoneNumber:1234" on a forwarded line I setup.

Finally, the big question is how to change the forwarding information, this is pretty easy as well.  You can set forwarding information on two levels, the entire line (which impacts all addresses), or a specific address.  This is done through two methods present on both TapiAddress and TapiLine which are Forward and CancelForward.  So, to cancel all forwarding in effect on every line we could do the following:

Console.WriteLine("Canceling all forwards:");
foreach (TapiLine line in
mgr.Lines)
{
   
if
(line.Capabilities.SupportsForwarding)
   {
      
try
      
{
         line.CancelForward();
      }
      
catch (TapiException
ex)
      {
         
Console.WriteLine("{0} - {1}"
, line, ex.Message);
      }
   }
}

Or, to setup the forwarding as above, I can issue a call to the Forward method:

ForwardInfo[] fwdInfo = new ForwardInfo[] {
      
new ForwardInfo(ForwardingMode.Unconditional, 0, "1234"
)
};

foreach (TapiLine line in mgr.Lines)
{
   
if
(line.Capabilities.SupportsForwarding)
   {
      
try
      
{
         line.Forward(fwdInfo, 5,
null
);
      }
      
catch (TapiException
ex)
      {
         
Console.WriteLine("{0} - {1}"
, line.Name, ex.Message);
      }
   }
}

The ForwardInfo class describes a single forwarding instruction and you pass an array of these info the Forward method to indicate how things are to be managed.  Exceptions need to be handled because the TAPI service provider might not allow the particular forwarding at this point in time, or the destination might not be allowed, etc.

Under the covers this will issue a lineForward request with a LINEFORWARDLIST setup for each of the ForwardInfo structures.

That about covers it!  Ping me with any questions if you want.

 

posted on 3/15/2006 2:43:58 PM (Central Standard Time, UTC-06:00)  #   
 Tuesday, March 07, 2006

So, a question was asked "How do I determine what's happening in the TAPI3 wrapper"?  The answer is you turn on the internal trace source -- ITapi3 was built with a build in tracing facility to tell you when it had any underlying interface or COM failures and it's easy to activate.  First, add an Application Configuration File to your project.  Open that file and add the following lines:

<?xml version="1.0" encoding="utf-8" ?>
<
configuration>
   <
system.diagnostics>
      <
sources>
         <
source name="ITapiTrace" switchName="tapiSwitch" switchType="System.Diagnostics.SourceSwitch">
            <
listeners>
               
<add name="MyTraceLog" type="System.Diagnostics.TextWriterTraceListener" initializeData="MyTrace.txt" />
            </
listeners>
         </
source>
      </
sources>
      <
switches>
         <
add name="tapiSwitch" value="All" />
      </
switches>
   </
system.diagnostics>
</
configuration>

This will create a file called "MyTrace.txt" in your working directory.  The important line is the source tag which identifies the internal TraceSource object used by the ITapi3 library.  Inside this file will be the internal TAPI3 calls being made for your application.  As an example, the following trace shows me that several underlying COM errors occurred in the running of a simple TAPI3 application -- it was unable to retrieve the ITTerminal interface from the ITAddressEvent interface (which actually isn't really an error), failed to open the line (because Unimodem won't allow the media type VOICE to be passed for my modem), and failed to set the play list for this MSP -- [0x80040216] is actually a DirectShow error [VFW_E_NOT_FOUND].

ITapiTrace Verbose: 0 : Creating ITTAPI instance
ITapiTrace Verbose: 0 : Hooking up connection sink to ITTAPI interface
ITapiTrace Information: 0 : ITTapi::put_EventFilter(0x8001F) hr=0x0
ITapiTrace Error: 0 : COM Hresult 0x80040004 "The MEDIATYPE passed in to this method was invalid." generated 
   at JulMar.Tapi3.TapiException.ThrowExceptionForHR(Int32 hr)
   at JulMar.Tapi3.TTapi.RegisterCallNotifications(ITAddress* pitf, Int16 vbMonitor, Int16 vbOwner, Int32 supportedMediaTypes)
   at JulMar.Tapi3.TAddress.Open(TAPIMEDIATYPES supportedMediaTypes)
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 5, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 4, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 3, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 2, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 1, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 0, Terminal=
ITapiTrace Verbose: 0 : Processing TapiCallNotificationEventArgs: Event=CNE_OWNER, Call=TCall: 171360625 CS_OFFERING
ITapiTrace Verbose: 0 : Processing TapiCallStateEventArgs: Call=TCall: 171360625 CS_OFFERING, State=CS_OFFERING, Cause=CEC_NONE
ITapiTrace Error: 0 : COM Hresult 0x80040216 "" generated 
   at JulMar.Tapi3.TapiException.ThrowExceptionForHR(Int32 hr)
   at JulMar.Tapi3.TTerminal.set_MediaPlayList(String[] fileList)
   at AnsMachine.AutoAttendantForm.AnswerCall()
   at AnsMachine.AutoAttendantForm.OnCallState(Object sender, TapiCallStateEventArgs e)
ITapiTrace Verbose: 0 : Processing TapiCallStateEventArgs: Call=TCall: 171360625 CS_DISCONNECTED, State=CS_DISCONNECTED, Cause=CEC_DISCONNECT_NORMAL
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 5, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 4, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 3, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 2, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 1, Terminal=
ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055
ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 0, Terminal=
ITapiTrace Error: 0 : ITTapi::Shutdown hr=0x0

 

posted on 3/7/2006 5:18:50 PM (Central Standard Time, UTC-06:00)  #   

Matthias Moetje (Tapi MVP) has graciously taken one of the VB6 Platform SDK samples and ported it to the ITapi3 library and allowed me to distribute it in the Tapi3 Wrapper sample set.  It allows you to select an address, place a call on it and then monitor or generate digits.  Great sample - thanks Matthias!

posted on 3/7/2006 5:04:47 PM (Central Standard Time, UTC-06:00)  #   
 Monday, March 06, 2006

I got an email question today asking about how to write a .WAV file out to an active call using our TAPI3 wrapper.  It's actually pretty easy to do and it follows along with the normal SDK method used in C++.  Here's the relevant code (for the full example, download the http://www.julmar.com/samples/tapi3wrapper.zip and look at the AutoAttendant sample).

First, when a new offering call shows up, get hold of the file playback terminal for it and set it up to play your .WAV files.  We cannot play the files until the call is answered, but this will essentially "queue" it up.

// Method called when TE_CALLSTATE == OFFERING raised
private void AnswerCall()
{
   // Get the playback terminal from the call
   try
   {
      playbackTerminal = activeCall.RequestTerminal(TTerminal.FilePlaybackTerminal, 
                  TAPIMEDIATYPES.AUDIO, TERMINAL_DIRECTION.TD_CAPTURE);
      if (playbackTerminal != null)
      {
         playbackTerminal.MediaPlayList = new string[] { "Hello.wav" };
         activeCall.SelectTerminalOnCall(playbackTerminal);
         activeCall.Answer();
      }
      else
      {
         MessageBox.Show("Failed to retrieve playback terminal.");
         activeCall.Disconnect(DISCONNECT_CODE.DC_REJECTED);
      }
   }
   catch (TapiException ex)
   {
   }
}

Next, watch for the call media to change indicating we have an active stream for our terminal.  When that happens, start the playback stream:

// Method called when TE_CALLMEDIA is raised
private void OnCallMedia(object sender, TapiCallMediaEventArgs e)
{
   try 
   {
      if (activeCall != null && e.Event == CALL_MEDIA_EVENT.CME_STREAM_ACTIVE &&
            e.Terminal.Direction == TERMINAL_DIRECTION.TD_CAPTURE &&
            playbackTerminal != null)
      {
         playbackTerminal.Start();
         SetStatusMessage("File Playback Terminal started ");
      }
   }
   catch (TapiException ex)
   {
   }
}

Finally, when the file terminal is finished, close and dispose the stream.  This is done just to cleanup the resources properly:

// Method called when TE_FILETERMINAL is raised
private
void OnFileTerminal(object sender, TapiFileTerminalEventArgs e)
{
   // We are interested in TMS_IDLE because we will un-select playback and 
   // select recording
   if (e.State == TERMINAL_MEDIA_STATE.TMS_IDLE)
   {
      if (e.Terminal.Direction == TERMINAL_DIRECTION.TD_CAPTURE && playbackTerminal != null)
      {
         try
         {
            // Remove the playback terminal
            activeCall.UnselectTerminalOnCall(playbackTerminal);
            playbackTerminal.Dispose();
            playbackTerminal = null;
         }
         catch (TapiException ex)
         {
         }
      }
   }
}

That's pretty much it -- there's a full sample of this that should work with voice modems or any streaming-capable TSP. 

Updated: 3/16/05 -- It doesn't appear to completely work with the H.323 provider; the file terminal gets connected but apparently never reports an IDLE state and so never gets disconnected in the above sample.  This appears to be true of the Platform SDK samples as well.

posted on 3/6/2006 5:02:34 PM (Central Standard Time, UTC-06:00)  #   
 Friday, March 03, 2006

New announcement - TAPI3 wrapper for .NET available on our samples page -- http://www.julmar.com/samples/tapi3wrapper.zip

Recently, I released the ATAPI.NET project into the wild for people to be able to easily code up .NET applications that utilize TAPI.  That assembly is based on the C API exported from TAPI 2.1 and so uses the older form of TAPI programming.  With Windows 2000, Microsoft released a COM version of TAPI - dubbed TAPI 3.0 that was designed to allow VB developers to access the telephony API.  However, as I noted in my previous post, Microsoft claims that the object model is too complex and that there are significant issues with accessing it from managed code.

.NET has a built-in facility to access COM objects, it creates .NET wrappers (called RCW's) around the COM interfaces and then allows you to call the COM object as if it were a .NET object.  That's great until you need to mesh the COM style of cleanup (AddRef/Release) with the .NET style (GC).  Essentially, the COM object doesn't get released until the managed wrapper gets collected.  In addition, if you have multiple wrappers for a single interface (TAPI returns the same interface through different methods), the interface won't be released until all the wrappers are collected.

Sometimes, this isn't really a problem - so we keep a COM interface alive a little longer than normal.  In TAPI's case however it can be a huge problem because some TAPI service providers will not let you create new calls unless the existing call interface has been released - even if it isn't connected to anything.

So, two options exist today:

1) Don't use TAPI 3.x - use TAPI 2.x where you have more control over the underlying TAPI call handle and can call lineDeallocate yourself.  In .NET this means using some wrapper like ATAPI.NET.  If you don't need terminal and stream support, this is fine.  This is what the newsgroups typically will recommend to people.

2) Call Marshal.ReleaseCOMObject on every outstanding interface.  Just plain ugly and extremely error-prone.

Now, a new option exists.  I've been in conversations with Matthias Moetje - one of the TAPI MVPs and went back to my original TAPI3 wrapper which was written in MC++ (yuck) and ported it to C++/CLI (which, BTW, is very cool).  It has almost everything supported except agents and call center support.  It solves the above problem in a couple of steps:

1) It guarantees that the same interface will match 1:1 with a managed object as long as the object hasn't been collected yet.  This means the object will only have one holding managed object.
2) It implements IDisposable on most of the wrappers allowing the client to get rid of interfaces immediately.
3) It "auto-disposes" calls when they hit the disconnected state.  This can be turned on or off depending on need through the TTapi.AutoDestroyCalls flag

Instead of a Register function, each address has an Open and Monitor method and a Close method. Since you have to pass the address in anyway, I moved the function to that level and made it distinct rather than having you pass in two booleans to indicate intent.

There are some shorthand functions: for example, the TCall object (which represents ITBasicCallControl, ITCallInfo, et.al.) has a SelectDefaultTerminals method which enumerates the streams and hooks up the default static/video terminals for each stream - basically the code that was in every TAPI sample provided by MS. There's also a FindTerminal method on the TStream class which will locate a terminal of the proper media type/direction if it exists.

The library itself follows the TAPI3 interfaces pretty closely - except it combines the various control interfaces together.  So for example:

Class

Interfaces

TTapi

ITTapi, ITTapi2

TAddress

ITAddress, ITAddress2, ITAddressCapabilities, ITAddressTranslation, ITLegacyAddressMediaControl, ITLegacyAddressMediaControl2, ITMediaSupport, ITTerminalSupport

TCall

ITCallInfo, ITBasicCallControl, ITBasicCallControl2, ITStreamControl, ITLegacyCallMediaControl, ITLegacyCallMediaControl2

In each case, the same properties and methods are exposed by the reference class and it will do the underlying cast necessary to get to the functionality.  The downside of this, is that if the interface isn't supported, the library must throw an exception - in this case a TapiException which holds a string message indicating the failure and an error code (the HRESULT).  The TTapi class exposes the TE_xxx events you generally registered a sink for.

I didn't bother to encapsulate the DirectShow library - instead each of the TAPI wrappers provides a QueryInterface method which you can use to get to an interface if necessary.  So, for example, if you want to get to the venerable IVideoWindow interface from DirectShow, you can include an interop reference to Quartz.DLL and then do the following on the TTerminal object:

IVideoWindow videoWindow = thisTerminal.QueryInterface(typeof(IVideoWindow)) as IVideoWindow;
if (videoWindow != null) { ... }

So, here's an example of using it - I think it's much cleaner that the comparable RCW code, and it's thread-aware and works around the general problem of TAPI3 under .NET through a Dispose mechanism and the TTapi.AutoDestroyCalls flag.

using System;
using System.Collections.Generic;
using System.Text;
using JulMar.Tapi3;

namespace TestTapi
{
   class Program
   {
      static void Main(string[] args)
      {
         TTapi tapi = new TTapi();
         
TCall call = null; TAddress modemAddr = null;

         tapi.Initialize();
         tapi.TE_CALLNOTIFICATION +=
delegate(object sender, TapiCallNotificationEventArgs e)  
         {
            
Console.WriteLine("New call {0} detected from {1}", e.Call.ToString(), e.Event);
         };
         
tapi.TE_CALLSTATE += delegate(object sender, TapiCallStateEventArgs e)
         {
            
Console.WriteLine("{0}:{4} has changed state to {1} due to {2} - current={3}:{5}"
                  
e.Call, e.State, e.Cause, e.Call == call, e.Call.GetHashCode(), (call != null) ? call.GetHashCode() : 0);
            
if (e.State == CALL_STATE.CS_INPROGRESS && e.Call == call)
            {
               
Console.WriteLine("Dropping call");
               e.Call.Disconnect(
DISCONNECT_CODE.DC_NORMAL);
            }
         };

         foreach (TAddress addr in tapi.Addresses)
         {
            
if (String.Compare(addr.ServiceProviderName, "unimdm.tsp", true) == 0 && 
               
addr.QueryMediaType(TAPIMEDIATYPES.AUDIO))
               modemAddr = addr;
         }

         if (modemAddr != null)
         {
            
Console.WriteLine("{0} = {1} ({3}) [{2}]", modemAddr.AddressName, modemAddr.State, modemAddr.ServiceProviderName, modemAddr.DialableAddress);
            modemAddr.Monitor(
TAPIMEDIATYPES.AUDIO);
            
ConsoleKey ki = ConsoleKey.A;

            while (ki != ConsoleKey.Q)
            {
               
// Flip the auto-destroy flag
               
if (ki == ConsoleKey.D) 
               {
                  tapi.AutoDestroyCalls = !tapi.AutoDestroyCalls;
                  
Console.WriteLine("Set AutoDestroy to {0}", tapi.AutoDestroyCalls);
               }
               
// List existing calls
               else
if (ki == ConsoleKey.L)
               {
                  
foreach (TCall _call in modemAddr.EnumerateCalls())
                  {
                     
Console.WriteLine("Existing call found: {0}:{1}", _call, _call.GetHashCode());
                     _call.Dispose();
// Go ahead and dump it
                  
}
               }
               
// Create a new call
               
else
               
{
                  call = modemAddr.CreateCall(
"5551213", LINEADDRESSTYPES.PhoneNumber, TAPIMEDIATYPES.DATAMODEM);
                  
Console.WriteLine("Created new call {0}:{1}", call, call.GetHashCode());
                  
try
                  
{
                     
// This will fail if existing call interface is still around (i.e. not disposed)
                     
call.Connect(false);
                  }
                  
catch (TapiException ex)
                  {
                     
Console.WriteLine(ex.Message);
                  }
               }
               
Console.WriteLine("Press a key to try another call.. Q to quit");
               ki =
Console.ReadKey().Key;
            }
         }

         // This will destroy any outstanding interfaces
         tapi.Shutdown();

         // Call should be disposed here.. state will be CS_UNKNOWN 
         
if (call != null)
            
Console.WriteLine("{0} {1}", call, call.CallState);
      }
   }
}

 

posted on 3/3/2006 12:05:50 PM (Central Standard Time, UTC-06:00)  #   
 Wednesday, February 15, 2006

Ok, ok so it's been a while.  I've been very busy with two tasks -- first, I spent the last few weeks doing a Guerrilla .NET for DevelopMentor with Rich Blewitt.  We had a blast together and it was great to hang out with him.  I also spent a day sitting in on DM's new C++/CLI class being taught by the very capable Marcus Heege - incredible stuff which every C++ guy on the Microsoft platform should get into..

The other thing I've been working with is resurrecting an old project of mine - ATAPI which was originally setup to wrap the TAPI 2.x API in an "easy to use" set of C++ classes.  I'd ported it to .NET a few years ago but was not really happy with the results.  I had the chance to revisit it because of a client's requirement to integrate TAPI into their .NET platform code.  So, I spent a couple of weeks working on the codebase again under .NET 2.0 and this time around I'm pretty pleased with the architecture.  I wanted something very easy to use, and I think I've achieved that even though it isn't a complete wrapper. 

For example, to walk through all the lines and dump out the device classes available - you can simply do this:

using System;
using System.Collections.Generic;
using System.Text;
using JulMar.Atapi;

namespace EnumDevices
{
    class Program
    {
        static void Main(string[] args)
        {
            TapiManager mgr = new TapiManager("EnumDevices");
            mgr.Initialize(); // Start up Tapi

            foreach (TapiLine line in mgr.Lines)
            {
                foreach (string s in line.Capabilities.AvailableDeviceClasses)
                    Console.WriteLine("{0} - {1}", line.Name, s);
            }           
            mgr.Shutdown();
        }
    }
}

Cool huh?

So.. why not use the TAPI3 COM API you ask?  Well, as it turns out, it doesn't work that well with the RCW infrastructure in .NET -- check out http://support.microsoft.com/kb/841712/en-us where Microsoft basically says "TAPI3 is too complicated".. like I needed someone to tell me that..

The ATAPI.NET stuff is available from JulMar's download area - you can get it along with a sample program from http://www.julmar.com/samples/atapinet.zip.

enjoy.

posted on 2/15/2006 1:20:58 PM (Central Standard Time, UTC-06:00)  #