实现托管代码调用非托管代码以及非托管代码调用托管代码>COM Interop

代码下载位置: CLRInsideOut2007_01.exe (1480 KB)
Browse the Code Online

  目录 
一个简单的 ATL COM 服务器
创建一个 COM 客户端
使用 Tlbimp 转换 COM DLL
编写一个托管客户端
封送一个结构和一个字符串
在托管代码中使用 P/Invoke
使用接口调用托管代码
调试托管代码和非托管代码
总结

COM 是一种很出色的技术。正是由于公共语言运行库 (CLR) 能够使 Microsoft® .NET 应用程序和非托管 COM 组件之间进行无缝交互,才使得 CLR 成为极其强大的平台。但是我在网络上进行搜索时,几乎找不到能够说明 COM Interop 的最基本概念的有用示例。本专栏的目的是讲解这些基本概念,并提供切实有用的示例,帮助这一技术领域的用户快速入门。

我将以一个简单的活动模板库 (ATL) COM 服务器开始介绍,使用一个非托管 COM 客户端对该服务器尝试不同的访问方法,然后使用托管客户端进行相同的操作。我将逐一介绍各个 DLL,说明从非托管到托管的转换,并且还将说明如何使用 P/Invoke 在非托管 DLL 中访问导出的方法。本文中最难的部分是理解复杂结构的封送,在本专栏中将不对这一点进行详尽的介绍。仅介绍这一知识点就需要开辟一个完整的专栏或写一本书。我将为您展示非托管代码如何使用接口来回调托管代码。(您也可以使用委托达到这一目的,但本专栏不介绍这种方法。)

最后,我将介绍使用公共符号对您的 COM Interop 项目进行调试。这部分将对 WinDbg.exe、非托管调试和使用 SOS 进行的托管调试进行最基本的介绍。我将演示在托管代码中调用非托管代码或在非托管代码中调用托管代码时堆栈的情况。

一个简单的 ATL COM 服务器

我们首先来编写一个简单的 COM 服务器。客户端将承载运行中的服务器,并在服务器中执行方法。

为了使开发变得简单,我将使用 ATL COM。创建名为 COMInteropSample 的目录,然后创建一个新的 Visual C++® ATL 项目并为它命名(假设为 MSDNCOMServer)。取消选择“创建新目录”选项,其他项采用默认值。

现在向解决方案中添加一个新的类/接口。如果您还没有看到它,请打开解决方案资源管理器。右键单击 MSDNCOMServer,然后选择“添加”|“类”。选择“ATL”和“ATL 简单对象”,然后单击“添加”。输入类的简称(假设为 MyCOMServer),然后其他项采用默认值。单击“完成”以添加该类。

向为您创建好的接口添加一个新方法。转到“类”视图,右键单击 IMyCOMServer 接口,然后选择“添加”|“添加方法”。为该方法命名(假设为 MyCOMServerMethod),然后单击“完成”。

现在,我们要在类中实现该方法。选择 MyCOMServer.cpp 并查看源代码。您将在 cpp 文件的接口中看到向导添加的方法。在 TODO 部分中,添加以下代码:

wprintf_s(L"Inside MyCOMServerMethod");
 

编译代码以生成服务器 DLL。

现在,我们已完成了 ATL COM 服务器。它将在类 CMyCOMServer 中实现 IMyCOMServer 接口,并向默认输出流中写入一条消息。接口的 GUID 在 IDL 文件中定义为接口定义的一个属性,如图 1 所示。

  Figure 1 IMyCOMServer 定义

[
    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;
    };
};

创建一个 COM 客户端

我们将尝试使用本机(非托管)客户端访问 COM 服务器。为了简单起见,我们仍然使用 Visual Studio® 及其向导。创建一个空的、新 Visual C++ 项目。从解决方案资源管理器中选择“源文件”,然后添加一个新的 C++ 文件。为它命名(假设为 COMClient.cpp),然后将图 2 中的代码粘贴到该文件中。

  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;
}

从服务器添加头文件(MSDNCOMServer.h 和 MSDNCOMServer_i.c),然后编译并运行客户端。您会看到控制台显示“Inside MyCOMServerMethod”。此消息来自 COM 服务器。

成功了!您已经创建了一个 COM 服务器并将其编译成为一个 DLL,创建了一个 COM 客户端并从客户端调用了 COM 服务器的方法。

使用 Tlbimp 转换 COM DLL

现在有了 COM DLL,让我们来看看如何从一个托管客户端访问它。打开 Visual Studio 命令提示,然后转到创建 COM DLL 的目录。现在运行以下命令:

tlbimp MSDNCOMServer.dll

Tlbimp.exe 是 .NET Framework SDK 中附带的类型库导入程序。此命令输出一个名为 MSDNCOMServerLIB.dll 的托管 DLL,该 DLL 作为非托管 COM DLL 的托管包装。

