views:

27

answers:

1

I posted this question on the MSDN forums, but my experience has been a better quality of answer here on Stack Overflow, so I'm posting here as well. As I've posted several times before, I'm working on a browser automation framework, automating Internet Explorer from an external process. My architecture is as follows: I have a server which opens a named pipe that my automation client pushes commands to. The server interprets the commands, and executes them on the IWebBrowser2 object, which I have wrapped in my own C++ class. Everything works fine until I try to sink events on the IE instance. My wrapper class implements IDispEventSimpleImpl, but when I try to sink the events, the browser instance doesn't respond to any communication, either programmatically or via the UI. Here are my main two methods with the most relevance:

void BrowserManager::Start(void)
{
  CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  std::basic_string<TCHAR> pipeName =L"\\\\.\\pipe\\managerpipe";
  HANDLE hPipe = ::CreateNamedPipe(pipeName.c_str(), 
    PIPE_ACCESS_DUPLEX, 
    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 
    PIPE_UNLIMITED_INSTANCES,
    1024,
    1024,
    0,
    NULL);

  if (hPipe == INVALID_HANDLE_VALUE)
  {
    DWORD dwError = ::GetLastError();
  }

  this->m_isRunning = true;

  while (this->m_isRunning)
  {
    BOOL result = ::ConnectNamedPipe(hPipe, NULL);
    std::vector<CHAR> inputBuffer(1024);
    DWORD bytesRead = 0;
    ::ReadFile(hPipe, &inputBuffer[0], 1024, &bytesRead, NULL);

    std::string command = &inputBuffer[0];
    std::string response = DispatchCommand(command);

    std::vector<CHAR> outputBuffer(response.begin(), response.end());
    ::WriteFile(hPipe, &outputBuffer[0], outputBuffer.size(), &bytesRead, NULL);
    ::FlushFileBuffers(hPipe);
    ::DisconnectNamedPipe(hPipe);

    if (strcmp(command.c_str(), "quit\r\n") == 0)
    {
      this->m_isRunning = false;
    }
  }

  ::CloseHandle(hPipe);
  CoUninitialize();
}

std::string BrowserManager::DispatchCommand(std::string command)
{
  std::string response;
  if (strcmp(command.c_str(), "start\r\n") == 0)
  {
    // Launch the browser process using CreateProcess on XP 
    // or IELaunchURL on Vista or higher. This is done on a
    // low-integrity thread so we have the correct integrity.
    DWORD procId = this->m_factory->LaunchBrowserProcess();
    CComPtr<IWebBrowser2> pBrowser(this->m_factory->AttachToBrowser(procId));
    BrowserWrapper wrapper(pBrowser);
    this->m_wrapper = wrapper;
    response = "started";
  }
  else if (strcmp(command.c_str(), "goto\r\n") == 0)
  {
    this->m_wrapper.GoToUrl("http://www.google.com/");
    response = "navigated";
  }
  else if (strcmp(command.c_str(), "quit\r\n") == 0)
  {
    this->m_wrapper.CloseBrowser();
    response = "closed";
  }
  else
  {
    response = "invalid command";
  }

  return response;
}

Interestingly, I prototyped this same mechanism in C# before translating it into unmanaged C++ to make sure what I was attempting would work, since my C++ skills are not at the same level as my C# skills. Needless to say, it works fine in C#, but it is a requirement that this component be written in unmanaged code. I'm sure I'm overlooking something obvious that the .NET Framework abstracts away, but whatever it is, it isn't obvious to me.

To help me learn from my mistake, I'd appreciate a pointer to what it is that the .NET Framework is doing to let this work. In the C# version, I'm using a single thread with blocking I/O on the pipe, just like I (think I) am here. If the posted code snippet isn't enough to point toward a diagnosis, I'm more than happy to provide a full Visual Studio 2008 solution demonstrating the difficulty.

+2  A: 

Your app is becoming a com server by providing event sinks. The com app needs active 'message pump'(s).

If your blocking the message pump while your doing your pipe/dispatch command then it will block IE from calling on your event sink.

C# may work simply for the other hidden windows it has and however you have set up the rest of that app.

Greg Domjan
That must be what's going on here. It looks like the .NET Framework spawns additional threads when you sink the DWebBrowserEvents2 events. This COM interop code must be what handles all of the cross-thread marshalling when running in a single-threaded console application (which normally has no message loop). Now I just need to figure out how to accomplish something similar in my unmanaged C++ code.
JimEvans