Praktik Pengodian COM
Topik ini menjelaskan cara untuk membuat kode COM Anda lebih efektif dan kuat.
The __uuidof Operator
Saat membuat program, Anda mungkin mendapatkan kesalahan linker yang mirip dengan yang berikut ini:
unresolved external symbol "struct _GUID const IID_IDrawable"
Kesalahan ini berarti bahwa konstanta GUID dideklarasikan dengan tautan eksternal (ekstern ), dan linker tidak dapat menemukan definisi konstanta. Nilai konstanta GUID biasanya diekspor dari file pustaka statis. Jika Anda menggunakan Microsoft Visual C++, Anda dapat menghindari kebutuhan untuk menautkan pustaka statis dengan menggunakan operator __uuidof. Operator ini adalah ekstensi bahasa Microsoft. Ini mengembalikan nilai GUID dari ekspresi. Ekspresi dapat berupa nama jenis antarmuka, nama kelas, atau penunjuk antarmuka. Dengan menggunakan __uuidof, Anda dapat membuat objek Dialog Item Umum sebagai berikut:
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
__uuidof(pFileOpen), reinterpret_cast<void**>(&pFileOpen));
Pengkompilasi mengekstrak nilai GUID dari header, sehingga tidak ada ekspor pustaka yang diperlukan.
Nota
Nilai GUID dikaitkan dengan nama jenis dengan mendeklarasikan __declspec(uuid( ... ))
di header. Untuk informasi selengkapnya, lihat dokumentasi untuk __declspec dalam dokumentasi Visual C++.
Makro IID_PPV_ARGS
Kami melihat bahwaCoCreateInstancedanQueryInterfacememerlukan koercing parameter akhir ke jenis batal** . Ini menciptakan potensi ketidakcocokan jenis. Pertimbangkan fragmen kode berikut:
// Wrong!
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(
__uuidof(FileOpenDialog),
NULL,
CLSCTX_ALL,
__uuidof(IFileDialogCustomize), // The IID does not match the pointer type!
reinterpret_cast<void**>(&pFileOpen) // Coerce to void**.
);
Kode ini meminta antarmuka IFileDialogCustomize, tetapi meneruskan penunjuk IFileOpenDialog. Ekspresi reinterpret_cast menghindari sistem jenis C++, sehingga pengkompilasi tidak akan menangkap kesalahan ini. Dalam kasus terbaik, jika objek tidak mengimplementasikan antarmuka yang diminta, panggilan hanya gagal. Dalam kasus terburuk, fungsi berhasil dan Anda memiliki penunjuk yang tidak cocok. Dengan kata lain, jenis penunjuk tidak cocok dengan vtable aktual dalam memori. Seperti yang dapat Anda bayangkan, tidak ada yang baik dapat terjadi pada saat itu.
Nota
vtable (tabel metode virtual) adalah tabel penunjuk fungsi. Vtable adalah cara COM mengikat panggilan metode ke implementasinya pada waktu proses. Tidak kebetulan, vtable adalah bagaimana sebagian besar pengkompilasi C++ menerapkan metode virtual.
Makro IID_PPV_ARGS membantu menghindari kelas kesalahan ini. Untuk menggunakan makro ini, ganti kode berikut:
__uuidof(IFileDialogCustomize), reinterpret_cast<void**>(&pFileOpen)
dengan ini:
IID_PPV_ARGS(&pFileOpen)
Makro secara otomatis menyisipkan __uuidof(IFileOpenDialog)
untuk pengidentifikasi antarmuka, sehingga dijamin cocok dengan jenis penunjuk. Berikut adalah kode yang dimodifikasi (dan benar):
// Right.
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
IID_PPV_ARGS(&pFileOpen));
Anda bisa menggunakan makro yang sama dengan QueryInterface:
IFileDialogCustomize *pCustom;
hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom));
Pola SafeRelease
Penghitungan referensi adalah salah satu hal dalam pemrograman yang pada dasarnya mudah, tetapi juga melelahkan, yang membuatnya mudah untuk salah. Kesalahan umum meliputi:
- Gagal merilis penunjuk antarmuka saat Anda selesai menggunakannya. Kelas bug ini akan menyebabkan program Anda bocor memori dan sumber daya lainnya, karena objek tidak dihancurkan.
- MemanggilRilisdengan pointer yang tidak valid. Misalnya, kesalahan ini dapat terjadi jika objek tidak pernah dibuat. Kategori bug ini mungkin akan menyebabkan program Anda mengalami crash.
- Dereferensi penunjuk antarmuka setelahRilisdipanggil. Bug ini dapat menyebabkan program Anda mengalami crash. Lebih buruk lagi, ini dapat menyebabkan program Anda crash secara acak di lain waktu, sehingga sulit untuk melacak kesalahan asli.
Salah satu cara untuk menghindari bug ini adalah dengan memanggilRilismelalui fungsi yang melepaskan pointer dengan aman. Kode berikut menunjukkan fungsi yang melakukan ini:
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
Fungsi ini mengambil penunjuk antarmuka COM sebagai parameter dan melakukan hal berikut:
- Memeriksa apakah pointer NULL.
- MemanggilRilisjika pointer tidak NULL.
- Mengatur pointer ke NULL.
Berikut adalah contoh cara menggunakan SafeRelease
:
void UseSafeRelease()
{
IFileOpenDialog *pFileOpen = NULL;
HRESULT hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen));
if (SUCCEEDED(hr))
{
// Use the object.
}
SafeRelease(&pFileOpen);
}
Jika CoCreateInstance berhasil, panggilan ke SafeRelease
merilis pointer. Jika CoCreateInstance gagal, pFileOpen tetap NULL. Fungsi SafeRelease
memeriksa ini dan melewati panggilan keRilis.
Juga aman untuk memanggil SafeRelease
lebih dari sekali pada pointer yang sama, seperti yang ditunjukkan di sini:
// Redundant, but OK.
SafeRelease(&pFileOpen);
SafeRelease(&pFileOpen);
COM Smart Pointers
Fungsi SafeRelease
berguna, tetapi mengharuskan Anda untuk mengingat dua hal:
- Inisialisasi setiap penunjuk antarmuka untuk null.
- Panggil
SafeRelease
sebelum setiap pointer keluar dari cakupan.
Sebagai programmer C++, Anda mungkin berpikir bahwa Anda tidak perlu mengingat salah satu hal ini. Lagipula, itulah sebabnya C++ memiliki konstruktor dan destruktor. Akan lebih baik untuk memiliki kelas yang membungkus penunjuk antarmuka yang mendasar dan secara otomatis menginisialisasi dan melepaskan pointer. Dengan kata lain, kita menginginkan sesuatu seperti ini:
// Warning: This example is not complete.
template <class T>
class SmartPointer
{
T* ptr;
public:
SmartPointer(T *p) : ptr(p) { }
~SmartPointer()
{
if (ptr) { ptr->Release(); }
}
};
Definisi kelas yang ditampilkan di sini tidak lengkap, dan tidak dapat digunakan seperti yang ditunjukkan. Minimal, Anda harus menentukan konstruktor salinan, operator penugasan, dan cara untuk mengakses penunjuk COM yang mendasar. Untungnya, Anda tidak perlu melakukan salah satu pekerjaan ini, karena Microsoft Visual Studio sudah menyediakan kelas penunjuk cerdas sebagai bagian dari Pustaka Templat Aktif (ATL).
Kelas penunjuk pintar ATL diberi nama CComPtr. (Ada juga kelas CComQIPtr, yang tidak dibahas di sini.) Berikut adalah contoh Buka Kotak Dialog ditulis ulang untuk menggunakan CComPtr.
#include <windows.h>
#include <shobjidl.h>
#include <atlbase.h> // Contains the declaration of CComPtr.
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED |
COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
CComPtr<IFileOpenDialog> pFileOpen;
// Create the FileOpenDialog object.
hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
if (SUCCEEDED(hr))
{
// Show the Open dialog box.
hr = pFileOpen->Show(NULL);
// Get the file name from the dialog box.
if (SUCCEEDED(hr))
{
CComPtr<IShellItem> pItem;
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr))
{
PWSTR pszFilePath;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
// Display the file name to the user.
if (SUCCEEDED(hr))
{
MessageBox(NULL, pszFilePath, L"File Path", MB_OK);
CoTaskMemFree(pszFilePath);
}
}
// pItem goes out of scope.
}
// pFileOpen goes out of scope.
}
CoUninitialize();
}
return 0;
}
Perbedaan utama antara kode ini dan contoh aslinya adalah bahwa versi ini tidak secara eksplisit memanggil Rilis. Ketika instans CComPtr keluar dari cakupan, destruktor memanggil Release pada pointer yang mendasar.
CComPtr adalah templat kelas. Argumen templat adalah jenis antarmuka COM. Secara internal, CComPtr memegang pointer jenis tersebut. CComPtr mengambil alih operator - operator>() dan &() sehingga kelas bertindak seperti pointer yang mendasar. Misalnya, kode berikut setara dengan memanggil metode IFileOpenDialog::Show secara langsung:
hr = pFileOpen->Show(NULL);
CComPtr juga mendefinisikan metode CComPtr::CoCreateInstance, yang memanggil fungsi com CoCreateInstance dengan beberapa nilai parameter default. Satu-satunya parameter yang diperlukan adalah pengidentifikasi kelas, seperti yang ditunjukkan contoh berikutnya:
hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
Metode CComPtr::CoCreateInstance disediakan murni sebagai kenyamanan; Anda masih dapat memanggil fungsiCOMCoCreateInstance, jika anda mau.
Depan
Penanganan Kesalahan di COM