2017 年 3 月

第 32 卷,第 3 期

本文章是由機器翻譯。

C++ - 透過 CComSafeArray 以 C++ 簡化安全陣列程式設計

Giovanni Dicanio | 2017 年 3 月

通常會開發複雜的軟體系統建置以不同語言撰寫的元件。例如,您可能有一些內嵌於 C 介面、 動態連結程式庫 (DLL) 或 COM 元件中的 c + + 程式碼高效能。或者,您可以撰寫 Windows 服務會公開某些 COM 介面的 c + + 中。在 C# 撰寫的用戶端可以與該服務,透過 COM interop 互動。

通常,您想要交換的陣列,這些元件之間的表單中的部分資料。例如,您可能有某些 c + + 元件,與一些硬體互動而產生的資料,如同代表讀取的輸入裝置的影像像素的位元組陣列或一組代表度量讀取感應器的浮點數陣列。或者,您可能必須在與其他低階模組互動,並傳回您想要使用 GUI 以 C# 或指令碼語言撰寫的用戶端中的字串陣列的 c + + 撰寫的 Windows 服務。跨模組界限傳遞該資料並不簡單,而且需要設計完善且經過精心設計的資料結構使用。

Windows 程式設計平台提供可用於此用途的簡便易用的已備妥資料結構︰ SAFEARRAY,其定義,請參閱 Windows 開發人員中心 (bit.ly/2fLXY6K)。基本上,SAFEARRAY 資料結構描述特定的執行個體的安全陣列,指定屬性,例如其數量的維度和實際安全陣列資料的指標。安全陣列通常會處理透過指向它的 SAFEARRAY 描述元的程式碼中也就是 SAFEARRAY *。用於管理安全的陣列,例如 SafeArrayCreate 和 SafeArrayDestroy 的建立和解構和其他函式來鎖定的安全陣列執行個體和安全地存取其資料也有 C 介面 Windows Api。如需詳細資訊的 SAFEARRAY C 資料結構,以及一些其原生 C 介面 Api,請參閱本文中,「 簡介 SAFEARRAY 資料結構 」 線上同一系列文件片段 (msdn.com/magazine/mt778923)。

不過,c + + 程式設計人員,而是在 C 介面層級,它是使用較高層級的 c + + 類別,例如 Active Template Library (ATL) ccomsafearray 的 Safearray 更為方便。

在本文中,我將討論 c + + 程式碼範例的不斷增加的複雜性來建立安全儲存不同種類的資料會使用 ATL helper 類別,包括 ccomsafearray 的 Safearray 的陣列。

安全陣列 vs。STL 向量

標準範本庫 (STL) 類別範本,例如 std:: vector 是絕佳容器模組界限內的 c + + 程式碼,我建議您在這些情況中使用這些。比方說,會動態地新增內容和成長的 std:: vector 比安全陣列,更有效率,而且 std:: vector 剛好也整合與 STL 和其他協力廠商跨平台 c + + 程式庫 (例如提升) 的演算法。此外,使用 std:: vector 可讓您開發跨平台標準 c + + 程式碼。相反地,安全陣列是 Windows 平台特定。

相反地,當安全陣列贏了,就在模組界限。事實上,std::vectors 無法安全地跨模組界限,而且無法由不同於 c + + 語言撰寫的用戶端。相反地,這些是非常因此必須使用安全陣列的內容。良好的程式碼撰寫模式是您執行使用標準 c + + 和 STL 容器,例如 std:: vector 核心處理。然後,當您需要跨模組界限傳送這類陣列的資料,您可以投影 std:: vector 是很好的候選針對跨模組界限中不同於 c + + 語言所撰寫的用戶端所使用的安全陣列的內容。

ATL ccomsafearray 的 Safearray 包裝函式

安全陣列的 「 原生 」 程式設計介面使用 Win32 C 介面 Api,這份文件的線上同一系列文件片段中所述。雖然您可以在 c + + 程式碼中使用這些 C 函式,他們傾向於會導致很麻煩且容易發生錯誤的程式碼。例如,您必須注意適當地比對為正確的相符項目鎖定每個安全陣列配置的安全陣列取消配置與解除鎖定,依此類推。