现在我们正在运行非托管客户端,让我们仔细看看从 Tlbimp.exe 生成的原始非托管 COM DLL 和托管 DLL。图 3 显示了来自原始 COM DLL 的 Oleview 转储。

  Figure 3 来自 MSDNCOMServer.dll 的 Oleview 转储

// 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();
    };

};

如果您使用的是 .NET 反编译器(如 Lutz Roeder 的 .NET Reflector),并且查看的是 Tlbimp 的托管库输出,则您将看到类似于图 4 的内容。

  Figure 4 托管的 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();
    }
}

看来发生了很多事情。首先,我们注意到库 MSDNCOMServerLib 转换为命名空间 MSDNCOMServerLib。其次,接口 IMyCOMServer 通过属性化的 GUID 保留下来,CoClass MyCOMServer 被转换为:一个由 IMyCOMServer 派生而来的公共接口 MyCOMServer;一个用于实现接口 IMyCOMServer 和 MyCOMServer 的类 MyCOMServerClass。

编写一个托管客户端

至此,您已经看到了编写 COM 服务器和非托管 COM 客户端的过程,以及在使用 Tlbimp 将非托管 DLL 转换成托管 DLL 时,所发生的具体转换。我们将在托管代码中使用 Tlbimp 导出的库,以编写一个托管 COM 客户端:

using System;
using MSDNCOMServerLib;

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

托管代码十分简单和简洁。我已将这段代码保存在名为 ManagedClient.cs 的文件中,并使用以下命令对其进行了编译:

csc ManagedClient.cs /reference:MSDNCOMServerLib.dll

如果您运行可执行文件 ManagedClient.exe,您会得到我们在非托管客户端中所得到的结果。

封送一个结构和一个字符串

现在,让我们为服务器添加一些参数:一个字符串和一个结构。这就需要对服务器自身和客户端均进行修改。为了完成这项工作,您需要先在服务器上定义方法。

首先,编辑 IDL 文件以包含如图 5 所示的代码。我添加了一个结构的定义,并且修改了 MyCOMServerMethod 的声明,以便引入两个参数:一个字符串和一个结构。

  Figure 5 修改 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);
};

接着,编辑服务器上的头文件并按照 IDL 中的定义添加以下方法原型。

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

修改方法的实现以反映原型的变化,还要添加更多的代码以操作传递给它的字符串。最终代码如图 6 所示。这样就完成了对服务器的更改,现在重新编译它。

  Figure 6 修改 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;
}

为了访问托管客户端上的新 COM 方法,我将 DLL 复制到了我的托管客户端所在的目录中。我重新运行以下命令,以获得包含新方法的新的 MSDNCOMServerLib.dll。

tlbimp MSDNCOMServer.dll

您可以在 Ildasm 中查看新的方法签名和实现。Ildasm 是 .NET Framework SDK 附带的另一个工具。在查看它时,您会注意到封送拆收器已将如图 7 所示的代码添加到了调用中。

  Figure 7 COM 服务器封送代码

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;
    }
}

输入的 ref 字符串被封送为 In,OUT BSTR 和结构保持不变,但是结构内的字符串被封送为 LPWStr。

既然服务器和由 Tlbimp 工具转换的托管 DLL 均已就绪,我就可以继续修改托管客户端以便传递一个字符串和一个结构,然后检查返回的值,看它是否有变化。图 8 显示的是修改后的客户端代码。

  Figure 8 传递字符串和结构

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);
    }
}

托管客户端仍然十分简洁和简单。它创建了字符串和从非托管 DLL 中导入的结构的一个实例。现在,按照与此前相同的方法编译以上代码,然后运行可执行文件。您将看到以下输出:

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

托管代码中的 Main 部分的调用访问了非托管服务器并显示了所传递的输入。服务器修改了字符串并在服务器上进行显示。为了验证修改后的字符串是否传递回客户端,我们还要在客户端显示字符串。

为了在同一个 COM DLL 中创建一个非托管函数(非 COM),我必须首先在非托管 DLL 中定义方法并实现它。选择 MSDNCOMServer.cpp,然后将以下代码粘贴到该文件的最底部。

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

您需要从 DLL 中导出该方法才能访问它。要执行此操作,请在 COMServer.def 文件中向此函数添加一个条目:

; COMServer.def : Declares the module parameters.

LIBRARY      "COMServer.DLL"

EXPORTS
    DllCanUnloadNow       PRIVATE
    DllGetClassObject     PRIVATE
    DllRegisterServer     PRIVATE
    DllUnregisterServer   PRIVATE
    MyNonCOMMethod        PRIVATE

编译服务器并生成内含 COM 方法和导出的函数的新 DLL。

将该 DLL 复制到托管客户端所在的目录中,然后对它运行 Tlbimp。使用 Ildasm,您可以看到导出的函数出现在托管 DLL 中。

在托管代码中使用 P/Invoke

