2

NOTE: Updated with a solution at the bottom of the question

I'm having some trouble with an application that uses the Lync 2013 SDK. Here is the behavior that I am seeing:

  • If Lync is already running when I start my application, then a call to LyncClient.GetClient() will return a valid client object.
  • If Lync is not running when I start my application, then a call to LyncClient.GetClient() will throw ClientNotFoundException. I can handle the exception and start a timer to ping for the client to appear. Later, when I start Lync, LyncClient.GetClient() will return a valid client object.
  • If Lync exits while my application is running, then I can detect this situation in multiple ways and start a timer to ping for the client to come back.

So far so good, but here's where the problems come in:

  • If Lync goes away while my application is running, then subsequent calls to LyncClient.GetClient() seem to return a valid client object (even though Lync is not running), but attempts to call into this object throw InvalidCastException.
  • Even after Lync is restarted, subsequent calls to LyncClient.GetClient() still return an object that throws InvalidCastException when I try to access it.

The details of the exception are:

Unable to cast COM object of type 'System.__ComObject' to interface type 'Microsoft.Office.Uc.IClient'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{EE9F3E74-AC61-469E-80D6-E22BF4EEFF5C}' failed due to the following error: The RPC server is unavailable. (Exception from HRESULT: 0x800706BA).

I tried the recommendation here: Troubleshooting Lync 2013 SDK development issues. It doesn't seem to make any difference. I continue to get an invalid client object for many minutes after Lync is running and signed-in again.

This doesn't happen every time. The problem seems to pretty consistently occur if Lync is already running when my application starts (i.e. the very first call to LyncClient.GetClient() succeeds.) On the other hand, everything seems to work fine across multiple restarts of Lync if I start Lync after my application is already running (i.e. the first attempt to GetClient() fails.)

Has anyone else seen this before? Any suggestions?


Update with an attempt at unloading the AppDomain

I tried to get the client object with this code, but the behavior is exactly the same:

public class LyncClientProvider
{
    private AppDomain _domain = CreateDomain();

    public LyncClient GetLyncClient()
    {
        if (_domain == null) CreateDomain();
        var client = GetClient();
        if (client != null && client.Capabilities != LyncClientCapabilityTypes.Invalid) return client;

        // Reload AppDomain
        if (_domain != null) AppDomain.Unload(_domain);
        _domain = CreateDomain();
        return GetClient();
    }

