5

プロパティの一部として構造体を設定および取得する独自のカスタム インターフェイスを備えたサード パーティの COM サーバーを使用しています。たまたま、クライアントに C++ を使用しています。以下の IDL ファイルから、名前を変更し、GUID を削除した代表的なコードを掲載しました。

構造体のパッキングは定義されていますか?それとも、クライアント コードがたまたま COM サーバーが構築されたときと同じパッキング設定を使用しているのは幸運でしょうか? デフォルトの C++ コンパイラのパッキング設定が変更されたプロジェクトで問題が発生する可能性はありますか? クライアント コンパイラのパッキング設定が正しいことを確認するために使用できるプラグマ パック設定はありますか?

IDL または MIDL から生成されたヘッダー ファイルのいずれにも、パッキング プラグマまたはステートメントが表示されません。クライアントが C# または VB の場合はどうなるでしょうか? IDispatch メカニズムを介して呼び出された場合、パッキング動作はより明確に指定されていますか?

struct MyStruct
{
    int a, b;
};

[
    object,
    uuid( /* removed */ ),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IVideoOutputSettings : IDispatch{

    [propget, id(1), HRESULT MyProperty([out, retval] struct MyStruct* pVal);
    [propput, id(1), HRESULT MyProperty([in] struct MyStruct newVal);

    /* other methods */
};
4

2 に答える 2

8

既定のパッキングは、次の MIDL コマンド ライン スイッチ リファレンスによると、8 バイト境界に沿っています。

/Zp スイッチ @ MSDN (MIDL 言語リファレンス)

pack の値が変更された場合、コードの他の部分が最初に壊れる可能性が高くなります。これは、IDL ファイルが通常事前にコンパイルされているためです。 C スコープ#pragma packをいじって、デフォルト状態を復元するのを忘れる可能性は非常にまれです)。

設定を変更する正当な理由がある場合は、pragma packステートメントを使用してパッキングを明示的に設定できます。

pragma 属性 @ MSDN (MIDL 言語リファレンス)

デフォルトのパッキングに干渉するような設定を変更した当事者がいないことは、非常に幸運です。うまくいかないことはありますか?はい、誰かがわざわざデフォルトを変更した場合。

IDL ファイルを使用する場合、詳細は通常、typelib (.tlb) にコンパイルされます。同じ typelib を使用する場合、サーバーとクライアントの両方でプラットフォームが同じであると想定されます。/Zpこれは、特定の値が特定の非 x86 または 16 ビット ターゲットに対して失敗するため、スイッチの脚注で提案されています。32bit <-> 64bit 変換のケースもあり、期待が崩れる可能性があります。残念ながら、さらに多くのケースがあるかどうかはわかりませんが、デフォルトは最小限の手間で機能します.

C# と VB には、.tlb 内の情報を処理する固有の動作がありません。代わりに、通常、tlbimp のようなツールを使用して、COM 定義を .NET から使用できる定義に変換します。C#/VB.NET と COM のクライアントおよびサーバーの間ですべての期待が成功するかどうかを確認することはできません。ただし、その設定でコンパイルされた IDL から作成された .tlb を参照する場合、8 以外の特定のプラグマ設定を使用しても機能することを確認できます。デフォルトのプラグマ パックを使用することはお勧めしませんが、実際の例を参照として使用する場合は、次の手順を実行してください。C++ ATL プロジェクトと C# プロジェクトを作成して確認しました。

C++ 側の命令は次のとおりです。

  1. Visual Studio 2010 の既定の設定でSampleATLProjectという ATL プロジェクトを作成しました。フィールドは変更しませんでした。これにより、dll プロジェクトが作成されます。
  2. プロジェクトをコンパイルして、適切な C 側インターフェイス ファイル (SampleATLProject_i.c および SampleATLProject_i.h) が作成されていることを確認します。
  3. プロジェクトに呼び出される ATL Simple Object を追加しましたSomeFoo。繰り返しますが、デフォルトは変更されていません。CSomeFooこれにより、プロジェクトに追加されるクラスが作成されます。
  4. SampleATLProject をコンパイルします。
  5. SampleATLProject.idl ファイルを右クリックし、MIDL 設定で、Struct Member Alignment を 4 バイト (/Zp4) に設定しました。
  6. SampleATLProject をコンパイルします。
  7. IDL を変更して、'BarStruct' という構造体定義を追加しました。これには、MIDL uuid 属性を持つ C スタイルの構造体定義と、構造体定義を参照するライブラリ セクションのエントリを追加する必要がありました。以下のスニペットを参照してください。
  8. SampleATLProject をコンパイルします。
  9. クラス ビューから を右クリックして、theBarという [in] パラメータとして を受け取るISomeFooというメソッドを追加しました。FooItstruct BarStruct
  10. SampleATLProject をコンパイルします。
  11. SomeFoo.cpp に、構造体のサイズを出力し、詳細を含むメッセージ ボックスを表示するコードを追加しました。

これが ATL プロジェクトの IDL です。

import "oaidl.idl";
import "ocidl.idl";

[uuid(D2240D8B-EB97-4ACD-AC96-21F2EAFFE100)]
struct BarStruct
{
  byte a;
  int b;
  byte c;
  byte d;
};

[
  object,
  uuid(E6C3E82D-4376-41CD-A0DF-CB9371C0C467),
  dual,
  nonextensible,
  pointer_default(unique)
]
interface ISomeFoo : IDispatch{
  [id(1)] HRESULT FooIt([in] struct BarStruct theBar);
};
[
  uuid(F15B6312-7C46-4DDC-8D04-9DEA358BD94B),
  version(1.0),
]
library SampleATLProjectLib
{
  struct BarStruct;
  importlib("stdole2.tlb");
  [
    uuid(930BC9D6-28DF-4851-9703-AFCD1F23CCEF)      
  ]
  coclass SomeFoo
  {
    [default] interface ISomeFoo;
  };
};

クラス内のCSomeFooの実装は次のとおりですFooIt()

STDMETHODIMP CSomeFoo::FooIt(struct BarStruct theBar)
{
  WCHAR buf[1024];
  swprintf(buf, L"Size: %d, Values: %d %d %d %d", sizeof(struct BarStruct), 
           theBar.a, theBar.b, theBar.c, theBar.d);

  ::MessageBoxW(0, buf, L"FooIt", MB_OK);

  return S_OK;
}

次に、C# 側で:

  1. SampleATLProject のデバッグまたは目的の出力ディレクトリに移動し、C++ プロジェクト出力の一部として生成された .tlb ファイルで tlbimp.exe を実行します。以下は私のために働いた:

    tlbimp SampleATLProject.tlb /out:Foo.dll /名前空間:SampleATL.FooStuff

  2. 次に、C# コンソール アプリケーションを作成し、Foo.dll への参照をプロジェクトに追加しました。

  3. References フォルダーで、[Properties for] に移動し、[ Embed Interop Types ]を false に設定してFooオフにします。
  4. SampleATL.FooStufftlbimp に与えられた名前空間を参照するために using ステートメントを追加し、[STAThread]属性を追加しMain()(COM アパートメント モデルはインプロセス消費のために一致する必要があります)、COM コンポーネントを呼び出すコードをいくつか追加しました。

Tlbimp.exe (タイプ ライブラリ インポーター) @ MSDN

そのコンソール アプリのソース コードを次に示します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SampleATL.FooStuff;

namespace SampleATLProjectConsumer
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            BarStruct s;
            s.a = 1;
            s.b = 127;
            s.c = 255;
            s.d = 128;

            ISomeFoo handler = new SomeFooClass();
            handler.FooIt(s);
        }
    }
}

