2012-04-13 15 views
3

私は独自のカスタムインターフェイスを使用してサードパーティのCOMサーバーと連携し、いくつかのプロパティとして構造体を設定し取得しています。私はクライアントのためにC++を使用しています。以下のIDLファイルから代表的なコードをいくつか投稿しました。名前は変更され、GUIDは削除されました。COMインターフェイスで渡された構造体のパッキングは定義されていますか?

構造体の定義が定義されているか、クライアントコードが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 */ 
}; 

答えて

6

デフォルトの梱包は、ここではMIDLコマンドラインスイッチのリファレンスによると、8バイト境界に沿っている:あなたのコードの

/Zp switch @ MSDN (MIDL Language Reference)

その他のパーツパック場合は最初のブレークする可能性が高くなります値は変更されます。IDLファイルは通常事前にコンパイルされているため、誰かが故意にMIDLに与えられたコマンドラインスイッチを変更することはまれです(ただし、Cスコープである#pragma packを忘れて、デフォルトの状態に戻す)。

設定を変更する必要がある場合は、pragma packステートメントで明示的にパッキングを設定できます。

pragma Attribute @ MSDN (MIDL Language Reference)

それは何の当事者がデフォルトのパッキングを妨害する任意の設定を変更していないことはかなり幸運です。それは間違っていますか?はい、誰かがデフォルトを変更するために道を離れる場合。

IDLファイルを使用する場合、詳細は通常typelib(.tlb)にコンパイルされ、同じタイプライブラリを使用している場合はプラットフォームがサーバとクライアントの両方で同じとみなされます。これは/Zpスイッチの脚注で示唆されています。特定の値が特定の非x86または16ビットターゲットに対して失敗するためです。また、32ビットの< - > 64ビットの変換ケースがあり、期待しないことがあります。残念ながら、そこにもっと多くのケースがあるかどうかは分かりませんが、デフォルトはちょっとした騒ぎで動作します。

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. SomeFooというATLシンプルオブジェクトをプロジェクトに追加しました。ここでも、デフォルトは変更されていません。これにより、プロジェクトに追加されるCSomeFooというクラスが作成されます。
  4. SampleATLProjectをコンパイルします。
  5. SampleATLProject.idlファイルを右クリックし、MIDL設定の下で、構造体メンバーのアライメントを4バイト(/ Zp4)に設定します。
  6. SampleATLProjectをコンパイルします。
  7. IDLを変更して、 'BarStruct'という構造体定義を追加しました。これには、MIDL uuid属性を持つCスタイルの構造体定義と、構造体定義を参照するライブラリセクションのエントリが追加されました。以下のスニペットを参照してください。
  8. SampleATLProjectをコンパイルします。クラスビューから
  9. は、私が ISomeFoo上で右クリックし、 theBarと呼ばれる[中]パラメータとして struct BarStructを取る FooItというメソッドを追加しました。
  10. SampleATLProjectをコンパイルします。
  11. SomeFoo.cppでは、構造体のサイズを出力し、詳細を含むメッセージボックスをスローするコードをいくつか追加しました。

ここは私のATLプロジェクトのIDLです。 CSomeFooクラスインサイド

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; 
    }; 
}; 

、ここ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#の側:SampleATLProjectのデバッグまたは所望の出力ディレクトリへ

  1. 移動し、C++プロジェクトの出力の一部として生成された.tlbファイルでTlbimp.exeを実行します。以下の私の仕事:

    tlbimpとSampleATLProject.tlb /out:Foo.dll /namespace:SampleATL.FooStuff

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

  3. Referencesフォルダで、Fooのプロパティに移動し、埋め込みInteropタイプをfalseに設定してオフにします。
  4. SampleATL.FooStuffをtlbimpに指定してusingステートメントを追加し、[STAThread]属性をMain()に追加しました(COMアパートメントモデルはインプロセス消費のために一致する必要があります)。COMコンポーネントを呼び出すコードが追加されました。ここTlbimp.exe (Type Library Importer) @ 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); 
     } 
    } 
} 

最後に、それが実行され、私は次の文字列を持つモーダルポップアップが表示され得る:4/8バイトパッキングがそうであるように(

Size: 12, Values: 1 127 255 128 

は、プラグマパックの変更を行うことができることを確認します使用される最も一般的なアライメント)、私はこれを1に変更するには、次の手順に従っ:

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

私はここからそれを実行し、この出力ました:

Size: 12, Values: 1 1551957760 129 3 

奇妙です。しかし、SampleATLProject_i.hのCレベルのプラグマを強制的に編集すると、正しい出力が得られます。 SampleATLProjectが、ここでは.TLBまたは.NETプロジェクトへの変更を再コンパイルされていない、と私たちは、次の取得

#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) 

Size: 7, Values: 1 127 255 128 

IDispatchに関しては、それはあなたのクライアントは遅延バインディングであるかどうかに依存します。遅延バインドされたクライアントは、タイプ情報側をIDispatchと解析し、重要でないタイプの適切な定義を識別する必要があります。 ITypeInfoTYPEATTRのドキュメントは、cbAlignmentフィールドが必要な情報を提供することを前提として可能であることを示しています。私は、ほとんどがデフォルトに反して行ったり、デフォルトに反することがないと思っています。これは、状況が間違っていたり、バージョン間でパックの期待値が変更されなければデバッグするのが面倒です。また、構造体は通常、IDispatchを消費する多くのスクリプトクライアントによってサポートされていません。 IDL oleautomationキーワードで管理されているタイプのみがサポートされることがよくあります。

IDispatch interface @ MSDN
IDispatch::GetTypeInfo @ MSDN
ITypeInfo interface @ MSDN
TYPEATTR structure @ MSDN

oleautomation keyword @ MSDN

5

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

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

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

関連する問題