What is the difference between pInvoke and COM Interop?

PInvoke uses the dynamic linking mechanism for bringing external code into the executing process. Dynamic linking libraries (DLLs) must have the same target architecture as the calling application, so there is no ability to make cross calls from 64-bit to 32-bit or vice versa. Instead the DLL is mapped into the caller's address space and executed in process.

COM, DCOM, COM+, and ActiveX are all based on interprocess communications libraries, but can sometimes devolve into a simple DLL load. COM linked objects are related, but not identical to CORBA objects, but while CORBA evolved its own object locator, the COM implementation is still loosely based on Sun Microsystems RPC and XDR libraries with extensions for the object oriented features of COM. COM objects are referenced not by DLL, but by a GUID which is used to look up the object class and query its interfaces. The object code usually runs in a separate process, and possibly on a separate server.


According to Interoperability (C# Programming Guide),

Interoperability enables you to preserve and take advantage of existing investments in unmanaged code. Code that runs under the control of the common language runtime (CLR) is called managed code, and code that runs outside the CLR is called unmanaged code. COM, COM+, C++ components, ActiveX components, and Microsoft Windows API are examples of unmanaged code.

.NET enables interoperability with unmanaged code through platform invoke services, the System.Runtime.InteropServices namespace, C++ interoperability, and COM interoperability (COM interop).

As Kevin Smathers has stated elsewhere in this thread,

PInvoke uses the dynamic linking mechanism for bringing external code into the executing process. Dynamic linking libraries (DLLs) must have the same target architecture as the calling application, so there is no ability to make cross calls from 64-bit to 32-bit or vice versa. Instead the DLL is mapped into the caller's address space and executed in process.

COM, DCOM, COM+, and ActiveX are all based on interprocess communications libraries, but can sometimes devolve into a simple DLL load. COM linked objects are related, but not identical to CORBA objects, but while CORBA evolved its own object locator, the COM implementation is still loosely based on Sun Microsystems RPC and XDR libraries with extensions for the object oriented features of COM. COM objects are referenced not by DLL, but by a GUID which is used to look up the object class and query its interfaces. The object code usually runs in a separate process, and possibly on a separate server.

(Emphasis mine.)

Per Performance Considerations for Interop (C++), section P/Invoke vs. C++ Interop,

For .NET languages, such as Visual Basic and C#, the prescribed method for interoperating with native components is P/Invoke. Because P/Invoke is supported by the .NET Framework, Visual C++ supports it as well, but Visual C++ also provides its own interoperability support, which is referred to as C++ Interop. C++ Interop is preferred over P/Invoke because P/Invoke is not type-safe. As a result, errors are primarily reported at run time, but C++ Interop also has performance advantages over P/Invoke.

...

The data marshaling performed by C++ Interop is the simplest possible form: the parameters are simply copied across the managed/unmanaged boundary in a bitwise fashion; no transformation is performed at all. For P/Invoke, this is only true if all parameters are simple, blittable types. Otherwise, P/Invoke performs very robust steps to convert each managed parameter to an appropriate native type, and vice versa if the arguments are marked as "out", or "in,out".

In other words, C++ Interop uses the fastest possible method of data marshaling, whereas P/Invoke uses the most robust method. This means that C++ Interop (in a fashion typical for C++) provides optimal performance by default, and the programmer is responsible for addressing cases where this behavior is not safe or appropriate.

C++ Interop therefore requires that data marshaling must be provided explicitly, but the advantage is that the programmer is free to decide what is appropriate, given the nature of the data, and how it is to be used. Furthermore, although the behavior of P/Invoke data marshaling can be modified at customized to a degree, C++ Interop allows data marshaling to be customized on a call-by-call basis. This is not possible with P/Invoke.

P/Invoke example below:

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

COM interop example (in C++ that is consuming C# code):

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

P/Invoke is used to call plain-C APIs (like most of the Win32 API). COM interop is used to call COM objects.

You might create a C++ COM wrapper around a C API and then use COM interop to call your wrapper if the number of API calls is relatively high (and you can use the COM wrapper to encapsulate them into just one or two calls). This is because managed-native interop can be relatively expensive and it's good to minimise the number of transitions. Though actually I would say using C++/CLI to create the wrapper would probably be a little more friendly for the C# side of thing (looking at SlimDX, for example, which is a C++/CLI wrapper around a COM API (DirectX)).

Having said that, unless you have a specific performance problem, I would just use whichever method is more natural for the API you're trying to call: if it's a C API (like the Win32 API is) then use P/Invoke. If it's COM-based, then use COM interop.

Tags:

.Net

Com

Pinvoke