最後に、それが実行され、次の文字列が表示されたモーダル ポップアップが表示されます。

Size: 12, Values: 1 127 255 128

プラグマ パックを確実に変更できるようにするため (4/8 バイト パッキングが使用される最も一般的なアラインメントであるため)、次の手順に従って 1 に変更しました。

  1. C++ プロジェクトに戻り、SampleATLProject.idl のプロパティに移動して、構造体メンバーの配置を 1 (/Zp1) に変更しました。
  2. SampleATLProject を再コンパイルする
  3. 更新された .tlb ファイルを使用して tlbimp を再度実行します。
  4. への .NET ファイル参照に警告アイコンが表示されますFooが、参照をクリックすると消える場合があります。そうでない場合は、C# コンソール プロジェクトへの参照を削除して再度追加し、新しい更新されたバージョンが使用されていることを確認してください。

ここから実行したところ、次の出力が得られました。

Size: 12, Values: 1 1551957760 129 3

それは変だ。ただし、SampleATLProject_i.h の C レベル プラグマを強制的に編集すると、正しい出力が得られます。

#pragma pack(push, 1)
/* [uuid] */ struct  DECLSPEC_UUID("D2240D8B-EB97-4ACD-AC96-21F2EAFFE100") BarStruct
    {
    byte a;
    int b;
    byte c;
    byte d;
    } ;
#pragma pack(pop)

SampleATLProject はここで再コンパイルされ、.tlb または .NET プロジェクトに変更はなく、次の結果が得られます。

Size: 7, Values: 1 127 255 128

に関してIDispatchは、クライアントが遅延バインドされているかどうかによって異なります。レイト バインド クライアントは、型情報側を解析し、IDispatch重要な型の適切な定義を識別する必要があります。とのドキュメントは、フィールドが必要な情報を提供することを考えると、それが可能であることITypeInfoTYPEATTR示唆しています。cbAlignment問題が発生した場合やバージョン間でパックの期待値を変更する必要がある場合にデバッグするのが面倒になるため、ほとんどの場合、デフォルトを変更したり変更したりすることは決してないと思います。また、構造体は通常、 を使用できる多くのスクリプト クライアントではサポートされていませんIDispatch。多くの場合、IDLoleautomationキーワードによって管理される型のみがサポートされると予想できます。

IDispatch インターフェイス @ MSDN
IDispatch::GetTypeInfo @ MSDN
ITypeInfo インターフェイス @ MSDN
TYPEATTR 構造体 @ MSDN

oleautomation キーワード @ MSDN

于 2012-04-13T23:59:55.500 に答える
7

はい、構造体は COM の問題です。IUnknown ベースのインターフェイスを使用する場合は、適切なコンパイラ設定でさいころを振る必要があります。デフォルトを変更する理由はほとんどありません。

COM オートメーションを使用する場合は、.IDL で typedef を使用して構造体を宣言する必要があります。クライアント コードが IRecordInfo を使用して、タイプ ライブラリ情報に基づいて構造体に適切にアクセスできるようにします。必要なのは、コンパイラの /Zp 設定が midl.exe の /Zp 設定と一致していることを確認することだけです。難しいことではありません。

どのような構造もプロパティを持つインターフェイスで記述できることを認識することで、問題を完全に回避できます。今は関係ありません。

于 2012-04-14T00:44:40.517 に答える