幸好有 c + + 就可能以簡化的 C 樣式的程式碼相當,使用方便的程式碼撰寫模式,例如 RAII 和解構函式。例如,您可以撰寫類別來包裝原始的 SAFEARRAY 描述元,並讓建構函式呼叫 SafeArrayLock,而解構函式呼叫相符 SafeArrayUnlock 函式。如此一來,包裝函式物件超出範圍後,會自動解除鎖定安全陣列。合理圖形做為範本,另外,這個類別,來強制執行安全陣列的型別安全。事實上,當在 C 中安全陣列 genericity SAFEARRAY::pvData 欄位使用 void 指標表示,在 c + + 可以進行更型別檢查在編譯時期使用範本。

幸運的是,您不需要從頭開始撰寫類別樣板。事實上,ATL 已經提供方便的 c + + 類別範本來簡化安全陣列程式設計︰ 在中宣告 ccomsafearray 的 Safearray,<atlsafe.h>標頭。</atlsafe.h>在 ATL ccomsafearray 的 Safearray<T>,T 範本參數所代表的資料儲存在安全的陣列類型。</T>比方說,是安全的位元組陣列的您會使用 ccomsafearray 的 Safearray<BYTE>; 浮點數的安全陣列包裝使用 ccomsafearray 的 Safearray<float> ,依此類推。</float> </BYTE>請注意,仍內部包裝安全陣列的多型的 void 指標型 C 樣式陣列。不過,c + + 包裝 ccomsafearray 的 Safearray 所建置的圖層提供更高且更安全的抽象層級,其中包含安全陣列自動生命週期管理和更好的型別安全,比 C void *。

Ccomsafearray 的 Safearray<T>有一個資料成員,m_psa,也就是安全陣列描述項 (SAFEARRAY *) ccomsafearray 的 Safearray 物件包裝的指標。</T>

建構 ccomsafearray 的 Safearray 的執行個體

Ccomsafearray 的 Safearray 預設建構函式會建立包裝函式物件,其中包含只安全陣列描述項; 的 null 指標基本上,它會執行任何動作。

此外,有數個可以派上用場的建構函式多載。例如,您可以傳遞建立指定數目之項目所做的安全陣列的項目計數︰

// Create a SAFEARRAY containing 1KB of data
CComSafeArray<BYTE> data(1024);

此外,也可以指定下限為零,這是預設值不同︰

// Create a SAFEARRAY containing 1KB of data
// with index starting from 1 instead of 0
CComSafeArray<BYTE> data(1024, 1);

不過,當您撰寫程式碼來操作要處理的 c + + 或 C# 的安全陣列 /.NET 用戶端,最好是使用一般的慣例的下限為零 (而不是一個)。

請注意 ccomsafearray 的 Safearray 建構函式會自動為您; 呼叫 SafeArrayLock因此,已包裝的安全陣列已鎖定且可供使用者程式碼中的讀取和寫入作業。

如果您有現有的安全陣列描述項的指標,您可以將它傳遞至另一個 ccomsafearray 的 Safearray 建構函式多載︰

// psa is a pointer to an existing safe array descriptor
// (SAFEARRAY* psa)
CComSafeArray<BYTE> sa(psa);

在此情況下,嘗試建立原始安全陣列的位元組 」 psa。 」 所指的深層複本 ccomsafearray 的 Safearray

使用 ccomsafearray 的 Safearray 的自動資源清除

當 ccomsafearray 的 Safearray 物件超出範圍時,其解構函式會自動清除的記憶體和已包裝的安全陣列所配置的資源。這是很方便,而且更簡化的 c + + 程式碼。如此一來,c + + 程式設計人員可以專注於他們的程式碼,而不是安全陣列記憶體清除,避免難處理的記憶體流失問題的詳細資料的核心。

探勘篇 ATL ccomsafearray 的 Safearray 程式碼中,會看到 ccomsafearray 的 Safearray 解構函式,呼叫 SafeArrayUnlock,然後 SafeArrayDestroy,因此不需要用戶端程式碼明確解除鎖定並摧毀安全陣列。實際上,那就是雙解構 bug。

一般的 SafeArray 作業的簡便的方法

自動安全陣列的存留期管理,除了 ccomsafearray 的 Safearray 類別樣板會提供一些便利的方法,以簡化安全陣列上的作業。比方說,您可以直接呼叫 GetCount 方法,以取得已包裝的安全陣列的元素數。若要取得安全陣列項目的存取權,您可以呼叫 CComSafeArray::GetAt 和 SetAt 方法,只需指定的項目索引。

