CLR Inside Out

Introduction to COM Interop

Thottam R. Sriram

Code download available at: CLR Inside Out 2007_01.exe(1480 KB)

Contents

A Simple ATL COM Server
Creating a COM Client
Converting a COM DLL Using Tlbimp
Writing a Managed Client
Marshalling a Structure and a String
Using P/Invoke from Managed Code
Calling Managed Code Using Interfaces
Debugging Managed and Unmanaged Code
Wrap-Up

COM is a wonderful technology. One aspect of the common language runtime (CLR) that makes it an extremely powerful platform is that it allows seamless interactions between Microsoft® .NET applications and unmanaged COM components. However, when I searched the Web, I found few working samples demonstrating the very basic concepts of COM interop. The purpose of this column is to illustrate those basic concepts in order to provide solid working examples that can jump-start users in this technology.

I'll start off with a simple Active Template Library (ATL) COM server and try accessing methods in this server using an unmanaged COM client, then do the same thing with a managed client. I'll walk through the various DLLs to illustrate the translation from unmanaged to managed, and I'll show how to access an exported method in an unmanaged DLL using P/Invoke. The toughest part of this is to figure out marshaling of complex structures, which I don't cover exhaustively in this column-it would be a complete column (or book) on its own. I'll show you how unmanaged code can call back into managed code using interfaces. (You could do this with delegates as well, but I won't cover that in this column.)

Finally, I'll discuss debugging your COM interop project using public symbols. This will give you very basic introduction to WinDbg.exe, unmanaged debugging, and managed debugging using SOS. I'll demonstrate the stack from managed to unmanaged as calls are made in either direction.

A Simple ATL COM Server

Let's start by writing a simple COM server. The client will host the server in-process and execute methods in the server.

To make development easy, I'll use ATL COM. Create a directory called COMInteropSample, then create a new Visual C++® ATL project and give it a name such as MSDNCOMServer. Unselect the Create a New Directory option, but use the defaults for the rest.

Now add a new Class/Interface to the solution. Open Solution Explorer if you don't already see it. Right-click on MSDNCOMServer and select Add | Class. Select ATL and ATL Simple Object and then click Add. Enter the short name of the class as MyCOMServer, then use the defaults for the rest. Click Finish to add the class.

Add a new method to the interface created for you. Go to Class View, right-click the IMyCOMServer interface, and select Add | Add Method. Give the method a name such as MyCOMServerMethod and click Finish.

Now implement the method in the class. Select MyCOMServer.cpp and view the source. You'll see the method that was added by the wizard in the interface in the cpp file. In the TODO section add the following code:

wprintf_s(L"Inside MyCOMServerMethod");

Compile the code to generate the server DLL.

You have now completed your ATL COM server, which implements the interface IMyCOMServer in the class CMyCOMServer and writes a message to the default output stream. The GUID for the interface is defined in the IDL file as an attribute on the interface definition as shown in Figure 1.

Figure 1 IMyCOMServer Definition

[
    object,
    uuid(45FA03A3-FD24-41EB-921C-15204CAF68AE),
    nonextensible,
    helpstring("IMyCOMServer Interface"),
    pointer_default(unique)
]
interface IMyCOMServer : IUnknown {
    [helpstring("method MyCOMServerMethod")] 
    HRESULT(MyCOMServerMethod)( void);
};

[
    uuid(3BA5DF7B-F8EF-4EDE-A7B5-5E7D13764ACC),
    version(1.0),
    helpstring("MSDNCOMServer 1.0 Type Library")
]
library MSDNCOMServerLib
{
    importlib("stdole2.tlb");
    [
        uuid(2D6D2821-A7FB-4F99-A61A-2286A47CD4D1),
        helpstring("MyCOMServer Class")
    ]
    coclass MyCOMServer
    {
        [default] interface IMyCOMServer;
    };
};

Creating a COM Client

