Perilaku Penyusunan Default
Penyusunan interop beroperasi pada aturan yang menentukan bagaimana data yang terkait dengan parameter metode berperilaku saat diteruskan 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 penyusun interopan. Bagian ini menyajikan informasi mendetail tentang array penyusunan, tipe Boolean, tipe char, delegasi, kelas, objek, string, dan struktur.
Catatan
Penyusunan jenis generik tidak didukung. Untuk informasi selengkapnya lihat, Interoperasi Menggunakan Jenis Generik.
Manajemen memori dengan penyusun interop
Penyusun interop selalu mencoba mengosongkan memori yang dialokasikan oleh kode yang tidak dikelola. Perilaku ini sesuai dengan aturan manajemen memori COM, tetapi berbeda dengan aturan yang mengatur C++ native.
Kebingungan dapat muncul jika Anda mengantisipasi perilaku C++ asli (tanpa pengosongan memori) saat menggunakan pemanggilan platform, yang secara otomatis mengosongkan memori untuk penunjuk. Misalnya, memanggil metode tidak terkelola berikut dari C++ DLL tidak secara otomatis mengosongkan memori apa pun.
Tanda tangan tidak terkelola
BSTR MethodOne (BSTR b) {
return b;
}
Namun, jika Anda mendefinisikan metode sebagai prototipe pemanggilan platform, ganti setiap jenis BSTR dengan jenis String, dan panggil MethodOne
, runtime bahasa umum mencoba untuk mengosongkan b
dua kali. Anda dapat mengubah perilaku penyusunan dengan menggunakan jenis IntPtr, bukan jenis String.
Runtime selalu menggunakan metode CoTaskMemFree pada Windows dan metode gratis pada platform lain untuk membebaskan memori. Jika memori yang Anda kerjakan tidak dialokasikan dengan metode CoTaskMemAlloc pada metode Windows atau malloc pada platform lain, 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 dikosongkan, seperti saat menggunakan fungsi GetCommandLine dari Kernel32.dll, yang menampilkan penunjuk ke memori kernel. Untuk detail tentang mengosongkan memori secara manual, lihat Sampel Buffer.
Penyusunan default untuk kelas
Kelas hanya dapat disusun oleh interop COM dan selalu disusun sebagai antarmuka. Dalam beberapa kasus, antarmuka yang digunakan untuk menyusun kelas dikenal sebagai antarmuka kelas. Untuk informasi tentang mengganti antarmuka kelas dengan antarmuka pilihan Anda, lihat Memperkenalkan antarmuka kelas.
Meneruskan Kelas ke COM
Ketika kelas terkelola diteruskan ke COM, penyusun interop secara otomatis membungkus kelas dengan proksi COM dan meneruskan antarmuka kelas yang dihasilkan oleh proksi ke pemanggilan 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.
Meneruskan Kelas ke Kode .NET
Rekan kelas biasanya tidak digunakan sebagai argumen metode dalam COM. Sebaliknya, antarmuka default biasanya diteruskan sebagai pengganti rekan kelas.
Jika antarmuka diteruskan ke kode terkelola, penyusun interop bertanggung jawab untuk membungkus antarmuka dengan pembungkus yang tepat dan meneruskan pembungkus ke metode terkelola. Menentukan pembungkus mana yang akan digunakan memang sulit. Setiap instans objek COM memiliki pembungkus tunggal yang unik, tidak peduli berapa banyak antarmuka yang diimplementasikan objek tersebut. 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, dua instans pembungkus akan dibuat.
Agar pembungkus mempertahankan jenis yang sama sepanjang masa pakainya, penyusun interop harus mengidentifikasi pembungkus yang benar saat pertama kali antarmuka yang diekspos oleh objek diteruskan melalui penyusun. Penyusun mengidentifikasi objek dengan melihat salah satu antarmuka yang diimplementasikan objek.
Misalnya, penyusun menentukan bahwa pembungkus kelas harus digunakan untuk membungkus antarmuka yang diteruskan ke kode terkelola. Ketika antarmuka pertama kali meneruskan penyusun, penyusun 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. Penyusun dapat dengan mudah mengidentifikasi antarmuka yang diekspos oleh objek terkelola dan mampu mencocokkan antarmuka dengan objek terkelola yang menyediakan implementasi. Objek terkelola kemudian diteruskan ke metode dan pembungkus tidak diperlukan.
Objek yang telah dibungkus mengimplementasikan antarmuka. Untuk menentukan apakah ini masalahnya, penyusun mengkueri objek untuk antarmuka IUnknown dan membandingkan antarmuka yang dikembalikan dengan antarmuka objek lain yang sudah dibungkus. Jika antarmuka sama dengan antarmuka pembungkus lain, objek memiliki identitas yang sama dan pembungkus yang ada diteruskan ke metode.
Jika antarmuka bukan dari objek yang diketahui, penyusun akan melakukan tindakan berikut:
Penyusun mengkueri objek untuk antarmuka IProvideClassInfo2. Jika disediakan, penyusun menggunakan CLSID yang dikembalikan dari IProvideClassInfo2.GetGUID untuk mengidentifikasi rekan kelas yang menyediakan antarmuka. Dengan CLSID, penyusun dapat menemukan pembungkus dari registri jika perakitan sebelumnya telah terdaftar.
Penyusun mengkueri antarmuka untuk antarmuka IProvideClassInfo. Jika disediakan, penyusun menggunakan ITypeInfo yang dikembalikan dari IProvideClassInfo.GetClassinfo untuk menentukan CLSID kelas yang mengekspos antarmuka. Penyusun dapat menggunakan CLSID untuk menemukan metadata untuk pembungkus.
Jika penyusun masih tidak dapat mengidentifikasi kelas, penyusun membungkus antarmuka dengan kelas pembungkus generik yang disebut System.__ComObject.
Penyusunan default untuk delegasi
Delegasi terkelola diatur sebagai antarmuka COM atau sebagai penunjuk fungsi, berdasarkan mekanisme panggilan:
Untuk pemanggilan platform, delegasi diatur sebagai penunjuk fungsi yang tidak dikelola secara default.
Untuk interop COM, delegasi diatur sebagai antarmuka COM jenis _Delegate secara default. Antarmuka _Delegate didefinisikan dalam pustaka jenis Mscorlib.tlb dan berisi metode Delegate.DynamicInvoke, yang memungkinkan Anda memanggil metode yang direferensikan oleh delegasi.
Tabel berikut menunjukkan opsi penyusunan untuk jenis data delegasi terkelola. Atribut MarshalAsAttribute menyediakan beberapa nilai enumerasi UnmanagedType untuk menyusun delegasi.
Jenis Enumerasi | Deskripsi format yang tidak terkelola |
---|---|
UnmanagedType.FunctionPtr | Penunjuk fungsi yang tidak dikelola. |
UnmanagedType.Interface | Antarmuka jenis _Delegate, seperti yang didefinisikan dalam Mscorlib.tlb. |
Pertimbangkan kode contoh 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 Masuk/Keluar.
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 didereferensi, sama seperti penunjuk fungsi lain yang tidak dikelola dapat didereferensi.
Dalam contoh ini, saat kedua delegasi disusun sebagai UnmanagedType.FunctionPtr, hasilnya adalah int
dan penunjuk ke int
. Karena jenis delegasi sedang disusun, 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.
Catatan
Referensi ke penunjuk fungsi ke delegasi terkelola yang dipertahankan oleh kode yang tidak dikelola tidak mencegah runtime bahasa umum melakukan pengumpulan sampah pada objek terkelola.
Misalnya, kode berikut salah karena referensi ke objek cb
, yang diteruskan ke metode SetChangeHandler
, tidak menjaga cb
tetap aktif di luar penggunaan metode Test
. Setelah objek cb
berada di dalam kumpulan sampah, penunjuk fungsi yang diteruskan ke SetChangeHandler
tidak valid lagi.
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 objek cb
tetap aktif selama penunjuk fungsi yang tidak dikelola sedang digunakan. Secara opsional, Anda dapat meminta kode yang tidak dikelola untuk memberi tahu kode yang dikelola saat 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;
}
}
Penyusunan default untuk jenis nilai
Sebagian besar jenis nilai, seperti bilangan bulat dan titik mengambang, dapat di-blitable dan tidak memerlukan penyusunan. Jenis non-blittable lainnya memiliki representasi yang berbeda dalam memori terkelola dan tidak terkelola serta memang memerlukan penyusunan. Jenis lain tetap memerlukan pemformatan eksplisit di seluruh batas interoperasi.
Bagian ini menyediakan informasi tentang jenis nilai berformat berikut:
Selain menjelaskan jenis yang diformat, topik ini mengidentifikasi Jenis Nilai Sistem yang memiliki perilaku penyusunan yang tidak biasa.
Jenis berformat adalah jenis kompleks yang berisi informasi yang secara eksplisit mengontrol tata letak anggotanya dalam memori. Informasi tata letak anggota disediakan menggunakan atribut StructLayoutAttribute. Tata letak dapat berupa salah satu dari nilai enumerasi LayoutKind berikut:
LayoutKind.Auto
Menunjukkan bahwa runtime bahasa umum bebas untuk mengurutkan ulang anggota jenis untuk efisiensi. Namun, ketika jenis nilai diteruskan ke kode yang tidak dikelola, tata letak anggota dapat diprediksi. Upaya untuk menyusun struktur tersebut secara otomatis menimbulkan pengecualian.
LayoutKind.Sequential
Menunjukkan bahwa anggota jenis harus diletakkan dalam memori yang tidak dikelola dalam urutan yang sama di mana mereka muncul dalam definisi jenis terkelola.
LayoutKind.Explicit
Menunjukkan bahwa anggota diletakkan sesuai dengan FieldOffsetAttribute yang disediakan dengan setiap bidang.
Jenis Nilai yang Digunakan dalam Pemanggilan Platform
Dalam contoh berikut, jenis Point
dan Rect
menyediakan informasi tata letak anggota 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 disusun ke kode yang tidak dikelola, jenis berformat ini disusun sebagai struktur gaya-C. Jenis ini menyediakan cara mudah untuk memanggil API tidak terkelola yang memiliki argumen struktur. Misalnya, struktur POINT
dan RECT
dapat diteruskan ke fungsi API Microsoft Windows PtInRect sebagai berikut:
BOOL PtInRect(const RECT *lprc, POINT pt);
Anda dapat meneruskan struktur menggunakan definisi pemanggilan 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 nilai Rect
harus diteruskan oleh referensi karena API yang tidak dikelola mengharapkan penunjuk ke RECT
untuk diteruskan ke fungsi. Jenis nilai Point
diteruskan oleh nilai karena API yang tidak dikelola mengharapkan POINT
untuk diteruskan pada tumpukan. Perbedaan yang tidak kentara ini sangat penting. Referensi diteruskan ke kode yang tidak dikelola sebagai penunjuk. Nilai diteruskan ke kode yang tidak dikelola pada tumpukan.
Catatan
Saat jenis berformat disusun sebagai struktur, hanya bidang dalam jenis yang dapat diakses. Jika jenis memiliki metode, properti, atau peristiwa, mereka tidak dapat diakses dari kode yang tidak dikelola.
Kelas juga dapat diatur ke kode yang tidak dikelola sebagai struktur gaya-C, asalkan mereka memiliki tata letak anggota yang tetap. Informasi tata letak anggota untuk suatu kelas juga disediakan dengan atribut StructLayoutAttribute. Perbedaan utama antara jenis nilai dengan tata letak tetap dan kelas dengan tata letak tetap adalah cara di mana mereka disusun ke kode yang tidak dikelola. Jenis nilai diteruskan berdasarkan nilai (pada tumpukan) dan akibatnya setiap perubahan yang dilakukan pada anggota jenis oleh penerima panggilan tidak dilihat oleh pemanggil. Jenis referensi diteruskan oleh referensi (referensi ke jenis diteruskan pada tumpukan); akibatnya, semua perubahan yang dilakukan pada anggota jenis blittable dari jenis oleh penerima panggilan dilihat oleh pemanggil.
Catatan
Jika jenis referensi memiliki anggota jenis non-bittable, konversi diperlukan dua kali: pertama kali saat argumen diteruskan ke sisi yang tidak dikelola dan kedua kalinya saat ditampilkan dari panggilan. Karena overhead yang ditambahkan ini, parameter Masuk/Keluar harus diterapkan secara eksplisit ke argumen jika pemanggil ingin melihat perubahan yang dibuat oleh penerima panggilan.
Dalam contoh berikut, kelas SystemTime
memiliki tata letak anggota berurutan dan dapat diteruskan ke fungsi GetSystemTime API Windows.
<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 didefinisikan sebagai berikut:
void GetSystemTime(SYSTEMTIME* SystemTime);
Definisi pemanggilan platform yang setara untuk GetSystemTime 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 berjenis argumen referensi karena SystemTime
merupakan kelas, bukan jenis nilai. Tidak seperti jenis nilai, kelas selalu diteruskan dengan referensi.
Contoh kode berikut menunjukkan kelas Point
yang berbeda yang memiliki metode yang disebut SetXY
. Karena jenis memiliki tata letak berurutan, jenis dapat diteruskan ke kode yang tidak dikelola dan disusun sebagai struktur. Namun, anggota SetXY
tidak dapat dipanggil dari kode yang tidak dikelola, meskipun objek diteruskan oleh 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 di Interop COM
Jenis yang diformat juga dapat diteruskan ke panggilan metode interop COM. Bahkan, saat diekspor ke pustaka jenis, jenis nilai secara otomatis dikonversi menjadi struktur. Seperti yang ditunjukkan dalam contoh berikut, jenis nilai Point
menjadi definisi jenis (typedef) dengan nama Point
. Semua referensi ke jenis nilai Point
di tempat lain dalam pustaka jenis diganti dengan typedef Point
.
Representasi pustaka jenis
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 yang digunakan untuk menyusun nilai dan referensi ke panggilan pemanggilan platform digunakan saat menyusun melalui antarmuka COM. Misalnya, saat instans jenis nilai Point
diteruskan dari .NET Framework ke COM, Point
diteruskan oleh nilai. Jika jenis nilai Point
diteruskan oleh referensi, penunjuk ke Point
diteruskan pada tumpukan. Penyusun interop tidak mendukung tingkat tidak langsung yang lebih tinggi (Titik **) dalam kedua arah.
Catatan
Struktur yang memiliki nilai enumerasi LayoutKind yang diatur ke Eksplisit 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 System.Int32 jenis nilai mewakili bentuk kotak ELEMENT_TYPE_I4. Sebagai ganti menyusun jenis-jenis ini sebagai struktur, seperti jenis-jenis berformat lainnya, Anda dapat menyusunnya dengan cara yang sama seperti jenis-jenis primitif yang mereka kotaki. System.Int32 karenanya disusun sebagai ELEMENT_TYPE_I4, bukan sebagai struktur yang berisi satu anggota dengan jenis panjang. Tabel berikut ini berisi daftar jenis nilai di namespace layanan Sistem yang merupakan representasi berkotak dari jenis 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 namespace layanan Sistem ditangani secara berbeda. Karena kode yang tidak terkelola sudah memiliki format yang mapan untuk jenis ini, penyusun memiliki aturan khusus untuk menyusunnya. Tabel berikut mencantumkan jenis nilai khusus di namespace Sistem, serta jenis tidak terkelola tempat mereka disusun.
Jenis nilai sistem | Jenis IDL |
---|---|
System.DateTime | TANGGAL |
System.Decimal | DECIMAL |
System.Guid | GUID |
System.Drawing.Color | OLE_COLOR |
Kode berikut menunjukkan definisi jenis yang tidak dikelola DATE, GUID, DECIMAL, dan OLE_COLOR di pustaka jenis Stdole2.
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 di antarmuka IValueTypes
terkelola.
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);
};