IAudioSessionManager2 notifications not sent - com

I'm trying to monitor new audio sessions via Windows 7's IAudioSessionManager2 COM interface (coupled with IAudioSessionNotification). Currently, IAudioSessionNotification::OnSessionCreated() is never called and I've run out of ideas as to why.
Code registering custom IAudioSessionNotification:
#define SAFE_RELEASE(comObj) \
if(comObj != NULL) \
{ (comObj)->Release(); comObj = NULL; }
BOOL success = false;
HRESULT res;
IClassFactory* pFactory;
IMMDevice* pDevice;
IMMDeviceEnumerator* pEnumerator;
SESSION_LISTENER = NULL;
SESSION = NULL;
res = CoInitialize(NULL);
if(res != S_OK && res != S_FALSE)
return false;
res = CoGetClassObject(CLSID_CustomAudioFactory, CLSCTX_ALL, NULL, __uuidof(IClassFactory), (void**)&pFactory);
if(res != S_OK) goto Exit;
res = pFactory->CreateInstance(NULL, CLSID_CustomAudioNotifications, (void**)&SESSION_LISTENER);
if(res != S_OK) goto Exit;
res = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
if(res != S_OK) goto Exit;
res = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDevice);
if(res != S_OK) goto Exit;
res = pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**)&SESSION);
if(res != S_OK) goto Exit;
res = SESSION->RegisterSessionNotification(SESSION_LISTENER);
if(res != S_OK) goto Exit;
success = true;
Exit:
SAFE_RELEASE(pFactory);
SAFE_RELEASE(pEnumerator);
SAFE_RELEASE(pDevice);
if(!success)
{
SAFE_RELEASE(SESSION_LISTENER);
SAFE_RELEASE(SESSION);
}
CustomAudioNotifications declaration:
class CustomAudioNotifications : public IAudioSessionNotification
{
public:
//Constructors
CustomAudioNotifications() { InterlockedIncrement(&g_notifyCount); m_listener = NULL; }
~CustomAudioNotifications() { InterlockedDecrement(&g_notifyCount); SAFE_RELEASE(m_listener); }
//IUnknown interface
HRESULT __stdcall QueryInterface(
REFIID riid ,
void **ppObj);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
//Notification
HRESULT __stdcall OnSessionCreated(IAudioSessionControl *NewSession);
private:
LONG m_nRefCount;
};
OnSessionCreated just posts a message to a window whenever a session is created for the time being; which never happens. Just in case my assumptions are totally off base, I'm expecting a notification whenever an application that has yet to play audio starts to do so; so launching VLC with a video file should immediately result in a notice, while visiting Pandora via a web browser would also trigger such a notice.
Debugging shows all returned values are S_OK.
My COM experience is pretty limitted, so pointing out general "WTFs?" would also be appreciated.