Let's try to access the COM server now using a native (unmanaged) client. Again, for simplicity, we'll use Visual Studio® and its wizards. Create a new, empty Visual C++ project. Select Source Files from Solution Explorer and add a new C++ file. Give it a name such as COMClient.cpp, then paste the code from Figure 2 into the file.

Figure 2 COMClient.cpp

#include "MSDNCOMServer.h"
#include "wchar.h"
#include "MSDNCOMServer_i.c"

void main()
{
    // Initialize COM
    IMyCOMServer *pUnk = NULL;
    CoInitialize(NULL);

    // Create the com object
    HRESULT hr = CoCreateInstance(CLSID_MyCOMServer, 
        NULL, CLSCTX_ALL, IID_IMyCOMServer, (void **)&pUnk);
    if(S_OK != hr)
    {
        wprintf(L"Error creating COM object ... %d", hr);
        return;
    }

    // Call a method on the COM object
    pUnk->MyCOMServerMethod();

    // Free resources and uninitialize COM
    pUnk->Release();
    CoUninitialize();
    return;
}

Add the header files from the server (MSDNCOMServer.h and MSDNCOMServer_i.c), then compile and run the client. You should see "Inside MyCOMServerMethod" printed to the console. This message is coming from the COM server.

Hurray! You've created a COM server and compiled it to a DLL, created a COM client, and called the COM server methods from the client.

Converting a COM DLL Using Tlbimp

Now that we have a COM DLL, let's see how to access it from a managed client. Open a Visual Studio command prompt and go to the directory where you created the COM DLL. Now run the following command:

tlbimp MSDNCOMServer.dll

Tlbimp.exe is the Type Library Importer tool included with the .NET Framework SDK. This command outputs a managed DLL called MSDNCOMServerLIB.dll that serves as a managed wrapper for the unmanaged COM DLL.

Now that we have the unmanaged client running, let's look closely into the original unmanaged COM DLL and the managed DLL generated from Tlbimp.exe. Figure 3 shows a dump from Oleview of the original COM DLL.

Figure 3 Oleview Dump from MSDNCOMServer.dll

// Generated .IDL file (by the OLE/COM Object Viewer)
// typelib filename: MSDNCOMServer.dll
[
    uuid(3BA5DF7B-F8EF-4EDE-A7B5-5E7D13764ACC),
    version(1.0),
    helpstring("MSDNCOMServer 1.0 Type Library"),
    custom(DE77BA64-517C-11D1-A2DA-0000F8773CE9, 100663662),
    custom(DE77BA63-517C-11D1-A2DA-0000F8773CE9, 1158351043),
    custom(DE77BA65-517C-11D1-A2DA-0000F8773CE9, Created by MIDL version 
           6.00.0366 at Fri Sep 15 13:10:42 2006)
]
library MSDNCOMServerLib
{
    // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface IMyCOMServer;

    [
      uuid(2D6D2821-A7FB-4F99-A61A-2286A47CD4D1),
      helpstring("MyCOMServer Class")
    ]
    coclass MyCOMServer {
        [default] interface IMyCOMServer;
    };

    [
      odl,
      uuid(45FA03A3-FD24-41EB-921C-15204CAF68AE),
      helpstring("IMyCOMServer Interface"),
      nonextensible
    ]
    interface IMyCOMServer : IUnknown {
        [helpstring("method MyCOMServerMethod")]
        HRESULT_stdcall MyCOMServerMethod();
    };

};

If you use a .NET decompiler, such as Lutz Roeder's .NET Reflector, and view the managed library output by Tlbimp, you'll see something like Figure 4.

Figure 4 Managed MSDNCOMServerLib

namespace MSDNCOMServerLib
{
    [ComImport, Guid("45FA03A3-FD24-41EB-921C-15204CAF68AE"), 
     InterfaceType(1), TypeLibType(0x80)]
    public interface IMyCOMServer
    {
        [MethodImpl(MethodImplOptions.InternalCall, 
         MethodCodeType=MethodCodeType.Runtime)]
        void MyCOMServerMethod();
    }