比方說,若要逐一查看現有的安全陣列中的項目,您可以使用如下的程式碼︰

// Assume sa is a CComSafeArray instance wrapping an existing safe array.
// Note that this code is generic enough to handle safe arrays
// having lower bounds different than the usual zero.
const LONG lb = sa.GetLowerBound();
const LONG ub = sa.GetUpperBound();
// Note that the upper bound is *included* (<=)
for (LONG i = lb; i <= ub; i++)
{
  // ... use sa.GetAt(i) to access the i-th item
}

此外,ccomsafearray 的 Safearray 多載的 operator [] 提供甚至更簡單的語法來存取安全陣列的項目。您會看到部分這些方法在後續章節中的這篇文章的動作。

此外,也可以將新的項目附加至現有的安全陣列,叫用 CComSafeArray::Add 方法。此外,CComSafeArray::Resize,正如其名稱清楚,可用來調整已包裝的安全陣列的大小。不過,一般而言,我會建議您撰寫 std:: vector,這是較安全的陣列,在調整大小時,也與其他 STL 演算法和其他協力廠商跨平台 c + + 程式庫,以及整合作業的 c + + 程式碼。然後,std:: vector 之內容的作業完成後,資料可以複製到安全的陣列,它可以安全地跨模組界限 (不同於 std:: vector) 中,並可供也不同於 c + + 語言撰寫的用戶端。

安全陣列的複製作業,可能使用多載的複製指派運算子會 = 或叫用方法,例如 CComSafeArray::CopyFrom。

'移動語意 (semantics)' 中 ccomsafearray 的 Safearray

Ccomsafearray 的 Safearray 類別不會實作 「 移動語意 (semantics) 」 嚴格的 C + + 11 意義。事實上,這個 ATL 類別之前 C + + 11,以及至少安裝 Visual Studio 2015,直到 C + + 11 移動作業例如移動建構函式和移動指派運算子沒有加入到它。不過,有不同種類的 ccomsafearray 的 Safearray 所提供的移動語意,則會根據幾個方法︰ 附加和卸離。基本上,如果您有安全陣列描述項 (SAFEARRAY *) 的指標,您可以將它傳遞至附加方法,和 ccomsafearray 的 Safearray 會取得該原始的安全陣列的擁有權。請注意,任何先前 ccomsafearray 的 Safearray 物件中包裝現有的安全陣列資料已適當地清理。

同樣地,您可以呼叫 CComSafeArray::Detach 方法,而 ccomsafearray 的 Safearray 包裝函式會釋放給呼叫者,將指標傳回至先前所擁有的安全陣列描述項已包裝的安全陣列的擁有權。卸離方法能派上用場時使用 ccomsafearray 的 Safearray,c + + 程式碼中建立安全陣列並接著您將它做為輸出指標參數給呼叫者,例如在 COM 介面方法或 C 介面的 DLL 函式。事實上,ccomsafearray 的 Safearray c + + 類別不能跨越 COM 或 C 介面 DLL 界限。可以只原始 SAFEARRAY * 描述元指標。

C + + 例外狀況無法跨 COM 和 C DLL 界限

某些 ccomsafearray 的 Safearray 方法,例如 Create、 CopyFrom、 SetAt、 加入和調整大小的傳回 Hresult,以表示成功或錯誤狀況,因為是在 COM 程式設計中的自訂。不過,其他方法,例如某些 ccomsafearray 的 Safearray 建構函式多載,或 GetAt 或 operator [] 中,實際上會在錯誤上擲回例外狀況。根據預設,ATL 會擲回類型 CAtlException,也就是 HRESULT 的小型包裝函式的 c + + 例外狀況。

不過,c + + 例外狀況無法跨 COM 方法或 C 介面的 DLL 函式界限。對於 COM 方法,是強制性的返回訊號錯誤的 Hresult。在 C 介面的 Dll,也可用此選項。因此,在撰寫使用 ccomsafearray 的 Safearray 包裝函式 (或任何其他擲回的元件,以及) 的 c + + 程式碼時,務必保護該程式碼使用 try/catch 區塊。例如,COM 方法的實作中,或傳回 HRESULT,在 DLL 界限函式,您可以撰寫如下的程式碼︰