That's a TON more work than you need to do.
You just need to write a class that derives from IAudioSessionNotifications - you don't need to actually write a whole COM object and register it.
You should also use the eConsole role instead of the eMultimedia role. It doesn't effectively matter (if you have only one audio device) but it's more correct.
The destructor for the CustomAudioNotification class should be private - that way you prevent accidental destruction. So I'd write:
CustomAudioNotification
*customNotification = new CustomAudioNotification();
SESSION->RegisterSessionNotification(customNotification);
I'm also assuming that you've initialized COM before your code snippet.
UPDATED: Kevin sent me his application and there are a couple of other issues with his application that are more fundamental (I'm working to get the documentation for the APIs improve to prevent any confusion in the future)
The first is that his application hasn't retrieved the current list of sessions. This is one of the really subtle things about the session enumeration APIs. In order to prevent a race condition that can occur when a session notification arrives while the application using the session APIs is starting up, the session enumeration API discards new session notifications until the application has first retrieved the list of existing sessions.
The expected usage pattern is:
Application activates a session manager2.
Application registers for session notifications.
Application retrieves the current list of sessions for the endpoint and stores the session control objects into a list (don't forget to addref the session).
When a new session is created, the application takes a reference to the newly created session control object and inserts it into the list if it's not already present. Note that the session control object passed into the notification will be destroyed when the session notification returns - if you call GetSessionEnumerator at this point it will probably NOT hold the newly created session (it might, it all depends on timing).
The application manages the lifetime of the session based on its own criteria - as long as the application has a reference to the session control the session control object will be valid. There is no expiration mechanism for audio session control objects.
In addition, the session APIs require that the MTA be initialized - this is unfortunate but because we create COM objects (which implement IAudioSessionControl) on a worker thread the API requires that the MTA be created before the notification is received.

Related

Text Services Framework AdviseSink failed for global compartment if application runs as SYSTEM user

When I try to advise a ITfCompartmentEventSink for a global compartment in a process that has started as a SYSTEM user, the AdviseSink fails with E_FAIL. I tried with impersonating as normal user, but it doesn't seem to help.
And the compartment GetValue will succeed, but SetValue will also fail with E_FAIL.
However when the process is started as normal user, the AdviseSink works correctly.
The AdviseSink in following sample code also fails for SYSTEM user.
Windows-classic-samples\Samples\Win7Samples\winui\tsf\tsfcompart\monitor.cpp
So do I have to somehow set a security descriptor or initialize COM library with a particular option to make this work?
Thanks.
HRESULT CCompartmentMonitor::Initialize( const GUID *pguidCompartment,
PCOMPARTMENTMONITORPROC pCallback,
LPARAM lParam)
{
if(!IsEqualGUID(m_guidCompartment, GUID_NULL))
{
//Initialize() has already been called
return E_UNEXPECTED;
}
m_guidCompartment = *pguidCompartment;
m_pCallback = pCallback;
m_lParam = lParam;
HRESULT hr;
ITfThreadMgr *pThreadMgr;
//create a thread manager object
hr = CoCreateInstance(CLSID_TF_ThreadMgr,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITfThreadMgr,
(void**)&pThreadMgr);
if(SUCCEEDED(hr))
{
ITfCompartmentMgr *pCompMgr;
//get the global compartment manager
hr = pThreadMgr->GetGlobalCompartment(&pCompMgr);
if(SUCCEEDED(hr))
{
//get the Speech UI compartment
hr = pCompMgr->GetCompartment(m_guidCompartment,
&m_pCompartment);
if(SUCCEEDED(hr))
{
ITfSource *pSource;
//install the advise sink
hr = m_pCompartment->QueryInterface(IID_ITfSource,
(LPVOID*)&pSource);
if(SUCCEEDED(hr))
{
hr = pSource->AdviseSink(IID_ITfCompartmentEventSink,
(ITfCompartmentEventSink*)this,
&m_dwCookie);
// AdviseSink fails with E_FAIL
}
//if something went wrong, release the member interface
if(FAILED(hr))
{
m_pCompartment->Release();
m_pCompartment = NULL;
}
}
//release the compartment manager
pCompMgr->Release();
}
//release the thread manager
pThreadMgr->Release();
}
return hr;
}

DLL Code injection to third party process using QueueUserAPC

I want to inject my dll in to 64 bit application and I have tried the logic explained in the given link using QueueUserAPC. I am getting success message for every API but when I see in ProcessExplorer I am not able to see my dll in the process.
Below is my code :
bool FindProcess(PCWSTR exeName, DWORD& pid, vector<DWORD>& tids) {
auto hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return false;
pid = 0;
PROCESSENTRY32 pe = { sizeof(pe) };
if (::Process32First(hSnapshot, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, exeName) == 0) {
pid = pe.th32ProcessID;
THREADENTRY32 te = { sizeof(te) };
if (::Thread32First(hSnapshot, &te)) {
do {
if (te.th32OwnerProcessID == pid) {
tids.push_back(te.th32ThreadID);
}
} while (::Thread32Next(hSnapshot, &te));
}
break;
}
} while (::Process32Next(hSnapshot, &pe));
}
::CloseHandle(hSnapshot);
return pid > 0 && !tids.empty();}
void main(){
DWORD pid;
vector<DWORD> tids;
if (FindProcess(L"DataGrid.exe", pid, tids))
{
printf("OpenProcess\n");
HANDLE hProcess = ::OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, pid);
printf("VirtualAllocEx\n");
auto p = ::VirtualAllocEx(hProcess, nullptr, 1 << 12, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
wchar_t buffer[] = L"C:\\Users\\sbhuma\\Documents\\Visual Studio 2015\\Projects\\GalaxyHook\\Debug\\GalaxyHook.dll";
printf("WriteProcessMemory\n");
::WriteProcessMemory(hProcess, p, buffer, sizeof(buffer), nullptr);
for (const auto& tid : tids)
{
printf("OpenThread\n");
HANDLE hThread = ::OpenThread(THREAD_SET_CONTEXT, FALSE, tid);
if (hThread)
{
printf("GetProcAddress\n");
DWORD word = ::QueueUserAPC((PAPCFUNC)::GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryW"), hThread, (ULONG_PTR)p);
if (word)
{
printf("insdie if\n");
}
printf("End of IF\n");
}
}
printf("VirtualFreeEx\n");
::VirtualFreeEx(hProcess, p, 0, MEM_RELEASE | MEM_DECOMMIT);
}}
Any help related to inject the dll in to 64 bit application is helpful as I am new to this topic.
Regards,
Sowmya.
First of all, ensure you’re building your injector app as 64 bit.
One possible reason is you’re releasing the buffer too early. QueueUserAPC doesn’t wait; it enqueues the call and returns immediately. Can be your injector process ends running that for loop, calls VirtualFreeEx, then your target process receives the APC, tries to load your DLL but the name buffer is already released by then, so LoadLibrary fails. To verify, comment out the call to VirtualFreeEx. If your DLL will load OK, one way to fix the memory leak is use a named event, CreateEvent in injector app before any calls to QueueUserAPC(), OpenEvent, SetEvent and CloseHandle in DllMain(DLL_PROCESS_ATTACH) of the DLL you’re injecting, WaitForSingleObject in injector app before VirtualFreeEx, I recommend using a timeout for the wait, CloseHandle at the end. As a side effect, your injector app will be able to find out, and report somewhere, whether the injection was successful.
Another possible reason is your target app never enters alertable state. Not all apps use APC, there’re multiple alternative methods to implement asynchronous stuff in Windows. So, not all apps ever call these SleepEx / WaitForMultipleObjectsEx functions. Such app will never receive that APC. If that’s the case, you should use another method of DLL injection. DataGrid.exe name hints your target app is probably a GUI app. You can EnumWindows or FindWindow to find its top-level window, GetWindowThreadProcessId to get thread ID who owns that window, SetWindowsHookEx to inject your DLL into the target process.

Testing Distributed Object Connections in OSX with Objective C

On Mac OSX with Objective C and Distributed Objects (IPC API), how can a server determine if the client has closed down their application and thus the NSConnection pointer has been broken? I mean, I might be in a while loop, reading a lot of data to send back from the server to the client, and I can test for stuff if I just knew what the trick was to determine if the client has suddenly disconnected.
Add this to your server's vended object class and then check it when doing something in any kind of loop for long periods of time. Replace "servicetest" phrase in the code below with the name of your actual process. You can run your client app and then check ps -ef at command line to see what your process is, if uncertain. It'll be the one with DerivedData if running temporarily in XCode.
A little warning, though. This routine is kind of heavy and you shouldn't call it with every loop iteration. Instead, check like every 500 iterations of your while loop you might be in, for instance. You could also have a background timer that runs and checks. Or you could use rand to determine 1 in 5 odds and run it then.
Note that this routine only runs properly with Distributed Objects on the same system, as in application to application IPC, rather than Distributed Objects over a LAN.
So, when I detected that a client suddenly disconnected, I do a break on my while loop (where I was reading slow process data) and then did a pclose() on that process because there was no sense running it anymore.
/* requires these includes
#include <libproc.h>
#include <sys/sysctl.h>
#include <string>
*/
- (bool)_clientSuddenlyDisconnected;
{
int numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
pid_t pids[1024];
bzero(pids, 1024);
proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
for (int i = 0; i < numberOfProcesses; ++i) {
if (pids[i] == 0) { continue; }
char pathBuffer[PROC_PIDPATHINFO_MAXSIZE];
bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE);
proc_pidpath(pids[i], pathBuffer, sizeof(pathBuffer));
if (strlen(pathBuffer) > 0) {
std::string sTest(pathBuffer);
sTest = "/" + sTest + "$$$";
if (sTest.find("/servicetest$$$") != std::string::npos) {
return false;
}
}
}
return true;
}

