Aracılığıyla paylaş


Yönetilen Koddan Yerel İşlevleri Çağırma

Ortak dil çalışma zamanı, yönetilen kodun yerel dinamik bağlantı kitaplıklarında(DLL'ler) C stili işlevleri çağırmasına imkan tanıyan Platform Çağırma Hizmetleri veya PInvoke sağlar. Aynı veri hazırlama çalışma zamanıyla COM birlikte işbirliği ve "sadece çalışır," öğesi veya IJW, mekanizma için kullanılır.

Daha fazla bilgi için bkz:

Bu bölümdeki örnekler sadece PInvoke öğesinin kullanımını gösterir. Yordam sıralama kodu yazmak yerine sıralama bilgilerini bildirimli olarak öznitelikler yoluyla sağladığınızdan PInvoke, özelleştirilmiş veri sıralamasını basitleştirebilir.

Not

Sıralama kitaplığı, en iyi duruma getirilmiş şekilde yerel ve yönetilen ortamlar arasında veri sıralamanın alternatif bir yolunu sağlar. Sıralama kitaplığı hakkında daha fazla bilgi için bkz. Overview of Marshaling in C++. Sıralama kitaplığı işlevler için değil yalnızca veriler için kullanılabilirdir.

PInvoke ve DllImport Özelliği

Aşağıdaki örnek bir Visual C++ programında PInvoke öğesinin kullanımını gösterir. Yerel işlev koymaları msvcrt.dll'de tanımlanır. DllImport özniteliği koymaların bildirimi için kullanılır.

// platform_invocation_services.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

[DllImport("msvcrt", CharSet=CharSet::Ansi)]
extern "C" int puts(String ^);

int main() {
   String ^ pStr = "Hello World!";
   puts(pStr);
}

Aşağıdaki örnek önceki örneğe eşdeğerdir ancak IJW kullanır.

// platform_invocation_services_2.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

#include <stdio.h>

int main() {
   String ^ pStr = "Hello World!";
   char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer(); 
   puts(pChars);
   
   Marshal::FreeHGlobal((IntPtr)pChars);
}

IJW avantajları

  • Programın kullandığı yönetilmeyen API'lere ilişkin DLLImport öznitelik bildirimlerini yazmaya gerek yok. Sadece üstbilgi dosyasını ve bağlantıyı içe aktarma kitaplığına ekleyin.

  • IJW mekanizması biraz daha hızlıdır (örneğin, IJW saplamalarının PIN veya kopya veri öğeleri ihtiyacını denetlemesi gerekmez çünkü açıkça geliştirici tarafından yapılır).

  • Performans sorunlarını açıkça gösterir. Bu durumda, bir Unicode dizesini bir ANSI dizesine çeviriyor olmanız ve görevli bir bellek ayırma ve ayırmayı kaldırma sahibi olmanız. Bu durumda IJW kullanarak kodu yazan geliştirici _putws ve PtrToStringChars öğesini çağırmanın performans için daha iyi olacağını görecektir.

  • Aynı veriyi kullanarak bir çok yönetilmeyen API çağırırsanız, bu veriyi bir kere hazırlama ve hazırlanmış kopyayı geçirme her seferinde yeniden hazırlamadan çok daha verimlidir.

IJW'nin Dezavantajları

  • Sıralama işlemi özniteliklere (çoğu zaman uygun varsayılanlara sahiptir) göre değil, kod içerisinde açıkça belirtilmelidir.

  • Sıralama kodu uygulama mantığının akışında daha bozucu olduğu satır içidir.

  • Açık sıralama API'ları 32 bitten 64 bite taşınabilirlik için IntPtr türlerini geri döndürdüğü için, ekstra ToPointer çağrıları kullanmalısınız.

C++ tarafından sunulan belirli yöntem bazı ek karmaşıklıklar pahasına, daha etkin ve açık bir yöntemdir.

Uygulama ağırlıklı olarak yönetilmeyen veri türleri kullanıyorsa veya .NET Framework API'larından daha fazla yönetilmeyen API'ları çağırıyorsa, IJW özelliğini kullanmanızı öneririz. Çoğunlukla yönetilen bir uygulamada arada bir yönetilmeyen bir API öğesini çağırmak için gereken seçim daha incedir.

Windows API'leri ile PInvoke

PInvoke, Windows'daki işlevleri çağırmak için uygundur.

Bu örnekte, bir Visual c++ programı Win32 API'ın parçası olan MessageBox işlevi ile birlikte çalışır.

// platform_invocation_services_4.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HWND;
[DllImport("user32", CharSet=CharSet::Ansi)]
extern "C" int MessageBox(HWND hWnd, String ^ pText, String ^ pCaption, unsigned int uType);

int main() {
   String ^ pText = "Hello World! ";
   String ^ pCaption = "PInvoke Test";
   MessageBox(0, pText, pCaption, 0);
}

Çıktı, PInvoke Testi başlığına sahip olan ve Hello World! metnini içeren bir ileti kutusudur.

Sıralama bilgileri DLL'deki işlevleri aramak için PInvoke tarafından da kullanılır. user32.dll öğesinde MessageBox işlevi yoktur ama CharSet=CharSet::Ansi PInvoke'un Unicode sürümü olan MessageBoxW yerine ANSI sürümü olan MessageBoxA öğesini kullanmasına olanak verir. Genel olarak, .NET Framework dize nesnelerinin yerel Unicode biçimlerini ANSI öğesine çevirme yükünü ortadan kaldıracağından yönetilmeyen API'lerin Unicode sürümlerini kullanmanızı öneririz.

PInvoke Ne Zaman Kullanılmaz

PInvoke kullanmak DLL'lerdeki tüm C-stili işlevlerde uygun değildir. Örneğin, mylib.dll üzerinde aşağıdaki şekilde açıklanan bir işlev MakeSpecial bulunduğunu varsayın:

char * MakeSpecial(char * pszString);

PInvoke'u bir Visual C++ uygulamasında kullanırsak, aşağıdakine benzer bir şey yazabiliriz:

[DllImport("mylib")]

extern "C" String * MakeSpecial([MarshalAs(UnmanagedType::LPStr)] String ^);

Buradaki zorluk MakeSpecial tarafından döndürülen yönetilmeyen dize için belleği silemememizdir. PInvoke aracılığıyla çağrılan diğer işlevler kullanıcı tarafından kaldırılması gerekmeyen dahili arabelleğe ilişkin bir işaretçi döndürür. Bu durumda bariz seçim IJW özelliğini kullanmaktır.

PInvoke öğesinin kısıtlamaları

Parametre olarak aldığınız yerel bir işlevden işaretçinin aynısını döndüremezsiniz. Yerel bir işlev PInvoke tarafından kendisine sıralanmış işaretçiyi döndürürse, bellek bozulması ve istisnalar oluşması beklenebilir.

__declspec(dllexport)
char* fstringA(char* param) {
   return param;
}

Aşağıdaki örnek bu problemi sergiler ve program doğru çıktıyı veriyor gibi görünse bile çıktı serbest bırakılan bellekten geliyor.

// platform_invocation_services_5.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
#include <limits.h>

ref struct MyPInvokeWrap {
public:
   [ DllImport("user32.dll", EntryPoint = "CharLower", CharSet = CharSet::Ansi) ]
   static String^ CharLower([In, Out] String ^);
};

int main() {
   String ^ strout = "AabCc";
   Console::WriteLine(strout);
   strout = MyPInvokeWrap::CharLower(strout);
   Console::WriteLine(strout);
}

Bağımsız Değişken Sıralama

PInvoke ile aynı forma sahip yönetilen ve C++ yerel temel türler arasında sıralamaya gerek yoktur. Örneğin, Int32 ve int arasında veya Çift ve çift arasında sıralama yapılması gerekli değildir.

Ancak, aynı forma sahip olmayan türleri sıralamanız gerekir. Bu, karakter, dize ve yapı türlerini içerir. Aşağıdaki tablo çeşitli türler için sıralayıcı tarafından kullanılan eşlemeleri gösterir:

wtypes.h

Visual C++

/clr'ye sahip Visual C++

Ortak dil çalışma zamanı

TANITICI

void *

void *

IntPtr, UIntPtr

BAYT

unsigned char

unsigned char

Bayt

SHORT

short

short

Int16

WORD

unsigned short

unsigned short

UInt16

INT

int

int

Int32

UINT

imzalanmamış int

imzalanmamış int

UInt32

LONG

long

long

Int32

BOOL

long

bool

Boolean

DWORD

imzasız uzun

imzasız uzun

UInt32

ULONG

imzasız uzun

imzasız uzun

UInt32

CHAR

char

char

Char

LPCSTR

char *

Dize ^ [in], StringBuilder ^ [in, out]

Dize ^ [in], StringBuilder ^ [in, out]

LPCSTR

const char *

Dize ^

Dize

LPWSTR

wchar_t *

Dize ^ [in], StringBuilder ^ [in, out]

Dize ^ [in], StringBuilder ^ [in, out]

LPCWSTR

const wchar_t *

Dize ^

Dize

KAYMA

float

float

Tek

ÇİFT

double

double

Çift

Adresi yönetilmeyen bir işleve geçirilirse sıralayıcı otomatik olarak çalışma zamanı yığınına ayrılmış belleği sabitler. Sabitleme, atık toplayıcısının ayrılan bellek bloğunu sıkıştırma sırasında taşımasını engeller.

Bu konunun önceki bölümlerinde gösterilen örnekte, DllImport öğesinin CharSet parametresi yönetilen Dizeler'in nasıl sıralanması gerektiğini belirtir; bu durumda bunlar yerel taraf için ANSI dizelerine sıralanmalıdır.

MarshalAs özniteliğini kullanılarak bir yerel işlevin tek tek bağımsız değişkenlerinin sıralama bilgilerini belirtebilirsiniz. Bir Dize * bağımsız değişkenini sıralamak için birkaç seçenek vardır: BStr, ANSIBStr, TBStr, LPStr, LPWStr ve LPTStr. Varsayılan LPStr'dir.

Bu örnekte, dize çift baytlı Unicode karakter dizesi, LPWStr olarak sıralanır. Çıktı Hello World! ifadesinin ilk harfidir Sıralanmış dizenin ikinci baytı boş olduğu ve puts bunu bir dize sonu işareti olarak yorumladığı için.

// platform_invocation_services_3.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

[DllImport("msvcrt", EntryPoint="puts")]
extern "C" int puts([MarshalAs(UnmanagedType::LPWStr)] String ^);

int main() {
   String ^ pStr = "Hello World!";
   puts(pStr);
}

MarshalAs özniteliği System::Runtime::InteropServices ad alanındadır. Öznitelik diziler gibi diğer veri türleri ile kullanılabilir.

Konuda daha önce de belirtildiği gibi sıralama kitaplığı, yerli ve yönetilen ortamlar arasında verileri sıralamak için yeni ve iyileştirilmiş bir yöntem sağlar. Daha fazla bilgi için bkz. Overview of Marshaling in C++.

Performans İle İlgili Önemli Noktalar

PInvoke, çağrı başına 10 ila 30 x 86 talimat yüke sahiptir. Bu sabit bedele ek olarak, hazırlama ek yük oluşturur. Yönetilen ve yönetilmeyen kod içinde aynı gösterimi içeren taşınabilir türler arasında hiçbir hazırlama maliyeti yok. Örneğin, int ve Int32 arasında dönüştürme maliyeti yoktur.

Daha iyi performans için, çağrı başına daha az veri sıralayan daha fazla çağrı yerine, mümkün olan en fazla veriyi sıralayan daha az PInvoke çağrısı ortaya koyun.

Ayrıca bkz.

Diğer Kaynaklar

Yerel ve.NET Birlikte Çalışabilirliği