2017-12-03 3 views
0

次のシグネチャを使用する方法で、C++で記述されたCOMオブジェクトがあります。バリアントにはBSTR(VT_BYREF | VT_BSTRではなくVT_BSTRのみ)が含まれているとします。入力BSTR VARIANTでVariantChangeTypeを呼び出すことはできますか?

HRESULT myfunc(/*[in]*/ VARIANT param) 

タイプを別のものに変更したいとします。 VariantChangeTypeの最初のパラメータが2番目のパラメータと同じ場合は、 "その変種はその場で変換されます。"

そこで、変換はできますか?

HRESULT myfunc(/*[in]*/ VARIANT param) 
{ 
    VariantChangeType(&param, param, 0, VT_I4); 
} 

または、第2の亜種にコピーしてください。

HRESULT myfunc(/*[in]*/ VARIANT param) 
{ 
    VARIANT temp; 
    VariantInit(&temp); 
    VariantChangeType(&temp, param, 0, VT_I4); 
} 

私の理解では、前者がクライアントによって所有され、クライアントによって解放しなければならないBSTRを解放するであろうから、後者が必要とされることです。

+0

いいえ、VariantChangeType()は結果を別のバリアントに格納します。第1引数として渡すもの。したがって、BSTRをリリースする理由は全くありません。 –

+0

@HansPassant VariantChangeType()の最初のパラメーターは、2番目のパラメーターと同じにすることができます。つまり、バリアントが変換されます。だから、おそらく、私はその場所に変換するか、第二の変種に変換する方がよいでしょうか?私は明確にするために質問を更新しました。 – jveazey

+1

FWIW、問題を示すためにGistを作成しました:https://gist.github.com/Neuroboy23/efb3d45783faf03bd87ee2a69519ce84 –

答えて

2

VariantChangeTypeを使用すると、第2の変種が必要ですが、それは明らかではありません。

バリアントが値渡しであっても、バリアントに含まれるポインタはすべて同じメモリアドレスを指します。 BSTRはポインタなので、パラメータがVARIANTではなくBSTRであるかのように、BSTRの元のアドレスが関数に渡されます。 VariantChangeType(インプレース)またはVariantClearを使用して

は(呼び出し側が所有している)、元の変異体はまだBSTRのアドレスが含まれていませんが、そのアドレスは、もは​​やBSTRを保持していることを意味し、SysFreeStringがトリガされます。 "Variant Manipulation Functions"ドキュメントから

...

VT_BSTR型と変異型のタイプを解放または変更する場合は、SysFreeStringのが含まれる文字列で呼び出されます。

これは明白ではない理由は、上に説明したすべてがそうしてはならないと言っても、このコードが機能するように見えるからです。

#ifndef WIN32_LEAN_AND_MEAN 
#define WIN32_LEAN_AND_MEAN 
#endif // !WIN32_LEAN_AND_MEAN 
#include <Windows.h> 
#include <OleAuto.h> 

HRESULT myfuncbad(/*[in]*/ VARIANT param) 
{ 
    // In-place conversion 
    VariantChangeType(&param, &param, 0, VT_I4); 
    return S_OK; 
} 

HRESULT myfuncgood(/*[in]*/ VARIANT param) 
{ 
    VARIANT temp; 
    VariantInit(&temp); 
    // Copy and convert into a new VARIANT 
    VariantChangeType(&temp, &param, 0, VT_I4); 
    VariantClear(&temp); 
    return S_OK; 
} 

int main() 
{ 
    VARIANT input; 
    VariantInit(&input); 
    V_BSTR(&input) = SysAllocString(L"1"); 
    V_VT(&input) = VT_BSTR; 

    myfuncgood(input); 
    wprintf(L"Memory location of BSTR = 0x%x\n", (unsigned)(V_BSTR(&input))); 
    wprintf(L"Contents of BSTR = %s\n", V_BSTR(&input)); 

    myfuncbad(input); 
    wprintf(L"Memory location of BSTR = 0x%x\n", (unsigned)(V_BSTR(&input))); 
    wprintf(L"Contents of BSTR = %s\n", V_BSTR(&input)); 
} 

このコードが実行され、出力以下

