Скажем, что я обращаюсь к сторонней библиотеке, для которой в документации указано, что я могу использовать pInvoke или создать библиотеку interop и использовать COM. В чем разница между этими двумя методами и почему я могу выбрать один из них?
Ответ 1
P/Invoke используется для вызова API-интерфейсов plain-C (как и большинство Win32 API). COM-вызов используется для вызова COM-объектов.
Вы можете создать обертку С++ COM вокруг API C, а затем использовать COM-взаимодействие для вызова вашей оболочки, если количество вызовов API относительно велико (и вы можете использовать COM-оболочку, чтобы инкапсулировать их в один или два вызова), Это связано с тем, что интерфейс с управляемым интерфейсом может быть относительно дорогостоящим, и хорошо свести к минимуму количество переходов. Хотя на самом деле я бы сказал, что использование С++/CLI для создания обертки, вероятно, будет немного более дружественным для стороны С# (смотря на SlimDX, например, который является оболочкой С++/CLI вокруг COM API (DirectX)).
Сказав это, если у вас нет конкретной проблемы с производительностью, я бы просто использовал любой метод, более естественный для API, который вы пытаетесь вызвать: если это API C (например, Win32 API), то используйте P/Invoke. Если это COM-based, то используйте COM-взаимодействие.
Ответ 3
Функциональная совместимость позволяет вам сохранять и использовать существующие вложения в неуправляемый код. Код, который выполняется под управлением общеязыковой среды выполнения (CLR), называется управляемым кодом, а код, который выполняется вне CLR, называется неуправляемым кодом. Компоненты COM, COM+, C++, ActiveX и Microsoft Win32 API являются примерами неуправляемого кода.
.NET Framework обеспечивает взаимодействие с неуправляемым кодом с помощью служб вызова платформы (P/Invoke), пространства имен System.Runtime.InteropServices, взаимодействия C++ и взаимодействия COM (взаимодействие COM).
PInvoke использует механизм динамической компоновки для добавления внешнего кода в исполняемый процесс. Библиотеки динамической компоновки (DLL) должны иметь ту же целевую архитектуру, что и вызывающее приложение, поэтому нет возможности выполнять перекрестные вызовы с 64-разрядных на 32-разрядные или наоборот. Вместо этого DLL отображается в адресное пространство вызывающего и выполняется в процессе.
COM, DCOM, COM+ и ActiveX основаны на библиотеках межпроцессного взаимодействия, но иногда превращаются в простую загрузку DLL. Связанные объекты COM связаны, но не идентичны объектам CORBA, но в то время как CORBA разработала свой собственный локатор объектов, реализация COM все еще слабо основана на библиотеках RPC и XDR Sun Microsystems с расширениями для объектно-ориентированных функций COM. На COM-объекты ссылаются не DLL, а GUID, который используется для поиска класса объекта и запроса его интерфейсов. Объектный код обычно выполняется в отдельном процессе или может быть на отдельном сервере.
Для языков .NET, таких как Visual Basic и С#, предписанный метод взаимодействия с нативными компонентами - P/Invoke. Поскольку P/Invoke поддерживается .NET Framework, Visual C++ также поддерживает его, но Visual C++ также предоставляет свою собственную поддержку взаимодействия, которая называется C++ Interop. C++ Взаимодействие предпочтительнее P/Invoke, потому что P/Invoke не является безопасным для типов. В результате ошибки в основном сообщаются во время выполнения, но C++ Interop также имеет преимущества в производительности по сравнению с P/Invoke.
Маршалинг данных, выполняемый Interop C++, является самой простой из возможных форм: параметры просто копируются через управляемую/неуправляемую границу побитовым образом; преобразование не выполняется вообще. Для P/Invoke это верно только в том случае, если все параметры являются простыми, blittable типами. В противном случае P/Invoke выполняет очень надежные шаги для преобразования каждого управляемого параметра в соответствующий собственный тип, и наоборот, если аргументы помечены как "out" или "in, out".
Другими словами, C++ Interop использует самый быстрый из возможных методов маршалинга данных, тогда как P/Invoke использует самый надежный метод. Это означает, что C++ Interop (в типичной для C++ манере) обеспечивает оптимальную производительность по умолчанию, и программист отвечает за рассмотрение случаев, когда это поведение небезопасно или нецелесообразно.
C++ Поэтому взаимодействие требует, чтобы маршалинг данных был предоставлен явно, но преимущество в том, что программист свободен решать, что уместно, учитывая природу данных и как их использовать. Кроме того, хотя поведение маршалинга данных P/Invoke может быть изменено до определенной степени, взаимодействие C++ позволяет настраивать маршалинг данных на индивидуальной основе. Это невозможно с 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);
}
}
Пример Com Interop (в C++, который потребляет код С#)
// 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;
}