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

Ortak dil çalışma zamanı, yönetilen kodun yerel dinamik bağlantılı kitaplıklarda (DLL) C stili işlevleri çağırmasını sağlayan Platform Çağırma Hizmetleri veya PInvoke sağlar. Aynı veri hazırlama, çalışma zamanıyla COM birlikte çalışabilirliği ve "It Just Works" veya IJW mekanizması için kullanılır.

Daha fazla bilgi için bkz.

Bu bölümdeki örnekler, nasıl PInvoke kullanılabileceğini gösterir. PInvoke yordamsal sıralama kodu yazmak yerine özniteliklerde sıralama bilgilerini bildirimli olarak sağladığınız için özelleştirilmiş veri hazırlamayı basitleştirebilir.

Not

Hazırlama kitaplığı, yerel ve yönetilen ortamlar arasında verileri iyileştirilmiş bir şekilde sıralamak için alternatif bir yol sağlar. Sıralama kitaplığı hakkında daha fazla bilgi için bkz . C++ dilinde Sıralamaya Genel Bakış. Hazırlama kitaplığı yalnızca veriler için kullanılabilir, işlevler için kullanılamaz.

PInvoke ve DllImport Özniteliği

Aşağıdaki örnekte bir Visual C++ programında kullanımı PInvoke gösterilmektedir. Yerel işlevi yerleştirir msvcrt.dll tanımlanır. DllImport özniteliği, puts 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'nin avantajları

  • Programın kullandığı yönetilmeyen API'ler için öznitelik bildirimleri yazmanız DLLImport gerekmez. Yalnızca üst bilgi dosyasını ekleyin ve içeri aktarma kitaplığıyla bağlantı sağlayın.

  • IJW mekanizması biraz daha hızlıdır (örneğin, geliştirici tarafından açıkça yapıldığından, IJW saplamalarının veri öğelerini sabitleme veya kopyalama gereksinimini denetlemesi gerekmez).

  • Performans sorunlarını açıkça gösterir. Bu durumda, Unicode dizesinden ANSI dizesine çeviri yaptığınız ve bir santral bellek ayırma ve serbest bırakma işlemine sahip olduğunuz gerçeği. Bu durumda, kodu IJW kullanarak yazan bir geliştirici çağırmanın _putws ve kullanmanın PtrToStringChars performans açısından daha iyi olacağını fark eder.

  • Aynı verileri kullanarak birçok yönetilmeyen API'yi çağırırsanız, bir kez sıralamak ve sıralanmış kopyayı geçirmek, her seferinde yeniden sıralamaktan çok daha verimlidir.

IJW'nin Dezavantajları

  • Sıralama, öznitelikler (genellikle uygun varsayılan değerlere sahip) yerine kodda açıkça belirtilmelidir.

  • Sıralama kodu satır içidir ve burada uygulama mantığının akışında daha invazivdir.

  • Açık hazırlama API'leri 32 bit ile 64 bit arasında taşınabilirlik türleri döndüreceği IntPtr için ek ToPointer çağrılar kullanmanız gerekir.

C++ tarafından sunulan belirli yöntem, bazı ek karmaşıklıklar karşılığında daha verimli ve açık bir yöntemdir.

Uygulama çoğunlukla yönetilmeyen veri türleri kullanıyorsa veya .NET Framework API'lerinden daha fazla yönetilmeyen API çağırıyorsa, IJW özelliğini kullanmanızı öneririz. Çoğunlukla yönetilen bir uygulamada zaman zaman yönetilmeyen API'yi çağırmak için seçim daha incedir.

Windows API'leri ile PInvoke

PInvoke, Windows'ta işlevleri çağırmak için kullanışlıdır.

Bu örnekte, Visual C++ programı Win32 API'sinin parçası olan MessageBox işleviyle 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 Merhaba Dünya! metnini içeren bir ileti kutusudur.

Hazırlama bilgileri, DLL'deki işlevleri aramak için PInvoke tarafından da kullanılır. user32.dll aslında hiçbir MessageBox işlevi yoktur, ancak CharSet=CharSet::Ansi, PInvoke'un Unicode sürümü olan MessageBoxW yerine ANSI sürümü olan MessageBoxA'yı kullanmasını sağlar. Genel olarak, .NET Framework dize nesnelerinin yerel Unicode biçiminden ANSI'ye çeviri ek yükünü ortadan kaldırdığından yönetilmeyen API'lerin Unicode sürümlerini kullanmanızı öneririz.