    [ComImport, CoClass(typeof(MyCOMServerClass)), 
     Guid("45FA03A3-FD24-41EB-921C-15204CAF68AE")]
    public interface MyCOMServer : IMyCOMServer
    {
    }

    [ComImport, TypeLibType(2), 
     Guid("2D6D2821-A7FB-4F99-A61A-2286A47CD4D1"), 
     ClassInterface(0)]
    public class MyCOMServerClass : IMyCOMServer, MyCOMServer
    {
        // Methods
        [MethodImpl(MethodImplOptions.InternalCall, 
         MethodCodeType=MethodCodeType.Runtime)]
        public virtual extern void MyCOMServerMethod();
    }
}

A lot has happened. First, notice that the library MSDNCOMServerLib is translated to namespace MSDNCOMServerLib. Also, the interface IMyCOMServer is retained with the attributed GUID, and the CoClass MyCOMServer is translated to a public interface MyCOMServer that derives from IMyCOMServer, and a class MyCOMServerClass that implements the interfaces IMyCOMServer and MyCOMServer.

Writing a Managed Client

So far you've seen the process of writing a COM server and an unmanaged COM client, as well as the translation that takes place when the unmanaged DLL is converted to a managed DLL using Tlbimp. Let's consume the library exported by Tlbimp in managed code in order to write a managed COM client:

using System;
using MSDNCOMServerLib;

class Test
{
    static void Main()
    {
        MyCOMServerClass comServer = new MyCOMServerClass();
        comServer.MyCOMServerMethod();
    }
}

The managed code is amazingly simple and compact. I saved this code in a file called ManagedClient.cs and compiled it using the following command:

csc ManagedClient.cs /reference:MSDNCOMServerLib.dll

If you run the executable ManagedClient.exe, you should get the same result we got from the unmanaged client.

Marshalling a Structure and a String

Now let's add some parameters to the server-a String and a struct. This will require modifications to both the server itself and to the clients. In order to accomplish this, you need to start by defining the method on the server.

First, edit the IDL file to include the code that is shown in Figure 5. I added a definition for a struct, and I modified the declaration for MyCOMServerMethod to accept two parameters, a string and a struct.

Figure 5 Modifying MyCOMServerMethod

struct MyTestStruct
{
    int nIndex;
    [string] WCHAR* sName;
    int nValue;
};

[
    object,
    uuid(45FA03A3-FD24-41EB-921C-15204CAF68AE),
    nonextensible,
    helpstring("IMyCOMServer Interface"),
    pointer_default(unique)
]
interface IMyCOMServer : IUnknown{
    [helpstring("method MyCOMServerMethod")] 
    HRESULT(MyCOMServerMethod)(
        BSTR *sTestString, struct MyTestStruct *pMyTestStruct);
};

Next, edit the header file on the server and add the following prototype of the method as defined in the IDL.

class ATL_NO_VTABLE CMyCOMServer :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CMyCOMServer, &CLSID_MyCOMServer>,
    public IMyCOMServer
{
public:
    STDMETHOD(MyCOMServerMethod)(
        BSTR *sTestString, struct MyTestStruct *pMyTestStruct);
};

Modify the implementation of the method to reflect the change in the prototype and also to add more code to manipulate the strings that are passed to it. The final code is shown in Figure 6. This completes the changes to the server, which I'll now recompile.

Figure 6 Modified CMyCOMServer

// MyCOMServer.cpp : Implementation of CMyCOMServer
#include "stdafx.h"
#include "MyCOMServer.h"

