Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Interop marshalling beroperasi pada aturan yang menentukan bagaimana data yang terkait dengan parameter metode berperilaku saat melewati antara memori terkelola dan tidak terkelola. Aturan bawaan ini mengontrol aktivitas marshalling seperti transformasi jenis data, apakah penerima panggilan dapat mengubah data yang diteruskan ke dalamnya dan mengembalikan perubahan tersebut kepada pemanggil, dan dalam keadaan mana marshaller memberikan pengoptimalan performa.
Bagian ini mengidentifikasi karakteristik perilaku default dari layanan interop marshalling. Ini menyajikan informasi terperinci tentang pengelolaan array, jenis Boolean, tipe karakter, delegasi, kelas, objek, string, dan struktur.
Nota
Pengaturan jenis generik tidak didukung. Untuk informasi selengkapnya lihat, Mengoperasikan Menggunakan Jenis Generik.
Manajemen memori dengan interop marshaller
Marshaller interop selalu mencoba membebaskan memori yang dialokasikan oleh kode yang tidak dikelola. Perilaku ini mematuhi aturan manajemen memori COM, tetapi berbeda dari aturan yang mengatur C++asli.
Kebingungan mungkin timbul jika Anda mengharapkan perilaku C++ asli (tidak ada pembebasan memori) saat menggunakan panggilan platform, yang secara otomatis membebaskan memori untuk pointer. Misalnya, memanggil metode tidak terkelola berikut dari DLL C++ tidak secara otomatis membebaskan memori apa pun.
Tanda tangan tidak terkelola
BSTR MethodOne (BSTR b) {
return b;
}
Namun, jika Anda mendefinisikan metode sebagai prototipe pemanggilan platform, ganti setiap BSTR jenis dengan String jenis, dan panggil MethodOne, runtime bahasa umum akan mencoba membebaskan b dua kali. Anda dapat mengubah perilaku marshalling dengan menggunakan jenis IntPtr dibandingkan dengan jenis String.
Runtime selalu menggunakan CoTaskMemFree metode pada Windows dan free metode pada platform lain untuk membebaskan memori. Jika memori yang Anda kerjakan tidak dialokasikan menggunakan metode CoTaskMemAlloc pada Windows atau menggunakan metode malloc pada platform lainnya, Anda harus menggunakan IntPtr dan membebaskan memori secara manual menggunakan metode yang sesuai. Demikian pula, Anda dapat menghindari pengosongan memori otomatis dalam situasi di mana memori tidak boleh dibebaskan, seperti saat menggunakan GetCommandLine fungsi dari Kernel32.dll, yang mengembalikan pointer ke memori kernel. Untuk detail tentang mengosongkan memori secara manual, lihat Sampel Buffer.
Marshalling bawaan untuk kelas-kelas
Kelas hanya dapat dinaungi oleh interop COM dan selalu dinaungi sebagai antarmuka. Dalam beberapa kasus, antarmuka yang digunakan untuk pengaturan kelas dikenal sebagai antarmuka kelas. Untuk informasi tentang mengesampingkan antarmuka kelas dengan antarmuka pilihan Anda, lihat Memperkenalkan antarmuka kelas.
Meneruskan Kelas ke COM
Ketika kelas terkelola diteruskan ke COM, marshaller interop secara otomatis membungkus kelas dengan proksi COM dan meneruskan antarmuka kelas yang dihasilkan oleh proksi ke panggilan metode COM. Proksi kemudian mendelegasikan semua panggilan pada antarmuka kelas kembali ke objek terkelola. Proksi juga mengekspos antarmuka lain yang tidak diimplementasikan secara eksplisit oleh kelas. Proksi secara otomatis mengimplementasikan antarmuka seperti IUnknown dan IDispatch atas nama kelas.
Mengoperkan Kelas ke Kode .NET
Biasanya, coclass tidak digunakan sebagai argumen metode dalam COM. Sebaliknya, antarmuka default biasanya diteruskan untuk menggantikan coclass.
Ketika antarmuka diteruskan ke kode terkelola, interop marshaller bertanggung jawab untuk membungkus antarmuka dengan pembungkus yang tepat dan meneruskan pembungkus ke metode terkelola. Menentukan pembungkus mana yang akan digunakan bisa sulit. Setiap instans objek COM memiliki satu pembungkus unik, tidak peduli berapa banyak antarmuka yang diterapkan objek. Misalnya, satu objek COM yang mengimplementasikan lima antarmuka berbeda hanya memiliki satu pembungkus. Pembungkus yang sama mengekspos kelima antarmuka. Jika dua instans objek COM dibuat, maka dua instans objek pembungkus dibuat.
Agar pembungkus mempertahankan jenis yang sama sepanjang masa pakainya, marshaller interop harus mengidentifikasi pembungkus yang benar saat pertama kali antarmuka yang diekspos oleh objek diteruskan melalui marshaller. Marshaller mengidentifikasi objek dengan melihat salah satu antarmuka yang diterapkan objek.
Misalnya, marshaller menentukan bahwa pembungkus kelas harus digunakan untuk membungkus antarmuka yang diteruskan ke dalam kode terkelola. Ketika antarmuka pertama kali diteruskan melalui marshaller, marshaller memeriksa apakah antarmuka berasal dari objek yang diketahui. Pemeriksaan ini terjadi dalam dua situasi:
Antarmuka sedang diimplementasikan oleh objek terkelola lain yang diteruskan ke COM di tempat lain. Marshaller dapat dengan mudah mengidentifikasi antarmuka yang diekspos oleh objek terkelola dan dapat mencocokkan antarmuka dengan objek terkelola yang menyediakan implementasi. Objek terkelola kemudian diteruskan ke metode dan tidak diperlukan pembungkus.
Objek yang telah dibungkus sedang mengimplementasikan antarmuka. Untuk menentukan apakah ini benar-benar terjadi, marshaller meneruskan permintaan kepada objek untuk antarmuka
IUnknown-nya dan membandingkan antarmuka yang dikembalikan dengan antarmuka objek lain yang sudah dibungkus. Jika antarmukanya sama dengan pembungkus lain, objek memiliki identitas yang sama dan pembungkus yang ada diteruskan ke metode .
Jika antarmuka bukan dari objek yang diketahui, marshaller melakukan hal berikut:
Marshaller meminta objek untuk antarmuka IProvideClassInfo2 . Jika disediakan, marshaller menggunakan CLSID yang dikembalikan dari IProvideClassInfo2.GetGUID untuk mengidentifikasi kolase yang menyediakan antarmuka. Dengan CLSID, marshaller dapat menemukan pembungkus dari registri jika rakitan sebelumnya telah terdaftar.
Marshaller mengkueri antarmuka yang terkait dengan
IProvideClassInfo. Jika disediakan, marshaller menggunakanITypeInfoyang dikembalikan dari IProvideClassInfo.GetClassinfo untuk menentukan CLSID dari kelas yang mengekspos antarmuka. Marshaller dapat menggunakan CLSID untuk menemukan metadata untuk pembungkus.Jika marshaller masih tidak dapat mengidentifikasi kelas, marshaller membungkus antarmuka dengan kelas pembungkus generik yang disebut System.__ComObject.
Pemrosesan default untuk delegat
Delegasi terkelola dinamai sebagai antarmuka COM atau sebagai penunjuk fungsi, berdasarkan mekanisme panggilan:
Untuk pemanggilan platform, sebuah delegate diubah menjadi penunjuk fungsi yang tidak dikelola secara default.
Untuk interop COM, delegasi dinamai sebagai antarmuka COM jenis
_Delegatesecara default. Antarmuka_Delegatedidefinisikan dalam pustaka jenis Mscorlib.tlb dan berisi metode Delegate.DynamicInvoke, yang memungkinkan Anda memanggil metode yang direferensikan delegasi.
Tabel berikut ini memperlihatkan opsi marshalling untuk tipe data delegasi terkelola. Atribut MarshalAsAttribute ini menyediakan beberapa UnmanagedType nilai enumerasi untuk marshal delegate.
| Jenis enumerasi | Deskripsi format tidak terkelola |
|---|---|
| UnmanagedType.FunctionPtr | Penunjuk fungsi yang tidak dikelola. |
| UnmanagedType.Interface | Antarmuka jenis _Delegate, seperti yang didefinisikan dalam Mscorlib.tlb. |
Pertimbangkan contoh kode berikut di mana metode DelegateTestInterface diekspor ke pustaka jenis COM. Perhatikan bahwa hanya delegasi yang ditandai dengan kata kunci ref (atau ByRef) yang diteruskan sebagai parameter masukan/keluaran.
using System;
using System.Runtime.InteropServices;
public interface DelegateTest {
void m1(Delegate d);
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d);
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);
}
Representasi pustaka jenis
importlib("mscorlib.tlb");
interface DelegateTest : IDispatch {
[id(…)] HRESULT m1([in] _Delegate* d);
[id(…)] HRESULT m2([in] _Delegate* d);
[id(…)] HRESULT m3([in, out] _Delegate** d);
[id()] HRESULT m4([in] int d);
[id()] HRESULT m5([in, out] int *d);
};
Penunjuk fungsi dapat didereferensikan, sama seperti penunjuk fungsi lain yang tidak dikelola dapat didereferensikan.
Dalam contoh ini, ketika kedua delegasi dimarshall sebagai UnmanagedType.FunctionPtr, hasilnya adalah int dan sebuah pointer ke int. Karena tipe delegasi sedang diproses, int di sini mewakili penunjuk ke 'void' (void*), yang merupakan alamat delegasi dalam memori. Dengan kata lain, hasil ini khusus untuk sistem Windows 32-bit, karena int di sini mewakili ukuran penunjuk fungsi.
Nota
Referensi ke pointer fungsi ke delegasi terkelola yang dipegang oleh kode tidak terkelola tidak mencegah Common Language Runtime melakukan pengumpulan sampah pada objek terkelola.
Misalnya, kode berikut salah karena objek cb referensi, yang diteruskan ke metode SetChangeHandler, tidak membuat cb tetap hidup di luar masa pakai metode Test. Setelah objek dikumpulkan oleh sistem pengumpul sampah, penunjuk fungsi yang diberikan ke cb tidak lagi valid.
public class ExternalAPI {
[DllImport("External.dll")]
public static extern void SetChangeHandler(
[MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);
}
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);
public class CallBackClass {
public bool OnChange(string S){ return true;}
}
internal class DelegateTest {
public static void Test() {
CallBackClass cb = new CallBackClass();
// Caution: The following reference on the cb object does not keep the
// object from being garbage collected after the Main method
// executes.
ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
}
}
Untuk mengimbangi pengumpulan sampah yang tidak terduga, pemanggil harus memastikan bahwa cb objek tetap hidup selama penunjuk fungsi yang tidak dikelola sedang digunakan. Secara opsional, Anda dapat meminta kode yang tidak dikelola memberi tahu kode terkelola ketika penunjuk fungsi tidak lagi diperlukan, seperti yang ditunjukkan contoh berikut.
internal class DelegateTest {
CallBackClass cb;
// Called before ever using the callback function.
public static void SetChangeHandler() {
cb = new CallBackClass();
ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
}
// Called after using the callback function for the last time.
public static void RemoveChangeHandler() {
// The cb object can be collected now. The unmanaged code is
// finished with the callback function.
cb = null;
}
}
Pengaturan bawaan untuk jenis nilai
Sebagian besar jenis nilai, seperti bilangan bulat dan angka floating-point, bersifat blittable dan tidak memerlukan marshalling. Jenis non-blittable lainnya memiliki representasi yang berbeda dalam memori terkelola dan tidak terkelola, serta membutuhkan marshalling. Masih jenis lain memerlukan pemformatan eksplisit di seluruh batas interoperabilitas.
Bagian ini menyediakan informasi tentang jenis nilai yang diformat berikut ini:
Selain menjelaskan jenis yang diformat, topik ini mengidentifikasi Jenis Nilai Sistem yang memiliki perilaku marshalling yang tidak biasa.
Jenis yang diformat adalah jenis kompleks yang berisi informasi yang secara eksplisit mengontrol tata letak anggotanya dalam memori. Informasi tata letak anggota disediakan menggunakan StructLayoutAttribute atribut . Tata letak bisa menjadi salah satu nilai enumerasi berikut LayoutKind :
LayoutKind.Auto
Menunjukkan bahwa runtime bahasa pemrograman umum bebas untuk mengatur ulang anggota tipe demi efisiensi. Namun, ketika jenis nilai diteruskan ke kode yang tidak dikelola, tata letak anggota dapat diprediksi. Upaya untuk melakukan pemrosesan struktur seperti itu secara otomatis menyebabkan pengecualian.
LayoutKind.Sequential
Menunjukkan bahwa anggota tipe akan ditata dalam memori tak dikelola secara berurutan seperti dalam definisi tipe terkelola.
LayoutKind.Explicit
Menunjukkan bahwa anggota diatur sesuai dengan FieldOffsetAttribute yang disediakan untuk setiap bidang.
Jenis Tipe Nilai yang Digunakan dalam Platform Invoke
Dalam contoh berikut, jenis Point dan Rect menyediakan informasi tata letak member menggunakan StructLayoutAttribute.
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> Public Structure Point
Public x As Integer
Public y As Integer
End Structure
<StructLayout(LayoutKind.Explicit)> Public Structure Rect
<FieldOffset(0)> Public left As Integer
<FieldOffset(4)> Public top As Integer
<FieldOffset(8)> Public right As Integer
<FieldOffset(12)> Public bottom As Integer
End Structure
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}
[StructLayout(LayoutKind.Explicit)]
public struct Rect {
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}
Ketika diangkut ke kode yang tidak dikelola, jenis yang diformat ini diproses sebagai struktur gaya C. Ini menyediakan cara mudah untuk memanggil API yang tidak dikelola yang memiliki argumen struktur. Misalnya, POINT dan RECT struktur dapat diteruskan ke fungsi Microsoft Windows API PtInRect sebagai berikut:
BOOL PtInRect(const RECT *lprc, POINT pt);
Anda dapat meneruskan struktur menggunakan definisi panggilan platform berikut:
Friend Class NativeMethods
Friend Declare Auto Function PtInRect Lib "User32.dll" (
ByRef r As Rect, p As Point) As Boolean
End Class
internal static class NativeMethods
{
[DllImport("User32.dll")]
internal static extern bool PtInRect(ref Rect r, Point p);
}
Jenis Rect nilai harus dilewatkan melalui referensi karena API unmanaged mengharapkan penunjuk ke RECT untuk diteruskan ke fungsi. Tipe nilai Point diteruskan berdasarkan nilai karena API yang tidak dikelola mengharapkan POINT diteruskan di tumpukan. Perbedaan yang halus ini sangat penting. Referensi diteruskan ke kode tidak terkelola sebagai pointer. Nilai diteruskan ke kode yang tidak dikelola pada tumpukan.
Nota
Ketika tipe diformat sebagai struktur, hanya bidang-bidang dalam tipe tersebut yang dapat diakses. Jika jenis memiliki metode, properti, atau peristiwa, mereka tidak dapat diakses dari kode yang tidak dikelola.
Kelas juga dapat dinaungi ke kode yang tidak dikelola sebagai struktur gaya C, asalkan mereka memiliki tata letak anggota tetap. Informasi tata letak anggota untuk kelas juga disediakan dengan StructLayoutAttribute atribut . Perbedaan utama antara jenis nilai dengan tata letak tetap dan kelas dengan tata letak tetap adalah cara di mana mereka dinaungi ke kode yang tidak dikelola. Jenis nilai diteruskan oleh nilai (pada tumpukan) dan akibatnya setiap perubahan yang dilakukan pada anggota jenis oleh penerima panggilan tidak dilihat oleh pemanggil. Tipe referensi diteruskan berdasarkan referensi (sebuah referensi terhadap tipe diteruskan pada tumpukan); akibatnya, semua perubahan yang dilakukan pada anggota tipe blittable oleh penelpon dapat dilihat oleh pemanggil.
Nota
Jika jenis referensi memiliki anggota jenis non-blittable, konversi diperlukan dua kali: pertama kali ketika argumen diteruskan ke sisi unmanaged dan kedua kalinya saat kembali dari panggilan. Karena overhead tambahan ini, parameter In/Out harus diterapkan secara eksplisit ke argumen jika pemanggil ingin melihat perubahan yang dibuat oleh fungsi yang dipanggil.
Dalam contoh berikut, SystemTime kelas memiliki tata letak anggota berurutan dan dapat diteruskan ke fungsi Windows API GetSystemTime .
<StructLayout(LayoutKind.Sequential)> Public Class SystemTime
Public wYear As System.UInt16
Public wMonth As System.UInt16
Public wDayOfWeek As System.UInt16
Public wDay As System.UInt16
Public wHour As System.UInt16
Public wMinute As System.UInt16
Public wSecond As System.UInt16
Public wMilliseconds As System.UInt16
End Class
[StructLayout(LayoutKind.Sequential)]
public class SystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
Fungsi GetSystemTime ini didefinisikan sebagai berikut:
void GetSystemTime(SYSTEMTIME* SystemTime);
Definisi GetSystemTime pemanggilan platform yang setara adalah sebagai berikut:
Friend Class NativeMethods
Friend Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (
ByVal sysTime As SystemTime)
End Class
internal static class NativeMethods
{
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
internal static extern void GetSystemTime(SystemTime st);
}
Perhatikan bahwa argumen SystemTime tidak ditetapkan sebagai argumen referensi karena SystemTime merupakan kelas, bukan tipe nilai. Tidak seperti jenis nilai, kelas selalu dilewatkan sebagai referensi.
Contoh kode berikut menunjukkan kelas berbeda Point yang memiliki metode yang disebut SetXY. Karena tipe memiliki tata letak berurutan, itu dapat diteruskan ke kode yang tidak dikelola dan dikemas sebagai struktur. Namun, SetXY anggota tidak dapat dipanggil dari kode yang tidak dikelola, bahkan jika objek diteruskan melalui referensi.
<StructLayout(LayoutKind.Sequential)> Public Class Point
Private x, y As Integer
Public Sub SetXY(x As Integer, y As Integer)
Me.x = x
Me.y = y
End Sub
End Class
[StructLayout(LayoutKind.Sequential)]
public class Point {
int x, y;
public void SetXY(int x, int y){
this.x = x;
this.y = y;
}
}
Jenis Nilai yang Digunakan dalam Interop COM
Jenis yang diformat juga dapat diteruskan ke panggilan metode interop COM. Bahkan, ketika diekspor ke pustaka jenis, jenis nilai secara otomatis dikonversi ke struktur. Seperti yang ditunjukkan oleh contoh berikut Point, tipe nilai menjadi definisi tipe (typedef) dengan nama Point. Semua referensi ke tipe nilai Point di tempat lain dalam pustaka tipe diganti dengan typedef Point.
Representasi perpustakaan tipe
typedef struct tagPoint {
int x;
int y;
} Point;
interface _Graphics {
…
HRESULT SetPoint ([in] Point p)
HRESULT SetPointRef ([in,out] Point *p)
HRESULT GetPoint ([out,retval] Point *p)
}
Aturan yang sama digunakan untuk pengumpulan nilai dan referensi ke panggilan invoke platform digunakan saat pengumpulan melalui antarmuka COM. Misalnya, ketika instans Point jenis nilai diteruskan dari .NET Framework ke COM, Point diteruskan berdasarkan nilai. Jika jenis nilai Point diteruskan melalui referensi, maka penunjuk ke Point akan diteruskan di tumpukan. Marshaller interop tidak mendukung tingkat tidak langsung yang lebih tinggi (Titik **) di kedua arah.
Nota
Struktur yang memiliki LayoutKind nilai enumerasi diatur ke Explicit tidak dapat digunakan dalam interop COM karena pustaka jenis yang diekspor tidak dapat mengekspresikan tata letak eksplisit.
Jenis Nilai Sistem
Namespace System memiliki beberapa jenis nilai yang mewakili bentuk kotak dari jenis primitif runtime. Misalnya, struktur jenis System.Int32 nilai mewakili bentuk ELEMENT_TYPE_I4 dalam kotak. Daripada memproses jenis ini sebagai struktur, seperti jenis berformat lainnya, Anda memprosesnya dengan cara yang sama seperti jenis primitif yang mereka bungkus. Oleh karena itu System.Int32 dimarshal sebagai ELEMENT_TYPE_I4 bukannya struktur yang berisi satu anggota jenis long. Tabel berikut berisi daftar tipe nilai di namespace System yang merupakan representasi berbentuk kotak dari tipe primitif.
| Jenis nilai sistem | Jenis elemen |
|---|---|
| System.Boolean | ELEMENT_TYPE_BOOLEAN |
| System.SByte | ELEMENT_TYPE_I1 |
| System.Byte | ELEMENT_TYPE_UI1 |
| System.Char | ELEMENT_TYPE_CHAR |
| System.Int16 | ELEMENT_TYPE_I2 |
| System.UInt16 | ELEMENT_TYPE_U2 |
| System.Int32 | ELEMENT_TYPE_I4 |
| System.UInt32 | ELEMENT_TYPE_U4 |
| System.Int64 | ELEMENT_TYPE_I8 |
| System.UInt64 | ELEMENT_TYPE_U8 |
| System.Single | ELEMENT_TYPE_R4 |
| System.Double | ELEMENT_TYPE_R8 |
| System.String | ELEMENT_TYPE_STRING |
| System.IntPtr | ELEMENT_TYPE_I |
| System.UIntPtr | ELEMENT_TYPE_U |
Beberapa jenis nilai lain di System namespace ditangani secara berbeda. Karena kode yang tidak dikelola sudah memiliki format yang mapan untuk tipe-tipe ini, pemroses memiliki aturan khusus untuk mengolahnya. Tabel berikut mencantumkan jenis nilai khusus di System namespace, serta jenis tidak terkelola ke mana mereka dimarshall.
| Jenis nilai sistem | Jenis IDL |
|---|---|
| System.DateTime | TANGGAL |
| System.Decimal | DESIMAL |
| System.Guid | GUID |
| System.Drawing.Color | OLE_COLOR |
Kode berikut menunjukkan definisi jenis DATE, GUID, DECIMAL, dan OLE_COLOR di pustaka jenis Stdole2 yang tidak dikelola.
Representasi pustaka jenis
typedef double DATE;
typedef DWORD OLE_COLOR;
typedef struct tagDEC {
USHORT wReserved;
BYTE scale;
BYTE sign;
ULONG Hi32;
ULONGLONG Lo64;
} DECIMAL;
typedef struct tagGUID {
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[ 8 ];
} GUID;
Kode berikut menunjukkan definisi yang sesuai dalam antarmuka terkelola IValueTypes .
Public Interface IValueTypes
Sub M1(d As System.DateTime)
Sub M2(d As System.Guid)
Sub M3(d As System.Decimal)
Sub M4(d As System.Drawing.Color)
End Interface
public interface IValueTypes {
void M1(System.DateTime d);
void M2(System.Guid d);
void M3(System.Decimal d);
void M4(System.Drawing.Color d);
}
Representasi pustaka jenis
[…]
interface IValueTypes : IDispatch {
HRESULT M1([in] DATE d);
HRESULT M2([in] GUID d);
HRESULT M3([in] DECIMAL d);
HRESULT M4([in] OLE_COLOR d);
};