    private LyncClient GetClient()
    {
        if (_domain == null) return null;
        return ((InternalProvider)
            _domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
                typeof (InternalProvider).FullName)).GetLyncClient();
    }

    private static AppDomain CreateDomain()
    {
        return AppDomain.CreateDomain("LyncClientCreator", null, new AppDomainSetup
        {
            ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
            ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
        });
    }

    [Serializable]
    private class InternalProvider
    {
        public LyncClient GetLyncClient()
        {
            try
            {
                return LyncClient.GetClient();
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}

Example of a solution using a dedicated thread

Thanks to input from djp, I implemented this simple class to provide a LyncClient, and reconnects are now working properly.

public class LyncClientProvider
{
    private Thread _thread;
    private volatile bool _running;
    private volatile bool _clientRequested;
    private volatile LyncClient _lyncClient;

    private void MakeClientLoop()
    {
        while (_running)
        {
            if (_clientRequested) MakeClient();
            Thread.Sleep(100);
        }
    }

    private void MakeClient()
    {
        try
        {
            _lyncClient = LyncClient.GetClient();
        }
        catch (Exception)
        {
            _lyncClient = null;
        }
        _clientRequested = false;
    }

    public void Start()
    {
        if (_running) return;
        _running = true;
        _thread = new Thread(MakeClientLoop);
        _thread.Start();
    }

    public void Stop()
    {
        if (!_running) return;
        _running = false;
        _thread.Join();
        _clientRequested = false;
    }

    public LyncClient GetLyncClient()
    {
        if (!_running) return null;
        _clientRequested = true;
        while (_clientRequested) Thread.Sleep(100);
        return _lyncClient;
    }
}
danBhentschel
  • 863
  • 7
  • 24
  • `public static LyncClient GetClient` - `static` here confuses me. If implementation of this method caches COM object in lazily initialized static variable, and there's no way to invalidate cached value, then the only way is to create another app domain, create this method in that domain, and unload domain, when Lync was exited and you've catch an exception. Have you compared references, returned by `GetClient`? Are they the same? Is there any method, which allows to reset/re-create client value? – Dennis Jun 05 '15 at 13:11
  • It certainly seems like I am getting a cached object. Printing out the value of `GetHashCode()` prints the same value over and over when I'm getting a bad object. OTOH, I get a unique hash code every time in the scenario where reconnects work correctly. I can't find any way to reset the cache. – danBhentschel Jun 05 '15 at 13:33
  • I have no knowledge about Lync, therefore this is only a guess. If my guess is correct, let me know to convert it into an answer. I believe you should store a unique identity whenever you run a LyncClient.GetClient based on the result. When you catch an InvalidCastException while running LyncClient,GetClient, you should mark the unique identifier as invalid. Whenever the unique identifier got from GetClient changes, reset its validity. If in a certain moment the validity is invalid, then handle it accordingly. – Lajos Arpad Jun 05 '15 at 13:40
  • Thanks, but I think that either you misunderstand me, or I'm misunderstanding your comment. My problem is that once I get into this situation, the return from `GetClient()` **never** changes until I restart the program. It's not an issue of recognizing a valid result. The problem is that the result is **always** invalid, given a certain sequence of events. – danBhentschel Jun 05 '15 at 13:54
  • @danBhentschel: I'm afraid, that unload app domain is your only way. – Dennis Jun 05 '15 at 14:17
  • Thanks again. That was a solution I hadn't considered. I just tried it, (in the edited question above) but surprisingly, the result is the same. If Lync is already running, then I can't reconnect after a disconnect. If it isn't running at startup, then I can reconnect just fine. Did I do something wrong in my AppDomain implementation? I can see it going through the code to unload and recreate the AppDomain. Any other suggestions? I'm considering creating a separate process and using WCF via a named pipe to proxy requests to the LyncClient. That's pretty ugly, though. – danBhentschel Jun 05 '15 at 15:14
  • I'm running the timer to check the lync presence similar to what you are doing. It does take few seconds for `LyncClient` to get the instance but not minutes. Not sure, but it can be machine specific issue. Can try to perform the same operation on some other machine? – Ankit Vijay Jun 06 '15 at 05:41

3 Answers3

1

I had the same issue, the fix for me is to make sure that every call to LyncClient.GetClient() happens on the same thread

djp
  • 636
  • 3
  • 13
  • Thank you very much for taking the time to answer! I actually had shelved this issue for a bit and had fully intended to resurrect the problem when I got to work this morning. Your answer is very timely and also very useful. I implemented a simple `LyncClientProvider` class this morning that ensures that the `LyncClient` is always retrieved by the same thread, and now everything works perfectly. Thanks again. I have accepted your answer. – danBhentschel Jul 10 '15 at 13:12
0

"•If Lync goes away while my application is running, then subsequent calls to LyncClient.GetClient() seem to return a valid client object (even though Lync is not running), but attempts to call into this object throw InvalidCastException."

Can you elaborate what you mean by "If Lync goes away". Do you mean you have signed out of Lync or you have exited the Lync (i.e. no Lync process running)? If you have just signed out of the Lync LyncClient will not throw exception. You can listen to LyncClient.StateChanged event and check for clientStateChangedEventArgs.NewState event.

private void LyncClient_StateChanged(object sender, ClientStateChangedEventArgs e) {
        switch (e.NewState) {
                case ClientState.Invalid:
                case ClientState.ShuttingDown:                  
                    this.IsLyncStarted = false;
                    this.IsLyncSignedIn = false;
                    break;
                case ClientState.SignedOut:
                case ClientState.SigningIn:
                case ClientState.SigningOut  
                    this.IsLyncStarted = true;
                    this.IsLyncSignedIn = false;
                    break;
                case ClientState.SignedIn:
                    this.IsLyncStarted = true;
                    this.IsLyncSignedIn = true;
                    break;
            }
            if (!this.IsLyncStarted || !this.IsLyncSignedIn) {                    
                // Do relevant operation
                // Show error message to user - Lync is not present
            }
    }

When the user exits the Lync - I start the timer similar to your approach and try to Initialize Lync like below:

private void InitializeLync() {
        try {
            if (this.LyncClient == null || this.LyncClient.State == ClientState.Invalid) {
                this.LyncClient = LyncClient.GetClient();

                if (this.LyncClient != null) {
                    this.LyncClient.StateChanged += this.LyncClient_StateChanged;       
                }
            }

        } catch (Exception ex) {
            // Show message to user that Lync is not started
        }
}

This works for me. May be it is machine specific issue.

Ankit Vijay
  • 3,752
  • 4
  • 30
  • 53
-2

I am assuming you are being disconnected from the Lync server. The LyncClient.GetClient() method may still work because you have some lingering variables, but you won't be able to preform any Lync actions with it.

I think looking at the UI suppression stuff would help. https://msdn.microsoft.com/en-us/library/office/hh345230%28v=office.14%29.aspx

jrs
  • 1
  • 1
  • Thanks, but I don't really want to supress the client UI. I very much want the Lync client interface to continue to work properly. My application works in concert with Lync. It's not a replacement. – danBhentschel Jun 05 '15 at 13:34