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ınPtrToStringChars
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 ekToPointer
ç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.