Reading data from an HID device from userspace in OSX [duplicate]

I am attempting to communicate with a rather specific USB device and developing both Windows and Mac code to do so.
The device is a USB device with a HID interface (class 3) with two endpoints, an interrupt input and an interrupt output. The nature of the device is such that data is sent out from the device on the input endpoint only when data is requested from the host: the host sends it data which the device responds to on its input interrupt endpoint. Getting data to the device (a write) is much more simple...
The code for Windows is rather straight-forward: I get a handle to the device and then call either ReadFile or WriteFile. Apparently much of the underlying asynchronous behavior is abstracted out. It appears to work fine.
On Mac, however, it is a bit stickier. I have tried a number of things, none which have been fully successful, but here are the two things which seemed most promising...
1.) Attempt to get access to the device (as USB) via IOUSBInterfaceInterface, iterate through the endpoints to determine the input and output endpoints, and (hopefully) use ReadPipe and WritePipe to communicate. Unfortunately I am unable to open the interface once I have it, with the return value (kIOReturnExclusiveAccess) noting that something already has the device open exclusively. I have tried using IOUSBinterfaceInterface183, so that I could call USBInterfaceOpenSeize, but that results in the same return error value.
--- update 7/30/2010 ---
Apparently, the Apple IOUSBHIDDriver matches early to the device and this is what likely is preventing opening the IOUSBInterfaceInterface. From some digging about it seems that the common way to prevent the IOUSBHIDDriver from matching is to write a code-less kext (kernel extension) with a higher probe score. This would match early, preventing the IOUSBHIDDriver from opening the device, and should, in theory, permit me to open the interface and to write and read to endpoints directly. This is OK, but I would much prefer not having to install something additional on the user machine. If anyone knows of a solid alternative I would be thankful for the information.
2.) Open the device as an IOHIDDeviceInterface122 (or later). To read, I set up an async port, event source and callback method to be called when data is ready - when data is sent from the device on the input interrupt endpoint. However, to write the data — that the device needs — to initialize a response I can't find a way. I'm stumped. setReport typically writes to the control endpoint, plus I need a write that does not expect any direct response, no blocking.
I have looked around online and have tried many things, but none of them is giving me success. Any advice? I can not use much of the Apple HIDManager code since much of that is 10.5+ and my application must work on 10.4 as well.
I have now a working Mac driver to a USB device that requires communication through interrupt endpoints. Here is how I did it:
Ultimately the method that worked well for me was option 1 (noted above). As noted, I was having issues opening the COM-style IOUSBInterfaceInterface to the device. It became clear over time that this was due to the HIDManager capturing the device. I was unable to wrest control of the device from the HIDManager once it was captured (not even the USBInterfaceOpenSeize call or the USBDeviceOpenSeize calls would work).
To take control of the device I needed to grab it before the HIDManager. The solution to this was to write a codeless kext (kernel extension). A kext is essentially a bundle that sits in System/Library/Extensions that contains (usually) a plist (property list) and (occasionally) a kernel-level driver, among other items. In my case I wanted only the plist, which would give the instructions to the kernel on what devices it matches. If the data gives a higher probe score than the HIDManager then I could essentially capture the device and use a user-space driver to communicate with it.
The kext plist written, with some project-specific details modified, is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.iokit.IOUSBFamily</key>
<string>1.8</string>
<key>com.apple.kernel.libkern</key>
<string>6.0</string>
</dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleGetInfoString</key>
<string>Demi USB Device</string>
<key>CFBundleIdentifier</key>
<string>com.demiart.mydevice</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Demi USB Device</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>IOKitPersonalities</key>
<dict>
<key>Device Driver</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.apple.kernel.iokit</string>
<key>IOClass</key>
<string>IOService</string>
<key>IOProviderClass</key>
<string>IOUSBInterface</string>
<key>idProduct</key>
<integer>12345</integer>
<key>idVendor</key>
<integer>67890</integer>
<key>bConfigurationValue</key>
<integer>1</integer>
<key>bInterfaceNumber</key>
<integer>0</integer>
</dict>
</dict>
<key>OSBundleRequired</key>
<string>Local-Root</string>
</dict>
</plist>
The idVendor and idProduct values give the kext specificity and increase its probe score sufficiently.
In order to use the kext, the following things need to be done (which my installer will do for clients):
Change the owner to root:wheel (sudo chown root:wheel DemiUSBDevice.kext)
Copy the kext to Extensions (sudo cp DemiUSBDevice.kext /System/Library/Extensions)
Call the kextload utility to load the kext for immediate use without restart (sudo kextload -vt /System/Library/Extensions/DemiUSBDevice.kext)
Touch the Extensions folder so that the next restart will force a cache rebuild (sudo touch /System/Library/Extensions)
At this point the system should use the kext to keep the HIDManager from capturing my device. Now, what to do with it? How to write to and read from it?
Following are some simplified snippets of my code, minus any error handling, that illustrate the solution. Before being able to do anything with the device, the application needs to know when the device attaches (and detaches). Note that this is merely for purposes of illustration — some of the variables are class-level, some are global, etc. Here is the initialization code that sets the attach/detach events up:
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#include <mach/mach.h>
#define DEMI_VENDOR_ID 12345
#define DEMI_PRODUCT_ID 67890
void DemiUSBDriver::initialize(void)
{
IOReturn result;
Int32 vendor_id = DEMI_VENDOR_ID;
Int32 product_id = DEMI_PRODUCT_ID;
mach_port_t master_port;
CFMutableDictionaryRef matching_dict;
IONotificationPortRef notify_port;
CFRunLoopSourceRef run_loop_source;
//create a master port
result = IOMasterPort(bootstrap_port, &master_port);
//set up a matching dictionary for the device
matching_dict = IOServiceMatching(kIOUSBDeviceClassName);
//add matching parameters
CFDictionarySetValue(matching_dict, CFSTR(kUSBVendorID),
CFNumberCreate(kCFAllocatorDefault, kCFNumberInt32Type, &vendor_id));
CFDictionarySetValue(matching_dict, CFSTR(kUSBProductID),
CFNumberCreate(kCFAllocatorDefault, kCFNumberInt32Type, &product_id));
//create the notification port and event source
notify_port = IONotificationPortCreate(master_port);
run_loop_source = IONotificationPortGetRunLoopSource(notify_port);
CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source,
kCFRunLoopDefaultMode);
//add an additional reference for a secondary event
// - each consumes a reference...
matching_dict = (CFMutableDictionaryRef)CFRetain(matching_dict);
//add a notification callback for detach event
//NOTE: removed_iter is a io_iterator_t, declared elsewhere
result = IOServiceAddMatchingNotification(notify_port,
kIOTerminatedNotification, matching_dict, device_detach_callback,
NULL, &removed_iter);
//call the callback to 'arm' the notification
device_detach_callback(NULL, removed_iter);
//add a notification callback for attach event
//NOTE: added_iter is a io_iterator_t, declared elsewhere
result = IOServiceAddMatchingNotification(notify_port,
kIOFirstMatchNotification, matching_dict, device_attach_callback,
NULL, &g_added_iter);
if (result)
{
throw Exception("Unable to add attach notification callback.");
}
//call the callback to 'arm' the notification
device_attach_callback(NULL, added_iter);
//'pump' the run loop to handle any previously added devices
service();
}
There are two methods that are used as callbacks in this initialization code: device_detach_callback and device_attach_callback (both declared at static methods). device_detach_callback is straightforward:
//implementation
void DemiUSBDevice::device_detach_callback(void* context, io_iterator_t iterator)
{
IOReturn result;
io_service_t obj;
while ((obj = IOIteratorNext(iterator)))
{
//close all open resources associated with this service/device...
//release the service
result = IOObjectRelease(obj);
}
}
device_attach_callback is where most of the magic happens. In my code I have this broken into multiple methods, but here I'll present it as a big monolithic method...:
void DemiUSBDevice::device_attach_callback(void * context,
io_iterator_t iterator)
{
IOReturn result;
io_service_t usb_service;
IOCFPlugInInterface** plugin;
HRESULT hres;
SInt32 score;
UInt16 vendor;
UInt16 product;
IOUSBFindInterfaceRequest request;
io_iterator_t intf_iterator;
io_service_t usb_interface;
UInt8 interface_endpoint_count = 0;
UInt8 pipe_ref = 0xff;
UInt8 direction;
UInt8 number;
UInt8 transfer_type;
UInt16 max_packet_size;
UInt8 interval;
CFRunLoopSourceRef m_event_source;
CFRunLoopSourceRef compl_event_source;
IOUSBDeviceInterface245** dev = NULL;
IOUSBInterfaceInterface245** intf = NULL;
while ((usb_service = IOIteratorNext(iterator)))
{
//create the intermediate plugin
result = IOCreatePlugInInterfaceForService(usb_service,
kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin,
&score);
//get the device interface
hres = (*plugin)->QueryInterface(plugin,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), (void**)&dev);
//release the plugin - no further need for it
IODestroyPlugInInterface(plugin);
//double check ids for correctness
result = (*dev)->GetDeviceVendor(dev, &vendor);
result = (*dev)->GetDeviceProduct(dev, &product);
if ((vendor != DEMI_VENDOR_ID) || (product != DEMI_PRODUCT_ID))
{
continue;
}
//set up interface find request
request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
request.bAlternateSetting = kIOUSBFindInterfaceDontCare;
result = (*dev)->CreateInterfaceIterator(dev, &request, &intf_iterator);
while ((usb_interface = IOIteratorNext(intf_iterator)))
{
//create intermediate plugin
result = IOCreatePlugInInterfaceForService(usb_interface,
kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin,
&score);
//release the usb interface - not needed
result = IOObjectRelease(usb_interface);
//get the general interface interface
hres = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(
kIOUSBInterfaceInterfaceID245), (void**)&intf);
//release the plugin interface
IODestroyPlugInInterface(plugin);
//attempt to open the interface
result = (*intf)->USBInterfaceOpen(intf);
//check that the interrupt endpoints are available on this interface
//calling 0xff invalid...
m_input_pipe = 0xff; //UInt8, pipe from device to Mac
m_output_pipe = 0xff; //UInt8, pipe from Mac to device
result = (*intf)->GetNumEndpoints(intf, &interface_endpoint_count);
if (!result)
{
//check endpoints for direction, type, etc.
//note that pipe_ref == 0 is the control endpoint (we don't want it)
for (pipe_ref = 1; pipe_ref <= interface_endpoint_count; pipe_ref++)
{
result = (*intf)->GetPipeProperties(intf, pipe_ref, &direction,
&number, &transfer_type, &max_packet_size, &interval);
if (result)
{
break;
}
if (transfer_type == kUSBInterrupt)
{
if (direction == kUSBIn)
{
m_input_pipe = pipe_ref;
}
else if (direction == kUSBOut)
{
m_output_pipe = pipe_ref;
}
}
}
}
//set up async completion notifications
result = (*m_intf)->CreateInterfaceAsyncEventSource(m_intf,
&compl_event_source);
CFRunLoopAddSource(CFRunLoopGetCurrent(), compl_event_source,
kCFRunLoopDefaultMode);
break;
}
break;
}
}
At this point we should have the numbers of the interrupt endpoints and an open IOUSBInterfaceInterface to the device. An asynchronous writing of data can be done by calling something like:
result = (intf)->WritePipeAsync(intf, m_output_pipe,
data, OUTPUT_DATA_BUF_SZ, device_write_completion,
NULL);
where data is a char buffer of data to write, the final parameter is an optional context object to pass into the callback, and device_write_completion is a static method with the following general form:
void DemiUSBDevice::device_write_completion(void* context,
IOReturn result, void* arg0)
{
//...
}
reading from the interrupt endpoint is similar:
result = (intf)->ReadPipeAsync(intf, m_input_pipe,
data, INPUT_DATA_BUF_SZ, device_read_completion,
NULL);
where device_read_completion is of the following form:
void DemiUSBDevice::device_read_completion(void* context,
IOReturn result, void* arg0)
{
//...
}
Note that to receive these callbacks the run loop must be running (see this link for more information about the CFRunLoop). One way to achieve this is to call CFRunLoopRun() after calling the async read or write methods at which point the main thread blocks while the run loop runs. After handling your callback you can call CFRunLoopStop(CFRunLoopGetCurrent()) to stop the run loop and hand execution back to the main thread.
Another alternative (which I do in my code) is to pass a context object (named 'request' in the following code sample) into the WritePipeAsync/ReadPipeAsync methods - this object contains a boolean completion flag (named 'is_done' in this example). After calling the read/write method, instead of calling CFRunLoopRun(), something like the following can be executed:
while (!(request->is_done))
{
//run for 1/10 second to handle events
Boolean returnAfterSourceHandled = false;
CFTimeInterval seconds = 0.1;
CFStringRef mode = kCFRunLoopDefaultMode;
CFRunLoopRunInMode(mode, seconds, returnAfterSourceHandled);
}
This has the benefit that if you have other threads that use the run loop you won't prematurely exit should another thread stop the run loop...
I hope that this is helpful to people. I had to pull from many incomplete sources to solve this problem and this required considerable work to get running well...
After reading this question a few times and thinking about it for a bit, I thought of another solution for emulating blocking read behavior, but using the HID manager instead of replacing it.
A blocking read function can register an input callback for the device, register the device on the current run loop, and then block by calling CFRunLoopRun(). The input callback can then copy the report into a shared buffer and call CFRunLoopStop(), which causes CFRunLoopRun() to return, thereby unblocking read(). Then, read() can return the report to the caller.
The first issue I can think of is the case where the device is already scheduled on a run loop. Scheduling and then unscheduling the device in the read function may have adverse affects. But that would only be a problem if the application is trying to use both synchronous and asynchronous calls on the same device.
The second thing that comes to mind is the case where the calling code already has a run loop running (Cocoa and Qt apps for example). But, the documentation for CFRunLoopStop() seems to indicate that nested calls to CFRunLoopRun() are handled properly. So, it should be ok.
Here's a bit of simplified code to go with that. I just implemented something similar in my HID Library and it seems to work, although I haven't tested it extensively.
/* An IN report callback that stops its run loop when called.
This is purely for emulating blocking behavior in the read() method */
static void input_oneshot(void* context,
IOReturn result,
void* deviceRef,
IOHIDReportType type,
uint32_t reportID,
uint8_t* report,
CFIndex length)
{
buffer_type *const buffer = static_cast<HID::buffer_type*>(context);
/* If the report is valid, copy it into the caller's buffer
The Report ID is prepended to the buffer so the caller can identify
the report */
if( buffer )
{
buffer->clear(); // Return an empty buffer on error
if( !result && report && deviceRef )
{
buffer->reserve(length+1);
buffer->push_back(reportID);
buffer->insert(buffer->end(), report, report+length);
}
}
CFRunLoopStop(CFRunLoopGetCurrent());
}
// Block while waiting for an IN interrupt report
bool read(buffer_type& buffer)
{
uint8_t _bufferInput[_lengthInputBuffer];
// Register a callback
IOHIDDeviceRegisterInputReportCallback(deviceRef, _bufferInput, _lengthInputBuffer, input_oneshot, &buffer);
// Schedule the device on the current run loop
IOHIDDeviceScheduleWithRunLoop(deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
// Trap in the run loop until a report is received
CFRunLoopRun();
// The run loop has returned, so unschedule the device
IOHIDDeviceUnscheduleFromRunLoop(deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
if( buffer.size() )
return true;
return false;
}
I ran into this same kIOReturnExclusiveAccess. Instead of fighting it (building a kext, etc). I found the device and used the POSIX api's.
//My funcation was named differently, but I'm using this for continuity..
void DemiUSBDevice::device_attach_callback(void * context,
io_iterator_t iterator)
{
DeviceManager *deviceManager = (__bridge DADeviceManager *)context;
io_registry_entry_t device;
while ((device = IOIteratorNext(iterator))) {
CFTypeRef prop;
prop = IORegistryEntrySearchCFProperty(device,
kIOServicePlane,
CFSTR(kIODialinDeviceKey),
kCFAllocatorDefault,
kIORegistryIterateRecursively);
if(prop){
deviceManager->devPath = (__bridge NSString *)prop;
[deviceManager performSelector:#selector(openDevice)];
}
}
}
once devPath is set you can call open and read/write..
int dfd;
dfd = open([devPath UTF8String], O_RDWR | O_NOCTTY | O_NDELAY);
if (dfd == -1) {
//Could not open the port.
NSLog(#"open_port: Unable to open %#", devPath);
return;
} else {
fcntl(fd, F_SETFL, 0);
}

How to make an Attended call transfer with UCMA

I'm struggling with making a call transfer in a UMCA IVR app I've built. This is not using Lync.
Essentially, I have an established call from an outside user and as part of the IVR application, they select an option to be transferred. This transfer is to a configured outside number (ie: Our Live Operator). What I want to do is transfer the original caller to the outside number, and if a valid transfer is established, I want to terminate the original call. If the transfer isn't established, I want to send control back to the IVR application to handle this gracefully.
My problem is my EndTransferCall doesn't get hit when the transfer is established. I would have expected it to hit, set my AutoResetEvent and return a True, and then in my application I can disconnect the original call. Can somebody tell me what I'm missing here?
_call is an established AudioVideoCall. My application calls the Transfer method
private AutoResetEvent _waitForTransferComplete = new AutoResetEvent(false);
public override bool Transfer(string number, int retries = 3)
{
var success = false;
var attempt = 0;
CallTransferOptions transferOptions = new CallTransferOptions(CallTransferType.Attended);
while ((attempt < retries) && (success == false))
{
try
{
attempt++;
_call.BeginTransfer(number, transferOptions, EndTransferCall, null);
// Wait for the transfer to complete
_waitForTransferComplete.WaitOne();
success = true;
}
catch (Exception)
{
//TODO: Log that the transfer failed
//TODO: Find out what exceptions get thrown and catch the specific ones
}
}
return success;
}
private void EndTransferCall(IAsyncResult ar)
{
try
{
_call.EndTransfer(ar);
}
catch (OperationFailureException opFailEx)
{
Console.WriteLine(opFailEx.ToString());
}
catch (RealTimeException realTimeEx)
{
Console.WriteLine(realTimeEx.ToString());
}
finally
{
_waitForTransferComplete.Set();
}
}
Is the behavior the same if you don't use the _waitForTransferComplete object? You shouldn't need it - it should be fine that the method ends, the event will still be raised. If you're forcing synchronous behavoir in order to fit in with the rest of the application though, try it like this:
_call.EndTransfer(
_call.BeginTransfer (number,transferOptions,null,null)
);
I'm just wondering if the waiting like that causes a problem if running on a single thread or something...