默认的包装是沿着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