Memory location of BSTR = 0x2d1af0c 
Contents of BSTR = 1 
Memory location of BSTR = 0x2d1af0c 
Contents of BSTR = 1 

しかし、なぜ何か?それはBSTR allocations are cachedだと分かります。したがって、VariantChangeType(インプレース)またはVariantClearが呼び出された場合でも、BSTRの割り当てはしばらくの間留まります。これらの関数に渡されたバリアントについてはすぐに明らかになりますが、バリアントの "バイナリー"コピーにはしばらくの間BSTRが表示されることがあります。

BSTRは技術的にはmyfuncbadで解放されており、もはや呼び出し元によって参照されるべきではありません。また、元のバリアントでVariantClearを呼び出すとエラーが発生する可能性があります。

追加の読み取り

+2

ここで重要なのは/ * [in] */annotationです。それはidlが持っていることを意味し、渡されたバッファはあなたのものではありません。呼び出し先に対しては読み取り専用です。そのバッファを変更することはできません。それがすべてです。 –

-1

コピーを作成する方が安全だろうが、私は(https://msdn.microsoft.com/en-us/library/ms810016.aspx)「コンポーネントオブジェクトモデルのルール」を読んで:

次のルールは、戻り値を含め、メンバ関数をインターフェースするためのパラメータに適用されます値によって渡されないもの: ◦パラメータの場合、呼び出し元はメモリを割り当てて解放する必要があります。

あなたが話しているケースは、この呼び出しでBSTRを含むVARIANTに関係なく、値渡しです。だから私は、このケースでは呼び出し先がパラメータを所有していると信じています。呼び出し元は、その値の継続的な実行可能性を確保したい場合、コピーを作成する必要があります。

+0

BSTRはSysAllocStringによって割り当てられたポインタです。したがって、VT_BYREFでなくても参照です。だから、技術的には、それは「価値によって」渡されません。 – jveazey

+1

私はこの上に@jveazeyと一緒にいます。呼び出し元は、常に、その「VARIANT」の割り当てを解除しようとします。また、格納されているメモリがすでに割り当て解除されている場合、クラッシュする可能性があります。 –

-1

インプレース変換が保存されます。あなたは一時的な変種は必要ありません。

私はこのような何かと私のATLのCOMコードのすべてでそれを使用します。

CComVariant v; 
GetSomeData(v); // Assume v returns a VT_BSTR variant. 
HRESULT hr = v.ChangeType(VT_I4); 
if (FAILED(hr)) 
    ... 

このコードは、議論の方法でインプレース変換に変換されます。 内部では、VARIANTBSTRの使用カウント値が減分される前に、結果はVarI4FromBSTRで計算されます。

私はこれについてもわからないので、いくつかのデバッグセッションでこれを検証しました。

EDIT最後に、私はMSDN that confirms thisのステートメントを見つけました。

VT_BSTRの場合、文字列の所有者は1人だけです。 バリアントのすべての文字列は、SysAllocString関数で割り当てる必要があります。 VT_BSTRタイプのバリアントのタイプを解放または変更すると、含まれている文字列に対して SysFreeStringが呼び出されます。

コードは@jveazeyの答えで動作することは、BSTRキャッシングとは関係ありません。本当のインプレースコンバージョンがあります!

+1

残念ながら、これは危険な答えです。あなたが提供する例では、メソッド呼び出しから 'VARIANT'を受け取っているので安全です - そのメソッド呼び出しが完了し、' VARIANT'の所有者とその 'BSTR'が呼び出し元になったら、それはうまくいきます呼び出し元がそれを変換したい場合。しかし、OPの質問は、「VARIANT」とそれに含まれるBSTRがメソッドに渡されている状況に関するものです。この場合、呼び出し元が所有するものの割り当てを解除する(呼び出し元)ので、型を変更することは安全ではありません(後でその呼び出しを再度割り当てようとします)。 –

+0

MSDNは明示的に「VT_BSTR型のバリアントの種類を解放または変更すると、含まれている文字列に対してSysFreeStringが呼び出されます。この例の呼び出し先(myfuncbad)は、呼び出し元が所有しているものを解放しているため、悪いです。 – jveazey

+0

私の答えを確認するMSDNへのリンクが追加されました! – xMRi

関連する問題