PInvoke Kullanılmadığında

PInvoke kullanmak DLL'lerdeki tüm C stili işlevler için uygun değildir. Örneğin, mylib.dll içinde aşağıdaki gibi bildirilen bir MakeSpecial işlevi olduğunu varsayalım:

char * MakeSpecial(char * pszString);

Visual C++ uygulamasında PInvoke 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 dizenin belleğini silemiyor olmamızdır. PInvoke aracılığıyla çağrılan diğer işlevler, kullanıcı tarafından serbest bırakılması gerekmeyen bir iç arabelleğe bir işaretçi döndürür. Bu durumda, IJW özelliğini kullanmak belirgin bir seçimdir.

PInvoke sınırlamaları

Parametre olarak aldığın yerel işlevden tam olarak aynı işaretçiyi döndüremezsiniz. Yerel bir işlev PInvoke tarafından ona sıralanmış işaretçiyi döndürürse, bellek bozulması ve özel durumlar oluşabilir.

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

Aşağıdaki örnekte bu sorun gösterilmektedir ve program doğru çıkışı vermiş gibi görünse de çıkış, serbest bırakılmış bellekten geliyordur.

// 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şkenleri Hazırlama

ile PInvoke, aynı forma sahip yönetilen ve C++ yerel temel türleri arasında sıralama gerekmez. Örneğin, Int32 ile int arasında veya Double ile double arasında sıralama gerekmez.

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

wtypes.h Visual C++ /clr ile Visual C++ Ortak dil çalışma zamanı
KULP boşluk* boşluk* IntPtr, UIntPtr
BYTE unsigned char unsigned char Bayt
KISA short short Int16
WORD imzasız short imzasız short UInt16
INT int int Int32
UINT unsigned int unsigned int UInt32
UZUN uzun uzun Int32
BOOL uzun ikili Boolean
DWORD imzasız long imzasız long UInt32
ULONG imzasız long imzasız long UInt32
CHAR char char Char
LPSTR Char* Dize ^ [in], StringBuilder ^ [in, out] Dize ^ [in], StringBuilder ^ [in, out]
LPCSTR const char * Dizgi^ String
LPWSTR wchar_t * Dize ^ [in], StringBuilder ^ [in, out] Dize ^ [in], StringBuilder ^ [in, out]
LPCWSTR const wchar_t * Dizgi^ String
FLOAT kayan noktalı sayı kayan noktalı sayı Tekli
ÇİFT çift çift Çift

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

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

MarshalAs özniteliğini kullanarak yerel işlevin bağımsız değişkenleri için sıralama bilgilerini belirtebilirsiniz. Dize * bağımsız değişkenlerini sıralamak için birkaç seçenek vardır: BStr, ANSIBStr, TBStr, LPStr, LPWStr ve LPTStr. Varsayılan değer LPStr'dir.

Bu örnekte, dize çift baytlık Unicode karakter dizesi (LPWStr) olarak sıralanır. Çıktı, Merhaba Dünya! çünkü sıralanmış dizenin ikinci bayt null olduğundan ve yerleştirir bunu dize sonu işaretçisi olarak yorumlar.

// 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. özniteliği, diziler gibi diğer veri türleriyle kullanılabilir.

Konu başlığında daha önce belirtildiği gibi, hazırlama kitaplığı yerel ve yönetilen ortamlar arasında verileri sıralamak için yeni, iyileştirilmiş bir yöntem sağlar. Daha fazla bilgi için bkz . C++'da Sıralamaya Genel Bakış.

Başarım Değerlendirmeleri

PInvoke'un çağrı başına 10 ile 30 x86 arası yönerge yükü vardır. Bu sabit maliyete ek olarak, sıralama ek ek yük oluşturur. Yönetilen ve yönetilmeyen kodda aynı temsile sahip olan kesilebilir türler arasında sıralama maliyeti yoktur. Örneğin, int ile Int32 arasında çeviri yapmak için bir maliyet yoktur.

Daha iyi performans için, çağrı başına daha az veri sıralayan daha fazla çağrı yerine mümkün olduğunca çok veri sıralayan daha az PInvoke çağrısına sahip olun.

Ayrıca bkz.

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