// CMyCOMServer
STDMETHODIMP CMyCOMServer::MyCOMServerMethod(
    BSTR *sString, struct MyTestStruct *pStruct)
{
    if((NULL == sString) || (NULL == pStruct) || (NULL == 
        pStruct->sName))
    {
        wprintf_s(L"Invalid input\r\n");
        return E_FAIL;
    }
    
    // Print the input to the COM Method
    wprintf_s(
        L"Inside MyCOMServerMethod: Param-1 = %s Param-2 = %s\r\n", 
        *sString, pStruct->sName);

    // Modifying the input string
    SysFreeString(*sString);
    *sString = SysAllocString(L"Changed String from COM Server");
    if(NULL == *sString)
    {
        wprintf_s(L"Allocation failed ...\r\n");
        return E_FAIL;
    }

    // Modifying the string in the structure
    CoTaskMemFree(pStruct->sName); 
    pStruct->sName = reinterpret_cast<WCHAR *>
        (CoTaskMemAlloc(sizeof
             (L"Changed Structure from COM Server")+10));
    if(NULL == pStruct->sName)
    {
        wprintf_s(L"Allocation failed ...\r\n");
        return E_FAIL;
    }

    size_t size = 0;
    size = wcslen(L"Changed Structure from COM Server");
    wcscpy_s(pStruct->sName, size+1, 
        L"Changed Structure from COM Server");

    //Print the changed strings in COM Server
    wprintf_s(
        L"Exiting MyCOMServerMethod: Param-1 = %s Param-2 = %s\r\n", 
        *sString, pStruct->sName);
    return S_OK;
}

In order to access the new COM method on the managed client, I copy over the DLL to the same directory where I have my managed client. I rerun the following command to get the new MSDNCOMServerLib.dll which now has the new method in it.

tlbimp MSDNCOMServer.dll

You can view the new method signature and implementation in Ildasm, another tool included with the .NET Framework SDK. When you view it, you'll notice that the marshaler has added the code shown in Figure 7 to the call.

Figure 7 COM Server Marshaling Code

namespace MSDNCOMServerLib
{
    [ComImport, Guid("45FA03A3-FD24-41EB-921C-15204CAF68AE"), 
     InterfaceType(1), TypeLibType(0x80)]
    public interface IMyCOMServer
    {
        [MethodImpl(MethodImplOptions.InternalCall, 
         MethodCodeType=MethodCodeType.Runtime)]
        void MyCOMServerMethod(
            [MarshalAs(UnmanagedType.BStr)] ref string sTestString, 
            ref MyTestStruct pMyTestStruct);
    }

    [ComImport, CoClass(typeof(MyCOMServerClass)), 
     Guid("45FA03A3-FD24-41EB-921C-15204CAF68AE")]
    public interface MyCOMServer : IMyCOMServer { }

    [ComImport, TypeLibType(2), 
     Guid("2D6D2821-A7FB-4F99-A61A-2286A47CD4D1"), 
     ClassInterface(0)]
    public class MyCOMServerClass : IMyCOMServer, MyCOMServer
    {
        // Methods
        [MethodImpl(MethodImplOptions.InternalCall, 
         MethodCodeType=MethodCodeType.Runtime)]
        public virtual extern void MyCOMServerMethod(
            [MarshalAs(UnmanagedType.BStr)] ref string sTestString, 
            ref MyTestStruct pMyTestStruct);
    }

    [StructLayout(LayoutKind.Sequential, Pack=4)]
    public struct MyTestStruct
    {
        public int nIndex;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string sName;
        public int nValue;
    }
}

The input ref string is marshaled as In, OUT BSTR and the struct remains the same, though the string within the structure is marshaled as LPWStr.

Now that the server is ready along with the managed DLL from the Tlbimp set, I can move on to modifying the managed client to pass a string and a struct and check the returned value to see if it is changed. Figure 8 shows the modified client code.

Figure 8 Passing Strings and Structs

using System;
using MSDNCOMServerLib;
using System.Runtime.InteropServices;

class Test
{
    [STAThread]
    static void Main()
    {
        // Calling COM methods from managed Client
        String param1 = "Original String from Managed Client";

        MyTestStruct param2 = new MyTestStruct();
        param2.sName = "Original Structure from Managed Client";
        
        MyCOMServerClass comServer = new MyCOMServerClass();
        comServer.MyCOMServerMethod(ref param1, ref param2);

        Console.WriteLine("Param1: {0} Param2: {1}\r\n\r\n", 
            param1, param2.sName);
    }
}

