Skip to content

Component discovery and configuration

Gregg Miskelly edited this page Nov 11, 2019 · 10 revisions

The basic unit of extensibility in Concord is a component. A component contains code (compiled dll) and a configuration file which describes when the code should be used. The configuration is in a .vsdconfig file which is shipped with the dll. Vsdconfig files are a binary format which is created with a tool that ships in the Visual Studio SDK (vsdconfigtool.exe). Vsdconfigtool.exe takes an XML input file (.vsdconfigxml) that contains this configuration information, it validates the configuration, and then spits out the .vsdconfig file. Concord dynamically discovers the set of components which are available when it is first initialized in the Visual Studio process. It does so by looking for .vsdconfig files. The recommended way to ship .vsdconfig files is by including it in a VSIX package. The other option is to install it into the debugger’s directory (\Common7\Packages\Debugger). Vsdconfig files do NOT need to be installed on the remote computer – the debugger will automatically transfer them over the wire. However, implementation dlls which must be loaded on the target computer need to be installed next to msvsmon.exe (dlls aren’t automatically transferred).

Configuration files contain the following information:

  • It contains a GUID to identify the component. Use a GUID generation tool (ex: Tools->Create GUID in Visual Studio) to create a new value.
  • It contains the information needed to load the implementation class(es) in the component. If the class is implemented in native code, this is the name of the dll, and the CLSID value. If the class is implemented in managed code, this is the assembly and class name.
  • It contains the component level that the component will load at.
  • It contain the set of interfaces that each class implements, along with filters to indicate the scenarios where the class loads.

The Hello World Sample gives an example of a configuration file, which is a good place to start in better understanding things. The vsdconfigxml format also has an annotated schema file (vsdconfig.xsd) which provides additional documentation (as well as validation when editing the file).

Interface filters

Much of the power of Concord comes from the fact that we can use the same interfaces, but with different implementations depending on the properties of what is being debugged. So for example, we have many different implementations of the API needed to read memory (IDkmMemoryOperation). Any concord component can call this API to read memory without needing to know what component will ultimately implement this interface, and any partner can come along with a new scenario targeting a new type of process and add a new implementation of reading memory.

To make this work, every interface in vsdconfig must declare a filter that indicates when it should be called. For example, here is how the built in Win32 BDM declares that it supports IDkmMemoryOperation:

<InterfaceGroup>
  <Filter>
    <BaseDebugMonitorId RequiredValue="DkmBaseDebugMonitorId.WindowsProcess"/>
  </Filter>

  <Interface Name="IDkmMemoryOperation"/>

If you look at the definition of any interface in vsdebugeng.h, it will indicate what types of filters are supported. All filtering is done based on properties of the first parameter to a Dkm interface.

Method Chaining

For situations where its not possible to select an implementation entirely through the 'Filter' elements in the vsdconfigxml, its also possible for an implementation to 'chain' to the next implementation. Here's an example:

    class Example: IDkmExample
    {
        string IDkmExample.ExampleMethod(DkmExample exampleObject, int argument)
        {
            if (argument == 0)
            {
                // We handle this case
                return "zero";
            }
            else
            {
                // chain to the default implementation for everything else
                return exampleObject.ExampleMethod(argument);
            }
        }
    }

In the above example, the component handles the case where argument is 0. Otherwise, we want the Dispatcher to find/call the next implementer of the interface. To do this, we call the analogous method on the first parameter to the interface method, which is always a dispatcher object. It works this way because the Dispatcher calls into components through an interface and it implements all methods on Dkm objects. In the example, the first parameter and dispatcher method is on the fake dispatcher class DkmExample. The remaining arguments are then passed into the call. In this case, the only remaining argument is argument.

Clone this wiki locally