接下来,我们将修改托管客户端,以便使用 P/Invoke 调用非托管函数。要执行此操作,您必须在托管代码中定义方法,然后进行调用。托管方法声明映射了非托管声明,并告知 CLR 可以在何处找到真正的实现部分。图 9 显示了对代码所做的更改。

  Figure 9 使用 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();
    }
}

我们已经为我们的原始托管客户端添加了一条 DllImport 语句和一个 MyNonCOMMethod 调用。当您编译并运行客户端时,您会看到如下输出:

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

对 MyNonCOMMethod 的调用确实被执行并且显示了正确的语句。

使用接口调用托管代码

最后,让我们使用反向的 P/Invoke 在非托管函数中进行托管调用。这并不是一个常用的方法,但是非常有用。您可以向非托管代码传递一个托管接口或一个委托,然后非托管代码便可以通过接口调用委托或方法。让我们先来处理托管接口,以便能够通过非托管代码调用它。要执行此操作,请在 ManagedClient.cs 中声明接口,如图 10 所示。

  Figure 10 声明托管接口

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);
}

在此代码中,我向非托管 DLL 中添加了一个 IReversePInvoke 的声明、一个实现接口的类和一个修改后的 DLLImport 方法,以便获得一个接口指针。我还修改了调用方法的位置,以便将 ManagedClass 实例传递给 MyNonCOMMethod。

您现在可以编译该客户端,但是它不能成功运行,因为还没有修改服务器,以使其将接口作为参数。所以,我们对托管可执行文件运行 Tlbexp(.NET Framework SDK 附带的另一个工具,可从托管程序集中生成一个类型库)以得到 .tlb 文件。在 Oleview 中打开它,复制接口的原型,并将其粘贴到服务器 IDL 文件中,如下所示:

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

现在服务器上便有了接口。您必须更改方法的签名,并添加对接口上的托管方法的调用方法如下:

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;
}

现在我们有了获得托管接口并调用托管接口上的方法的非托管函数。将它编译为一个 DLL,对其运行 Tlbimp 以得到一个更新的托管互操作 DLL,然后对 Tlbimp 生成的托管 DLL 再次编译托管代码。以下是得到的输出:

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

调用确实回到了托管代码并显示了我们所期望的“10”。托管方法将该值修改为 20,然后在非托管函数中显示它。

调试托管代码和非托管代码

非常希望您的所有代码都能正常工作。但是,显然我们很少能够编写出绝对没有 Bug 的代码。从某种意义上讲,您需要知道如何调试使用 interop 的代码。

为了成功完成调试,请确保您的计算机加载了完整的符号。符号可以使调试器能够更好地理解和解释代码中的操作。Microsoft 提供了一个公共服务器,调试器可以连接到该服务器来访问这些用于 Microsoft 提供的 DLL 的符号。若要配置 windbg 调试器以使用此公共符号服务器,请首先运行 windbg:

windbg managedclient.exe

然后使用以下命令语法来将其指向符号服务器:

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

接着,需要告知 windbg 在加载 mscorwks、COMServerLib 和 COMServer DLLs 时设置断点。使用 sxe ld 命令可以完成此操作:

sxe ld mscorwks
sxe ld COMServerLib
sxe ld COMServer

若要在调试器下使应用程序开始运行,请键入 g(表示“go”),然后按回车键。应用程序在运行过程中,会在加载 COMServer DLL 时中断并进入调试器,此时可以在 MyCOMServerMethod 处设置另一个断点(然后可以使用 bl 命令验证是否确实设置了断点):

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

现在已经加载了 mscorwks,我们希望 windbg 加载 SOS 调试器扩展,以便更加强大的调试器与托管应用程序交互:

.loadby sos mscorwks 

为了在通过反向 P/Invoke 从 COM 服务器调用的方法 ManagedMethod 上设置断点,我们使用 SOS 的 !bpmd 命令:

!bpmd ManagedClient ManagedClass.ManagedMethod

使用此断点工具时,我们仍然可以使用 g 命令继续。在某一时刻,我们会遇到 MyCOMServerMethod 上的非托管断点,然后我们可以使用 windbg kL 命令查看当前堆栈跟踪信息,如图 11 所示(该图还显示了使用其他 windbg 命令检查变量值的情形)。

  Figure 11 获取堆栈跟踪信息并查看内存

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

如果我们使用 g 命令继续,应用程序会在调用 ManagedMethod 时中断,此时,可以使用 SOS 命令 !clrstack 来查看托管堆栈:

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]

总结

有关其他信息,可以先访问 PInvoke.net 进行了解,这是一个社区型的资源站点。Adam Nathan 的 COM interop 著作,《NET and COM:The Complete Interoperability Guide》即将面世(他的博客为“Adam Nathan's Blog”)。有关调试(包括 WinDbg 下载和参考)的信息,请访问此处,您还可以在此处找到有关 SOS 调试扩展 (SOS.dll) 的参考信息。

 

转:http://msdn.microsoft.com/zh-cn/magazine/cc163494.aspx

原文地址:https://www.cnblogs.com/jiguixin/p/2473726.html