try
{
  // Do something that can potentially throw exceptions as CAtlException ...
}
catch (const CAtlException& e)
{
  // Exception object implicitly converted to HRESULT,
  // and returned as an error code to the caller
  return e;
}
// All right
return S_OK;

產生安全陣列的位元組

舊版概念 framework 安全陣列和方便的 c + + ccomsafearray 的 Safearray ATL 包裝函式的簡介之後,我想要討論一些實際應用的程式設計、 ccomsafearray 的 Safearray 顯示作用中的安全陣列。首先我要使用簡單的案例︰ 產生安全的 c + + 程式碼的位元組陣列。此安全陣列可以傳遞做為輸出參數,在 COM 介面方法或 C 介面的 DLL 函式。

安全陣列通常是透過其描述元,在 c + + 程式碼中會轉譯為 SAFEARRAY * 的指標參考。如果您有輸出安全陣列參數時,需要另一層間接取值,因此安全陣列做為輸出參數傳遞的 SAFEARRAY * * 格式 (SAFEARRARY 描述元雙精度浮點指標)︰

STDMETHODIMP CMyComComponent::DoSomething(
    /* [out] */ SAFEARRAY** ppsa)
{
  // ...
}

在 COM 方法中,別忘了使用 try/catch 成立條件來攔截例外狀況,並將它們轉譯成對應的 Hresult 前, 一段所示。(請注意 STDMETHODIMP 前置處理器巨集表示的方法會傳回 HRESULT)。

在 try 區塊中,會建立 ccomsafearray 的 Safearray 類別樣板的執行個體,做為範本參數指定的位元組︰

// Create a safe array storing 'count' BYTEs
CComSafeArray<BYTE> sa(count);

然後您可以直接存取使用 ccomsafearray 的 Safearray 的多載的運算子 [],例如安全陣列中的項目︰

for (LONG i = 0; i < count; i++)
{
  sa[i] = /* some value */;
}

一旦您完全安全陣列 (例如,複製 std:: vector) 來撰寫您的資料,安全陣列可能會傳回做為輸出參數,叫用 ccomsafearray 的 Safearray 卸離的方法︰

*ppsa = sa.Detach();

請注意,叫用後卸離,ccomsafearray 的 Safearray 包裝函式物件會傳送安全陣列的擁有權給呼叫端。因此,當 ccomsafearray 的 Safearray 物件超出範圍,在先前的程式碼中建立的安全陣列不會損毀。卸離,感謝安全陣列資料只傳輸,或者 「 移動 」 從先前的程式碼給呼叫者首先,建立安全的陣列。現在是在不再需要時終結安全陣列的呼叫者的責任。

安全陣列會跨 DLL 模組界限,以及交換陣列資料很方便的。例如,認為 C 介面 DLL,以產生一些 C# 用戶端程式碼都由安全陣列的 c + + 撰寫。如果界限函式匯出的 DLL 會出現此原型︰

extern "C" HRESULT __stdcall ProduceByteArrayData(/* [out] */ SAFEARRAY** ppsa)

對應的 C# PInvoke 宣告如下︰

[DllImport("NativeDll.dll", PreserveSig = false)]
public static extern void ProduceByteArrayData(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
  out byte[] result);

UnmanagedType.SafeArray 表示 SAFEARRAY 的函式界限上實際的原生陣列型別。指派給 SafeArraySubType 的 VarEnum.VT_UI1 值指定的安全陣列中儲存的資料類型的位元組 (這是不帶正負號的整數大小剛好有一個位元組的)。安全儲存 4 位元組簽署整數的陣列,在 c + +,您會需要 ccomsafearray 的 Safearray<int>,和對應的 PInvoke VarEnum 類型為 VT_I4 (使用 4 個位元組大小的意義帶正負號的整數)。</int>安全陣列會對應至 C# 中的 byte [] 陣列,並傳遞做為輸出參數。

PreserveSig = false 的屬性會告知 PInvoke 轉譯錯誤 Hresult 傳回原生函式的 C# 中的例外狀況。

