pInvoke和COM互操作有什么区别?

26

假设我正在访问一个第三方库,文档中说明我可以使用pInvoke或创建Interop库并使用COM。这两种技术有什么区别,我为什么选择其中之一?

pInvoke是通过调用原生代码来访问非托管DLL的技术,而创建Interop库并使用COM是使用.NET对非托管组件进行包装的技术。

选择哪种技术取决于许多因素,例如库的类型和可用性、开发人员的经验和技能以及团队内部的约定。


https://dev59.com/KmTWa4cB1Zd3GeqPF7Ng - Hans Passant
3个回答

16

P/Invoke用于调用纯C API(如大多数Win32 API)。COM interop用于调用COM对象。

如果API调用次数相对较高(并且您可以使用COM包装器将它们封装为仅需一两个调用),则可能会创建一个C ++ COM包装器来包装C API,然后使用COM interop调用您的包装器。这是因为托管本机互操作性可能相对昂贵,并且最好尽量减少转换次数。虽然实际上,我会说使用C ++ / CLI创建包装器可能对C#方面更加友好(例如,看看SlimDX,它是一个C ++ / CLI包装器,围绕着一个COM API(DirectX)。

话虽如此,除非您有特定的性能问题,否则我只会使用更自然的方法来调用所需的API:如果它是C API(如Win32 API),则使用P/Invoke。如果基于COM,则使用COM interop。


那么你的意思是,在底层,COM互操作正在自己进行P/Invoke操作?而COM只是一个友好的包装器? - Grant
2
不,COM互操作和P/Invoke是不同的,它们之间没有相互实现的关系。我在第二段中所说的是,如果C API需要大量函数调用,则可以创建一个COM包装器(使用C++),并从C#中调用该包装器以减少托管-本机转换的数量。我猜测这就是您的库文档所建议的。 - Dean Harding

4
根据C#编程指南:互操作性,互操作性使您能够保留和利用非托管代码的现有投资。在公共语言运行时(CLR)控制下运行的代码称为托管代码,而在CLR之外运行的代码称为非托管代码。COM、COM+、C++组件、ActiveX组件和Microsoft Windows API是非托管代码的示例。通过平台调用服务、System.Runtime.InteropServices命名空间、C++互操作和COM互操作(COM interop),.NET实现了与非托管代码的互操作性。
正如Kevin Smathers 在此讨论串的其他地方所述,
PInvoke使用动态链接机制将外部代码引入执行过程。动态链接库(DLL)必须与调用应用程序具有相同的目标体系结构,因此无法从64位到32位或反向进行交叉调用。相反,DLL被映射到调用者的地址空间并在进程中执行。
COM、DCOM、COM+和ActiveX都基于进程间通信库,但有时可能会退化为简单的DLL加载。COM链接对象与CORBA对象相关,但不完全相同,而CORBA演变出自己的对象定位器,而COM实现仍松散基于Sun Microsystems RPC和XDR库,并带有COM的面向对象特性扩展。 COM对象不是由DLL引用,而是由GUID引用,该GUID用于查找对象类并查询其接口。对象代码通常在单独的进程中运行,可能在单独的服务器上。

根据C++互操作性能考虑中的P/Invoke vs. C++ Interop一节,

对于.NET语言,例如Visual Basic和C#,与本地组件互操作的指定方法是P/Invoke。由于P/Invoke受.NET Framework支持,因此Visual C++也支持它,但是Visual C++还提供了自己的互操作支持,称为C++ Interop。 C++ Interop优于P / Invoke,因为P / Invoke不是类型安全的。因此,在运行时主要报告错误,但是C++ Interop也比P / Invoke具有性能优势。
C++ Interop执行的数据编组是最简单的形式:参数以按位方式跨越托管/非托管边界简单复制;根本不执行任何转换。对于P/Invoke,仅当所有参数都是简单可平铺的类型时,才是真实的。否则,P/Invoke会执行非常强大的步骤,将每个托管参数转换为适当的本机类型,反之亦然,如果参数标记为“out”或“in,out”。
换句话说,C++ Interop使用最快的数据编组方法,而P/Invoke使用最强大的方法。这意味着C++ Interop(通常为C++提供)默认情况下提供最佳性能,并且程序员负责处理此行为不安全或不适当的情况。
因此,C++ Interop要求必须显式提供数据编组,但优点是程序员可以自由决定在数据的性质以及如何使用数据的情况下什么是适当的。此外,尽管可以修改P/Invoke数据编组的行为并进行定制,但C++ Interop允许根据调用进行自定义数据编组。这在P/Invoke中是不可能的。

