2012-04-13 27 views
3

我正在與第三方COM服務器與自己的自定義接口,設置和獲取結構的一些屬性。碰巧我使用C++作爲客戶端。我從以下IDL文件中發佈了一些代表性代碼,其中更改了名稱並刪除了GUID。在COM接口中傳遞的結構包裝是否已定義?

是否定義了結構的包裝,還是我的客戶端代碼碰巧使用了與COM服務器一起構建的相同包裝設置?在默認的C++編譯器打包設置被更改的項目中,它可能會出錯嗎?是否有可用於確保客戶端編譯器打包設置正確的編譯包設置?

我在MIDL生成的IDL或頭文件中看不到任何打包雜注或語句。如果客戶端使用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

默認的包裝是沿着8字節邊界,根據MIDL命令行開關參考這裏:

/Zp switch @ MSDN (MIDL Language Reference)

你的代碼的其他部分更容易,如果包先破因爲IDL文件通常會提前預編譯,而且很少有人會故意改變給MIDL的命令行開關(但不是很少有人可以擺弄C範圍#pragma pack而忘記了恢復默認狀態)。

如果您有充分的理由更改設置,則可以使用pragma pack聲明明確設置包裝。

pragma Attribute @ MSDN (MIDL Language Reference)

這是相當好運氣,任何一方發生了變化,將使用默認的包裝干涉任何設置。它會出錯嗎?是的,如果有人不想改變默認設置。

使用IDL文件時,詳細信息通常會編譯到typelib(.tlb)中,並且假定使用相同typelib時,平臺對於服務器和客戶端都是相同的。這在/Zp開關的腳註中被提出,因爲某些值將針對某些非x86或16位目標而失敗。也可能有32位< - > 64位轉換情況,可能會導致期望中斷。不幸的是,我不知道是否有更多的案例,但默認情況下工作最少。

C#和VB沒有任何內部行爲來處理.tlb中的信息;相反,像tlbimp這樣的工具通常用於將COM定義轉換爲.NET中可用的定義。我無法驗證C#/ VB.NET與COM客戶端和服務器之間的所有預期是否成功;但是,如果您引用從在該設置下編譯的IDL創建的.tlb,則可以驗證使用8以外的特定雜注設置是否可行。雖然我不推薦使用默認的編譯包,但如果您希望將工作示例用作參考,則需要執行以下步驟。我創建了一個C++ ATL項目和一個C#項目來檢查。

這裏是C++端說明。

  1. 我創建了一個名爲SampleATLProject在Visual Studio 2010中的默認設置的ATL項目,沒有任何字段改變。這應該爲你創建一個dll項目。
  2. 編譯項目以確保正在創建正確的C端接口文件(SampleATLProject_i.c和SampleATLProject_i.h)。
  3. 我在項目中添加了一個名爲SomeFoo的ATL簡單對象。再次,沒有違約被改變。這會創建一個名爲CSomeFoo的類,並將其添加到您的項目中。
  4. 編譯SampleATLProject。
  5. 我右鍵單擊SampleATLProject.idl文件,然後在MIDL設置下將Struct Member Alignment設置爲4個字節(/ Zp4)。
  6. 編譯SampleATLProject。
  7. 我改變了IDL來添加一個名爲'BarStruct'的結構定義。這需要在MIDL uuid屬性中添加C風格的結構定義,並在庫部分中引用結構定義。請參閱下面的代碼片段。
  8. 編譯SampleATLProject。
  9. 從類視圖,我右鍵點擊ISomeFoo和添加了一個名爲FooIt方法,採用一個struct BarStruct作爲稱爲theBar一個[in]參數。
  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 /namespace:SampleATL.FooStuff

  2. 接下來,我創建了一個C#控制檯應用程序,並增加了一個參考Foo.dll到項目。

  3. 在參考文件夾中,轉至Foo的屬性並關閉將互操作類型設置爲false。
  4. 我添加了一個using語句來引用命名空間SampleATL.FooStuff給予tlbimp,將[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); 
     } 
    } 
} 

最後,它運行和我出現以下字符串模式彈出顯示:

Size: 12, Values: 1 127 255 128 

要確保一個編譯包的變化可以製成(如4/8字節包裝是使用最普遍的比對),I按照這些步驟將其改爲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。人們經常會期望只支持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設置匹配。不難做到。

您完全意識到任何結構都可以通過具有屬性的接口進行描述,從而解決了這個問題。現在沒關係。

相關問題