The managed client is still sleek and simple. It creates the string and an instance of the struct that was imported from the unmanaged DLL. Now compile the above code the same way you did before, then run the executable. You should see the following output:

Inside MsyCOMServerMethod: Param-1 = Original String from Managed Client 
Param-2 = Original Structure from Managed Client
Exiting MyCOMServerMethod: Param-1 = Changed String from COM Server 
Param-2 = Changed Structure from COM Server
Param1: Changed String from COM Server Param2: Changed Structure from COM Server

The call from Main in managed code goes over to the unmanaged server and prints the input that is passed. The server modifies the strings and prints it on the server. To verify if the modified string is passed back to the client, we print the string in the client as well.

To create an unmanaged function (non-COM) in the same COM DLL, I first have to define the method and implement it in the unmanaged DLL. Select MSDNCOMServer.cpp and paste the code below at the very end of that file.

STDAPI MyNonCOMMethod()
{
    wprintf_s(L"Inside MyNonCOMMethod\r\n");
    return true;
} 

You need to export the method from the DLL in order to be able to access it. Do that by adding an entry to this function in the COMServer.def file:

; COMServer.def : Declares the module parameters.

LIBRARY      "COMServer.DLL"

EXPORTS
    DllCanUnloadNow       PRIVATE
    DllGetClassObject     PRIVATE
    DllRegisterServer     PRIVATE
    DllUnregisterServer   PRIVATE
    MyNonCOMMethod        PRIVATE

Compile the server and generate the new DLL with both the COM method and the exported function in it.

Copy the DLL to the directory that contains the managed client and run Tlbimp on it. You can view the exported function available in the managed DLL using Ildasm.

Using P/Invoke from Managed Code

Next we'll modify the managed client to call the unmanaged function using P/Invoke. To do this, you have to define the method in managed code and then make the call. The managed method declaration mirrors the unmanaged declaration and tells the CLR where it can expect to find the actual implementation. Figure 9 shows the changes to the code.

Figure 9 Using P/Invoke

class Test
{
    [DllImport("MSDNCOMServer.dll")]
    static extern bool MyNonCOMMethod();

    [STAThread]
    static void Main()
    {
        // Calling COM methods from managed Client
        ...

        // Calling unmanaged function using pInvoke
        MyNonCOMMethod();
    }
}

We've added a DllImport statement and a call to MyNonCOMMethod to our original managed client. When you compile and run the client, you should see output like this:

Inside MyCOMServerMethod: Param-1 = Original String from Managed Client 
Param-2 = Original Structure from Managed Client
Exiting MyCOMServerMethod: Param-1 = Changed String from COM Server 
Param-2 = Changed Structure from COM Server
Param1: Changed String from COM Server Param2: Changed Structure from COM Server

Inside MyNonCOMMethod

The call to the MyNonCOMMethod did get executed and prints the correct statement.

Calling Managed Code Using Interfaces

Finally, let's make a managed call from the unmanaged function using reverse P/Invoke. This is not as common a practice, but it's very useful. You can pass a managed interface or a delegate to unmanaged code, and the unmanaged code can invoke the delegate or methods on the interface. Let's get the managed interface going first so it can be invoked from the unmanaged code. To do this, declare the interface in ManagedClient.cs as shown in Figure 10.

Figure 10 Declaring the Managed Interface

using System;
using MSDNCOMServerLib;
using System.Runtime.InteropServices;

[Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44"), 
 InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
public interface IReversePInvoke
{
    void ManagedMethod(ref int val);
}

class ManagedClass : IReversePInvoke
{
    public void ManagedMethod(ref int nVal)
    {
        Console.WriteLine("ManagedMethod: " + nVal);
        nVal = 20;
        Console.WriteLine("ManagedMethod Value changed to: " + nVal);
    }
}

[DllImport("MSDNCOMServer.dll")]
static extern bool MyNonCOMMethod(IReversePInvoke ptr);

[STAThread]
static void Main()
{
    ...
    ManagedClass man = new ManagedClass();
    MyNonCOMMethod(man);
}

In this code I added a declaration for IReversePInvoke, a class that implements the interface, and a modified DLLImport method in the unmanaged DLL to take an interface pointer. I also modified the place where the method is called to pass the ManagedClass instance to MyNonCOMMethod.

You can compile the client now, but it won't run successfully as you haven't yet modified the server to take the interface as a parameter. So run Tlbexp (another tool from the .NET Framework SDK that produces a type library from a managed assembly) on the managed executable to get a .tlb file. Open it in Oleview, copy the prototype of the interface, and paste it into the server IDL file as follows:

[
    odl,
    uuid(7DAC8207-D3AE-4C75-9B67-92801A497D44)
]
interface IReversePInvoke : IUnknown {
    HRESULT _stdcall ManagedMethod([in, out] long* val);
};

Now you have the interface on the server. You have to change the signature of the method and add the call to invoke the managed method on the interface. Here is how we do this:

STDAPI MyNonCOMMethod(IReversePInvoke *ptr)
{
    int param = 10;
    
    wprintf_s(L"Inside MyNonCOMMethod\r\n");
    HRESULT hr = ptr->ManagedMethod(&param);
    wprintf_s(L"Value in MyNonCOMMethod = %d\r\n", param);
    wprintf_s(L"Exiting MyNonCOMMethod\r\n");
    
    return true;
}

Now you have the unmanaged function taking the managed interface and invoking a method on the managed interface. Compile this to a DLL, run Tlbimp on it to get an updated managed interop DLL, and compile the managed code against the managed DLL from Tlbimp. Here's the output you get:

Inside MyCOMServerMethod: Param-1 = Original String from Managed Client
Param-2 = Original Structure from Managed Client
Exiting MyCOMServerMethod: Param-1 = Changed String from COM Server 
Param-2 = Changed Structure from COM Server
Param1: Changed String from COM Server Param2: Changed Structure from 
COM Server

Inside MyNonCOMMethod
ManagedMethod: 10
ManagedMethod Value changed to: 20
Value in MyNonCOMMethod = 20
Exiting MyNonCOMMethod

The call did get back to managed code and printed "10" as expected. The managed method modifies the value to 20 and that is printed in the unmanaged function.

Debugging Managed and Unmanaged Code

Hopefully all of your code has worked successfully. Obviously, though, it's rare to write 100 percent bug-free code. At some point you'll need to know how to debug your code that uses interop.

To successfully debug, make sure your computer has full symbols loaded. Symbols allow a debugger to better understand and interpret what's happening in your code, and Microsoft provides a public server to which debuggers can connect in order to access these symbols for Microsoft-supplied DLLs. To configure the windbg debugger to use this public symbol server, first run windbg:

windbg managedclient.exe

Then use the following command syntax to point it at the symbol server:

.SymFix
.sympath+ https://msdl.microsoft.com/download/symbols
.Reload

Next, you need to tell windbg to set breakpoints for when the mscorwks, COMServerLib, and COMServer DLLs load. This is accomplished using the sxe ld command:

sxe ld mscorwks
sxe ld COMServerLib
sxe ld COMServer

To start the app running under the debugger, type g (for "go") and hit return. During the run of the application, it will break into the debugger due to the loading of the COMServer DLL, at which point we can set another breakpoint on the MyCOMServerMethod (you can then use the bl command to verify that the breakpoint has in fact been set):

bp COMServer!CMyCOMServer::MyCOMServerMethod (wchar_t **, 
    struct MyTestStruct *)

Now that mscorwks has been loaded, we want windbg to load the SOS debugger extensions that allow for much more robust debugger interaction with managed applications:

.loadby sos mscorwks 

To set a breakpoint on the ManagedMethod method that will be called from the COM server through reverse P/Invoke, we use SOS's !bpmd command:

!bpmd ManagedClient ManagedClass.ManagedMethod

With this breakpoint set, we can again use the g command to proceed. At some point, we'll hit the unmanaged breakpoint on MyCOMServerMethod and then we can view the current stack trace using the windbg kL command, as shown in Figure 11 (which also demonstrates using additional windbg commands to examine the values of variables).

Figure 11 Getting a Stack Trace and Examining Memory

0:000> kL
ChildEBP RetAddr  
0012f328 79f21268 MSDNCOMServer!CMyCOMServer::MyCOMServerMethod
0012f3fc 0090a28e mscorwks!CLRToCOMWorker+0x196
0012f438 00f800e5 CLRStub[StubLinkStub]@90a28e
01271c08 79e88f63 ManagedClient!Test.Main()+0x75
0012f490 79e88ee4 mscorwks!CallDescrWorker+0x33
0012f510 79e88e31 mscorwks!CallDescrWorkerWithHandler+0xa3
0012f650 79e88d19 mscorwks!MethodDesc::CallDescr+0x19c
0012f668 79e88cf6 mscorwks!MethodDesc::CallTargetWorker+0x20
0012f67c 79f084b0 mscorwks!MethodDescCallSite::Call_RetArgSlot+0x18
0012f7e0 79f082a9 mscorwks!ClassLoader::RunMain+0x220
0012fa48 79f0817e mscorwks!Assembly::ExecuteMainMethod+0xa6
0012ff18 79f07dc7 mscorwks!SystemDomain::ExecuteMainMethod+0x398
0012ff68 79f05f61 mscorwks!ExecuteEXE+0x59
0012ffb0 79011b5f mscorwks!_CorExeMain+0x11b
0012ffc0 7c816fd7 mscoree!_CorExeMain+0x2c
0012fff0 00000000 KERNEL32!BaseProcessStart+0x23

0:000> dv
           this = 0x00fc2fb8
        sString = 0x0012f364
        pStruct = 0x00195ef8
           size = 0x12f330

0:000> dt sString
Local var @ 0x12f334 Type wchar_t**
0x0012f364 
 -> 0x001a61e4  "Original String from Managed Client"

0:000> dt -r pStruct
Local var @ 0x12f338 Type MyTestStruct*
0x00195ef8 
   +0x000 nIndex   : 0
   +0x004 sName    : 0x001a4f38  "Original Structure from Managed Client"
   +0x008 nValue   : 0

If we continue with g again, we'll break at the call to ManagedMethod, at which point we can use the SOS command !clrstack to examine the managed stack:

0:000> !clrstack
OS Thread Id: 0x87c (0)
ESP       EIP     
0012ef80 00f80168 ManagedClass.ManagedMethod(Int32 ByRef)
0012f148 79f1ef33 [GCFrame: 0012f148] 
0012f2a0 79f1ef33 [ComMethodFrame: 0012f2a0] 
0012f454 79f1ef33 [NDirectMethodFrameSlim: 0012f454] Test.MyNonCOMMethod(IReversePInvoke)
0012f464 00f80119 Test.Main()
0012f69c 79e88f63 [GCFrame: 0012f69c]

Wrap-Up

For additional information, start with PInvoke.net, a community-driven source. Adam Nathan's COM interop tome, .NET and COM: The Complete Interoperability Guide, is back in print (and his blog is at "Adam Nathan's Blog"). General information about debugging, including the WinDbg download and reference, is available, and you can find a reference on the SOS Debugging Extension (SOS.dll) here.

Send your questions and comments to  clrinout@microsoft.com.

Thottam R. Sriram has a Masters Degree in Computer Science from Concordia University, Montreal Canada. He is currently a Program Manager in the CLR team working on COM interop. Thottam worked with the Windows team before joining the CLR team. Thanks to COM interop team members Mason Bendixen, Claudio Caldato, Alessandro Catorcini, Ben Gillis, and Snesha Arumugam for their help with this column.