默認的包裝是沿着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++端說明。
- 我創建了一個名爲SampleATLProject在Visual Studio 2010中的默認設置的ATL項目,沒有任何字段改變。這應該爲你創建一個dll項目。
- 編譯項目以確保正在創建正確的C端接口文件(SampleATLProject_i.c和SampleATLProject_i.h)。
- 我在項目中添加了一個名爲
SomeFoo
的ATL簡單對象。再次,沒有違約被改變。這會創建一個名爲CSomeFoo
的類,並將其添加到您的項目中。
- 編譯SampleATLProject。
- 我右鍵單擊SampleATLProject.idl文件,然後在MIDL設置下將Struct Member Alignment設置爲4個字節(/ Zp4)。
- 編譯SampleATLProject。
- 我改變了IDL來添加一個名爲'BarStruct'的結構定義。這需要在MIDL uuid屬性中添加C風格的結構定義,並在庫部分中引用結構定義。請參閱下面的代碼片段。
- 編譯SampleATLProject。
- 從類視圖,我右鍵點擊
ISomeFoo
和添加了一個名爲FooIt
方法,採用一個struct BarStruct
作爲稱爲theBar一個[in]參數。
- 編譯SampleATLProject。
- 在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#側:
轉到調試或期望的輸出目錄SampleATLProject和作爲C++項目輸出的一部分生成.tlb文件運行tlbimp.exe是。以下爲我工作:
TLBIMP SampleATLProject.tlb /out:Foo.dll /namespace:SampleATL.FooStuff
接下來,我創建了一個C#控制檯應用程序,並增加了一個參考Foo.dll到項目。
- 在參考文件夾中,轉至
Foo
的屬性並關閉將互操作類型設置爲false。
- 我添加了一個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:
- 我返回到C++項目,就到了屬性SampleATLProject.idl,改變了結構體成員對齊爲1(/ ZP1)。
- 重新編譯SampleATLProject
- 再次用更新的.tlb文件運行tlbimp。
- .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
的類型信息,並辨別非平凡類型的正確定義。 ITypeInfo
和TYPEATTR
的文檔表明,如果cbAlignment
字段提供了必要的信息,那麼這是可能的。我懷疑大多數人永遠不會改變或違背默認設置,因爲如果事情出錯或者版本之間的軟件包期望不得不改變,那麼調試會很乏味。此外,許多腳本客戶端通常不支持結構,這些客戶端可能會使用IDispatch
。人們經常會期望只支持IDL oleautomation
關鍵字所支持的類型。
IDispatch interface @ MSDN
IDispatch::GetTypeInfo @ MSDN
ITypeInfo interface @ MSDN
TYPEATTR structure @ MSDN
oleautomation keyword @ MSDN