P/Invoke示例如下

using System;
using System.Runtime.InteropServices;

public class Win32 {
     [DllImport("user32.dll", CharSet=CharSet.Auto)]
     public static extern IntPtr MessageBox(int hWnd, String text, 
                     String caption, uint type);
}

public class HelloWorld {
    public static void Main() {
       Win32.MessageBox(0, "Hello World", "Platform Invoke Sample", 0);
    }
}

C++调用C#代码的COM互操作示例

// ConLoan.cpp : Defines the entry point for the console application.  
#include "stdafx.h"  
#import "..\LoanLib\LoanLib.tlb" raw_interfaces_only  
using namespace LoanLib;  
  
int main(int argc, char* argv[])  
{  
    HRESULT hr = CoInitialize(NULL);  
  
    ILoanPtr pILoan(__uuidof(Loan));  
  
    if (argc < 5)   
    {  
        printf("Usage: ConLoan Balance Rate Term Payment\n");  
        printf("    Either Balance, Rate, Term, or Payment must be 0\n");  
        return -1;  
    }  
  
    double openingBalance = atof(argv[1]);  
    double rate = atof(argv[2])/100.0;  
    short  term = atoi(argv[3]);  
    double payment = atof(argv[4]);  
  
    pILoan->put_OpeningBalance(openingBalance);  
    pILoan->put_Rate(rate);  
    pILoan->put_Term(term);  
    pILoan->put_Payment(payment);  
  
    if (openingBalance == 0.00)   
         pILoan->ComputeOpeningBalance(&openingBalance);  
    if (rate == 0.00) pILoan->ComputeRate(&rate);  
    if (term == 0) pILoan->ComputeTerm(&term);  
    if (payment == 0.00) pILoan->ComputePayment(&payment);  
  
    printf("Balance = %.2f\n", openingBalance);  
    printf("Rate    = %.1f%%\n", rate*100);  
    printf("Term    = %.2i\n", term);  
    printf("Payment = %.2f\n", payment);  
  
    VARIANT_BOOL MorePmts;  
    double Balance = 0.0;  
    double Principal = 0.0;  
    double Interest = 0.0;  
  
    printf("%4s%10s%12s%10s%12s\n", "Nbr", "Payment", "Principal", "Interest", "Balance");  
    printf("%4s%10s%12s%10s%12s\n", "---", "-------", "---------",   
"--------", "-------");  
  
    pILoan->GetFirstPmtDistribution(payment, &Balance, &Principal, &Interest, &MorePmts);  
  
    for (short PmtNbr = 1; MorePmts; PmtNbr++)   
    {  
        printf("%4i%10.2f%12.2f%10.2f%12.2f\n",  
        PmtNbr, payment, Principal, Interest, Balance);  
  
        pILoan->GetNextPmtDistribution(payment, &Balance, &Principal, &Interest, &MorePmts);   
    }  
  
    CoUninitialize();  
    return 0;  
}

请不要发布仅包含链接的答案。链接可能会在某些时候失效,留下一个无用的答案。 - Brian Rasmussen
1
你在这个回答中整合了一系列独立但相关的资源,做得非常好。感谢你的付出。虽然@BrianRasmussen是正确的,仅包含链接的答案并不有用,但请确保包括链接及其相关内容。这有三个好处:它增加了你的回答的权威性;承认他人的工作是良好和公平的做法;只要链接仍然存在,我就可以使用它们来查找更多信息。希望这可以帮助你,并再次感谢你为贡献有用的信息而努力。 - Bondolin

3

PInvoke使用动态链接机制将外部代码引入执行进程。动态链接库(DLL)必须具有与调用应用程序相同的目标架构,因此无法从64位调用32位或反之的跨平台调用。相反,DLL被映射到调用程序的地址空间中并在进程中执行。

COM、DCOM、COM+和ActiveX都基于进程间通信库,但有时会退化为简单的DLL加载。COM链接的对象与CORBA对象相关,但并不完全相同,虽然CORBA发展了自己的对象定位器,但COM实现仍松散地基于Sun Microsystems RPC和XDR库,并扩展了COM的面向对象特性。COM对象不是通过DLL引用,而是通过GUID引用,该GUID用于查找对象类并查询其接口。对象代码通常在单独的进程中运行,可能在单独的服务器上。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接