我正在使用第三方COM服务器,它有自己的自定义接口,可以设置和获取结构作为它的一些属性。碰巧我正在为客户端使用C ++。我在下面的IDL文件中发布了一些代表性代码,其中名称已更改且GUID已删除。
是否定义了结构的包装或者我的客户端代码是否恰好使用与COM服务器构建的相同打包设置?在默认C ++编译器打包设置已更改的项目中是否可能出错?是否有可用于确保客户端编译器打包设置正确的pragma包设置?
我无法在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 */
};
答案 0 :(得分:7)
默认打包是沿着8字节边界,根据这里的MIDL命令行开关引用:
/Zp switch @ MSDN (MIDL Language Reference)
如果更改了包值,代码的其他部分更可能先破坏,因为IDL文件通常是提前预编译的,并且很少有人会故意改变提供给MIDL的命令行开关(但并非罕见,有人可以摆弄C范围#pragma pack
并忘记恢复默认状态。)
如果您有充分的理由更改设置,可以使用pragma pack
语句明确设置打包。
pragma Attribute @ MSDN (MIDL Language Reference)
非常幸运的是,没有任何一方改变任何会干扰默认包装的设置。它会出错吗?是的,如果有人不顾一切地改变默认值。
使用IDL文件时,通常会将详细信息编译为类型库(.tlb),并且假设使用相同的类型库时,服务器和客户端的平台相同。这在/Zp
开关的脚注中建议,因为某些值将针对某些非x86或16位目标失败。还可以有32位< - >。 64位转换案例可能导致预期中断。不幸的是,我不知道是否还有更多的案例,但默认情况下的工作量很小。
C#和VB没有任何内在行为来处理.tlb中的信息;相反,像tlbimp这样的工具通常用于将COM定义转换为可从.NET使用的定义。我无法验证C#/ VB.NET与COM客户端和服务器之间的所有期望是否成功;但是,我可以验证,如果引用从在该设置下编译的IDL创建的.tlb,则使用8以外的特定编译指示设置将起作用。虽然我不建议违反默认的pragma包,但如果您想要一个工作示例作为参考,请执行以下步骤。我创建了一个C ++ ATL项目和一个C#项目来检查。
以下是C ++方面的说明。
SomeFoo
的ATL简单对象。同样,没有更改默认值。这将创建一个名为CSomeFoo
的类,该类将添加到您的项目中。ISomeFoo
并添加了一个名为FooIt
的方法,该方法将struct BarStruct
作为[in]参数,名为 theBar 强> 这是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来关闭嵌入互操作类型。SampleATL.FooStuff
,将[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
为了确保可以更改pragma包(因为4/8字节打包是最常用的对齐方式),我按照以下步骤将其更改为1:
Foo
的.NET文件参考上,但如果单击参考,可能会消失。如果没有,您可以删除并重新添加对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
答案 1 :(得分:6)
是的,结构是COM中的问题。如果您使用基于IUnknown的接口,那么您将必须使用适当的编译器设置来掷骰子。改变默认值的理由很少。
如果使用COM Automation,则必须在.IDL中使用typedef声明结构。这样客户端代码就可以使用IRecordInfo在类型库信息的指导下正确访问结构。您所要做的就是确保编译器的/ Zp设置与midl.exe的/ Zp设置相匹配。不难做到。
通过认识到任何结构都可以通过具有属性的接口来描述,您可以完全解决问题。现在没关系。