<< PreviousNovember 2003Next >>

Tuesday 18 November, 2003
#MS Agent: When he calls you, don't call him

One of the many strange things about MS Agent is that it runs in a single-threaded apartment when underneath it's plainly multi-threaded. Pop it and watch it perform little animations all of his own accord. Call a method on it, and it'll return immediately and do the action asynchronously.

You might actually be interested in when those asynchronous actions finish (so you can, say, hide your custom speech balloon when the Agent's stopped speaking), and in a rare display of common sense the Agent will tell you. Your code needs to implement the AgentServerObjects.IAgentNotifySinkEx interface and register the implementation with the Agent.

  public class AgentWrapper : IAgentNotifySinkEx 
  {
    private static IAgentEx srvEx_;
    public static AgentWrapper LoadAgent(string CharacterFile) { ... }

    private int notifyId_;
    private IAgentCharacterEx characterEx_;
    private int speakReqId_;
    // ...
    private AgentWrapper()
    {
      // set up stuff 

      srvEx_.Register(this, out notifyId_);
    }

    public ~AgentWrapper()
    {
      srvEx_.Unregister(notifyId_);
    } 

    public void Say(string thingsToSay)
    {
      characterEx_.Speak(thingsToSay, null, out speakReqId_);
    } 

    // ... more stuff ...

    ////////////////////////////
    // IAgentNotifyEx implementation
    public virtual void RequestStart(Int32 dwRequestID) { }

    public virtual void RequestComplete(Int32 dwRequestID, 
                                        Int32 hrStatus) 
    {
      if(dwRequestID == speakReqId_)
	balloon_.Hide();
    } 

    public virtual void ActivateInputState(Int32 dwCharID, 
                                           Int32 bActivated) { }
    public virtual void ActiveClientChange(Int32 dwCharID, 
                                           Int32 lStatus) { }
    public virtual void AgentPropertyChange() { }
    // ... and so and so on 
  } 
The fact that you register your class with the AgentServer object rather than the AgentCharacter is just one more piece of API strangeness.

Anyway, so you're all registered up and happily receiving callbacks from the Agent. You might even be doing other things as a result. What you absolutely can't do is call a method on the Agent during a callback.

  ...
  public virtual void Click(Int32 dwCharID, Int16 fwKeys, 
                            Int32 x, Int32 y) 
  {
    // x and y are click positions
    if((fwKeys & AgentCharacter.MK_LBUTTON) != 0) 
    {
      int ax, ay;
      characterEx.GetPosition(out ax, out ay);  // throws InvalidCastException
      balloon_.PopupAt(ax, ay);  
    }
  } 
  ...
If you need information about the Agent, its position or size for instance, during a callback then you have to cache it ahead of time.

I suspect that the reason you can't call the Agent is because the callback is made by the multithreaded AgentServer, while the Agent is in a single-threaded apartment. The callback is therefore in a different thread to the Agent, and the marshalling breaks down. The Googling around I've done states that all this cross-apartment stuff should work automagically, but it plainly doesn't.

Update - The right search term combo turned up this bug report: InvalidCastException When You Call a COM Component That Is Marked as STA

[More about MS Agent]

Enter your comment

Name Email
URL
If you have an account, please log in.
No account? Just create one.