[圖 1顯示完整的範例程式碼片段,以便產生安全使用 ccomsafearray 的 Safearray 的 c + + 中的位元組陣列。這個程式碼是假設的 COM 方法。不過,相同 ccomsafearray 的 Safearray 為基礎的程式碼可以用於 C 介面 DLL 界限函式,以及使用。

[圖 1 產生安全陣列的位元組使用 ccomsafearray 的 Safearray

STDMETHODIMP CMyComComponent::DoSomething(/* [out] */ SAFEARRAY** ppsa) noexcept
{
  try
  {
    // Create a safe array storing 'count' BYTEs
    const LONG count = /* some count value */;
    CComSafeArray<BYTE> sa(count);
    // Fill the safe array with some data
    for (LONG i = 0; i < count; i++)
    {
      sa[i] = /* some value */;
    }
    // Return ("move") the safe array to the caller
    // as an output parameter
    *ppsa = sa.Detach();
  }
  catch (const CAtlException& e)
  {
    // Convert ATL exceptions to HRESULTs
    return e;
  }
  // All right
  return S_OK;
}

產生安全字串的陣列,

現在您已學到如何建立安全陣列的位元組和更安全陣列當做 C 介面的 DLL 函式的輸出參數傳遞時,在 C# 中使用的 PInvoke 宣告簽章。此編碼模式適用於安全陣列儲存其他純量類型,例如整數、 浮點數、 雙精度浮點數,依此類推。這些型別具有相同的二進位表示法的 c + + 和 C# 中,且會輕易地封送處理這些兩者之間,與 COM 元件界限,以及跨。

不過,一些多加注意應該付費安全儲存字串的陣列。字串需要特別小心,因為它們只是單一純量,例如位元組的整數或浮點數比更為複雜。BSTR 類型是方便用來表示可以安全地跨模組界限的字串。很多功能這種類型時,您可以將它做為固定長度 Unicode utf-16 字串指標。預設封送處理器會知道如何複製 Bstr 並使其跨 COM 或 C 介面函式界限。Eric Lippert 的部落格文章,可供讀取的有趣且詳細的描述 BSTR 語意bit.ly/2fLXTfY

若要建立安全的陣列,將字串儲存在 c + +,ccomsafearray 的 Safearray 類別樣板可以具現化使用 BSTR 類型︰

// Creates a SAFEARRAY containing 'count' BSTR strings
CComSafeArray<BSTR> sa(count);

安全陣列型別的指定如下的未經處理的 BSTR C 類型,而在 c + + 程式碼是較理想的 (也就是容易、 更安全) 使用原始 Bstr RAII 包裝函式。ATL 提供這類的方便包裝函式的 CComBSTR 類別形式。Win32/c + + 程式碼編譯與 Microsoft Visual c + + (MSVC),以 Unicode utf-16 字串,都可以使用 std:: wstring 類別來表示。因此,合理的建置將從 std:: wstring 轉換成 ATL::CComBSTR 的便利的協助程式函式︰

// Convert from STL wstring to the ATL BSTR wrapper
inline CComBSTR ToBstr(const std::wstring& s)
{
  // Special case of empty string
  if (s.empty())
  {
    return CComBSTR();
  }
  return CComBSTR(static_cast<int>(s.size()), s.data());
}

若要填滿 ccomsafearray 的 Safearray<BSTR>字串複製 STL 向量<wstring>、 向量可反覆執行,並在向量中每個 wstring,對應的 CComBSTR 物件來建立叫用先前提及的 helper 函式︰</wstring> </BSTR>

// 'v' is a std::vector<std::wstring>
for (LONG i = 0; i < count; i++)
{
  // Copy the i-th wstring to a BSTR string
  // safely wrapped in ATL::CComBSTR
  CComBSTR bstr = ToBstr(v[i]);

接著,傳回的 bstr 可以複製到安全的陣列物件,叫用 CComSafeArray::SetAt 方法中︰

hr = sa.SetAt(i, bstr);

SetAt 方法會傳回 HRESULT,因此它是良好的程式設計作法,檢查它的值,並擲回例外狀況發生錯誤︰

if (FAILED(hr))
  {
    AtlThrow(hr);
  }
} // For loop

例外狀況會轉換成 HRESULT 的 COM 方法或 C 介面的 DLL 函式界限。或者,傳回的錯誤 HRESULT 可能是直接從上述的程式碼片段。

相對於前一個 ccomsafearray 的 Safearray 這個 BSTR 安全陣列的大小寫的主要差異<BYTE>範例是 BSTR 字串前後中繼 CComBSTR 包裝函式物件的建立。</BYTE>不需要針對簡單的純量類型,例如位元組、 整數或浮點數; 這類包裝函式但是更複雜類型,例如 Bstr 需要適當的存留期管理,這些 c + + RAII 包裝函式派上用場。例如,包裝函式,例如 CComBSTR 隱藏例如 SysAllocString,用來建立新的 BSTR 從現有的 C 樣式字串指標的函式的呼叫。同樣地,CComBSTR 解構函式會自動呼叫 SysFreeString 來釋放的 BSTR 的記憶體。所有這些 BSTR 存留期管理細節內 CComBSTR 類別實作時,方便地隱藏起來,讓 c + + 程式設計人員可以專注於他們的程式碼的更高層級邏輯的注意。

'移動語意' 安全陣列的 Bstr 的最佳化

請注意,先前 ccomsafearray 的 Safearray<BSTR>:: SetAt 方法呼叫會執行安全陣列的輸入 BSTR 的深層複本。</BSTR>事實上,SetAt 方法具有其他 BOOL bCopy 第三個參數,則預設為 TRUE。此 bCopy 旗標參數並不重要的純量型別,例如位元組或整數的浮點的數字,因為它們全都深層複製安全陣列中。不過,請務必更複雜的類型,例如 Bstr 具有非一般的存留期管理和非一般的複製語意。例如,在此情況下,ccomsafearray 的 Safearray<BSTR>、 安全陣列做為 SetAt,第三個參數指定 FALSE 時,如果只需輸入 BSTR,而不是深層複製它的擁有權</BSTR>它是種極好最佳化的移動作業,而不是深層複本。此最佳化還需要卸離方法叫用上正確地從 CComBSTR 的 BSTR 擁有權轉移給 ccomsafearray 的 Safearray CComBSTR 包裝函式︰

hr = sa.SetAt(i, bstr.Detach(), FALSE);

如先前所示 ccomsafearray 的 Safearray<BYTE>範例中,一次建立 ccomsafearray 的 Safearray<BSTR>已完成,安全陣列可以傳遞 (移動) 至呼叫端具有類似的程式碼︰</BSTR> </BYTE>

// Return ("move") the safe array to the caller
// as an output parameter (SAFEARRAY **ppsa)
*ppsa = sa.Detach();

建置安全的 BSTR 字串陣列,並將其傳遞給呼叫者的 C 介面的 DLL 函式可以是 C# 中的 Pinvoke 像這樣︰

[DllImport("NativeDll.dll", PreserveSig = false)]
public static extern void BuildStringArray(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
  out string[] result);

請注意使用 VarEnum.VT_BSTR 來表示安全儲存的 BSTR 字串陣列。原生 c + + 程式碼所產生的 Bstr SAFEARRAY 是 C# 使用 string [] 陣列類型,傳遞做為輸出參數封送處理。

產生安全包含字串的 Variant 的陣列

讓我們進一步增加複雜度層的級的另一個步驟。除了安全陣列的儲存類型,例如位元組、 整數和 BSTR 字串的項目,您也可建立的 「 泛型 」 型別安全陣列 — VARIANT 型別。Variant 是各種的多型型別,可以儲存不同的類型,等範圍從整數、 浮點數,BSTR 字串的值。VARIANT C 類型基本上是理所當然的聯集。其定義位於bit.ly/2fMc4Bu。就像 ATL BSTR C 類型,提供方便的 c + + 包裝函式的原始 C VARIANT 型別周圍︰ ATL::CComVariant 類別。在 c + + 程式碼,就更簡單、 更安全地處理使用 CComVariant 包裝函式,而不直接叫用的配置、 複製和清除變異 C 函式的變數。

雖然 c + + 和 C# 撰寫的用戶端了解安全陣列儲存 「 直接 」 的型別 (如位元組和 BSTR 的先前範例所示),有一些指令碼的用戶端只安全儲存變數的陣列。因此,如果您想要建置這類指令碼的用戶端資料 c + + 和您想要讓該資料以供取用的陣列,此資料必須封裝在安全陣列儲存變體,加入新的層級的間接取值 (和一些額外的負擔,以及)。位元組、 浮點數、 BSTR 或任何支援的型別值,會依序儲存安全陣列中每個變異的項目。

假設您想修改先前的程式碼來建置安全陣列的 BSTR 字串改為使用安全 variant 的陣列。變數會依序包含 Bstr,但會產生安全陣列的情況下,不直接的安全陣列的 BSTR 字串。

首先,若要建立安全 variant 的陣列,ccomsafearray 的 Safearray 類別樣板可以具現化這種方式︰

// Create a safe array storing VARIANTs
CComSafeArray<VARIANT> sa(count);

然後,您可以逐一查看一組儲存,比方說,STL 向量中的字串<wstring>,並針對每個 wstring 您可以建立 CComBSTR 物件,就像在先前的程式碼範例︰</wstring>

// 'v' is a std::vector<std::wstring>
for (LONG i = 0; i < count; i++)
{
  // Copy the i-th wstring to a BSTR string
  // safely wrapped in ATL::CComBSTR
  CComBSTR bstr = ToBstr(v[i]);

現在那里來自不同的安全陣列的 Bstr 先前的大小寫。事實上,這次您要建置安全陣列的變數 (不是直接安全陣列的 Bstr),因為您無法直接在呼叫 SetAt ccomsafearray 的 Safearray 存放 BSTR 物件。相反地,第一次 variant 的物件必須建立包裝的 bstr。然後該 variant 的物件可以插入安全 variant 的陣列︰

// First create a variant from the CComBSTR
  CComVariant var(bstr);
  // Then add the variant to the safe array
  hr = sa.SetAt(i, var);
  if (FAILED(hr))
  {
    AtlThrow(hr);
  }
} // For loop

請注意,CComVariant 採取 const wchar_t * 指標,因此能夠直接建置 CComVariant 都從叫用 wstring c_str 方法的多載建構函式。不過,在此情況下,變數會儲存初始區塊的第一個 NUL 結束字元,直到原始的 wstring雖然 wstring 和 BSTR 可能可以儲存具有內嵌 NULs 的字串。因此,建置中繼 CComBSTR 都如先前所示的自訂 ToBstr helper 函式,從涵蓋此更普通的情況,也一樣。

像往常一樣,若要建立的安全陣列傳回呼叫端為 SAFEARRAY * * 輸出參數,可以使用 CComSafeArray::Detach 方法︰

// Transfer ownership of the created safe array to the caller
*ppsa = sa.Detach();

如果安全陣列會傳遞透過 C 介面函式,如下︰

extern "C" HRESULT __stdcall BuildVariantStringArray(/* [out] */ SAFEARRAY** ppsa)

可以使用下列 C# PInvoke 宣告︰

[DllImport("NativeDll.dll", PreserveSig = false)]
pubic static extern void BuildVariantStringArray(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
  out string[] result);

請注意 VarEnum.VT_VARIANT 用於 SafeArraySubType,因為這次在 c + + 中建立的安全陣列包含變化 (這又包裝 BSTR 字串),但不是 BSTR 直接。

一般情況下,我會建議您匯出資料而不安全的陣列儲存變體,使用安全陣列的直接的型別,除非您藉由您的資料只能處理 variant 的安全陣列的指令碼用戶端存取的限制。

總結

安全陣列資料結構是在不同的模組與語言界限之間交換陣列資料的便利工具。安全陣列是一種多樣化的資料結構,並在其中儲存簡單基本型別,例如位元組、 整數或浮點數,但也越複雜型別例如 BSTR 字串或甚至是一般的變體。這篇文章說明如何以簡化這種資料結構,在 c + + 程式設計與使用 ATL 協助程式類別的具體的範例案例。


Giovanni Dicanio是電腦程式設計人員專精於 c + + 和 Windows 作業系統、 Pluralsight 作者 (bit.ly/GioDPS) 和 Visual c + + MVP。 除了程式設計和撰寫課程,他喜歡在論壇和社群致力於 c + + 幫助其他人。他的連絡giovanni.dicanio@gmail.com。他的部落格上msmvps.com/gdicanio

感謝閱本篇文章的下列技術專家︰ David Cravey 和 Marc Gregoire
David Cravey GlobalSCAPE 企業架構,會導致數個 c + + 使用者的群組,但四個階段 Visual c + + MVP。
Marc Gregoire 從比利時、 比利時 c + + 使用者的群組,這作者"Professional c + + 」 (Wiley),「 c + + 標準程式庫快速參考 」 (Apress),技術編輯共同作者的書籍,而由於 2007年的創辦人的資深軟體工程師,收到每年的 MVP 獎勵的他 VC + + 專業知識。連絡 Marc marc.gregoire@nuonsoft.com