per*_*age 5 com idl visual-c++
我正在使用第三方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 */
};
Run Code Online (Sandbox Code Playgroud)
根据MIDL命令行开关引用,默认打包是沿8字节边界:
如果更改了包值,代码的其他部分更有可能首先破坏,因为IDL文件通常是提前预编译的,并且很少有人会故意改变提供给MIDL的命令行开关(但不是很少有人可以摆弄C-scope #pragma pack并忘记恢复默认状态).
如果您有充分的理由更改设置,则可以使用pragma pack语句显式设置打包.
pragma Attribute @ MSDN(MIDL语言参考)
非常幸运的是,任何一方都没有改变任何会干扰默认包装的设置.它会出错吗?是的,如果有人不顾一切地改变默认值.
使用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名为theBar的[in]参数.这是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;
};
};
Run Code Online (Sandbox Code Playgroud)
在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;
}
Run Code Online (Sandbox Code Playgroud)
接下来,在C#端:
转到SampleATLProject的调试或所需输出目录,并在作为C++项目输出的一部分生成的.tlb文件上运行tlbimp.exe.以下对我有用:
tlbimp SampleATLProject.tlb /out:Foo.dll /namespace:SampleATL.FooStuff
接下来,我创建了一个C#控制台应用程序,并在项目中添加了对Foo.dll的引用.
Foo并通过将Embed Interop Types设置为false来关闭它.SampleATL.FooStufftlbimp给出的命名空间,将[STAThread]属性添加到Main()(COM公寓模型必须匹配进程内消耗),并添加一些代码来调用COM组件.这是该控制台应用程序的源代码.
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
最后,它运行,我得到一个模态弹出窗口,显示以下字符串:
Size: 12, Values: 1 127 255 128
Run Code Online (Sandbox Code Playgroud)
为了确保可以更改pragma包(因为4/8字节打包是最常用的对齐方式),我按照以下步骤将其更改为1:
Foo,但如果单击该参考,则可能会消失.如果没有,您可以删除并重新添加对C#控制台项目的引用,以确保它使用新的更新版本.我从这里运行它得到了这个输出:
Size: 12, Values: 1 1551957760 129 3
Run Code Online (Sandbox Code Playgroud)
那真是怪了.但是,如果我们在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)
Run Code Online (Sandbox Code Playgroud)
这里重新编译SampleATLProject,对.tlb或.NET项目没有任何更改,我们得到以下内容:
Size: 7, Values: 1 127 255 128
Run Code Online (Sandbox Code Playgroud)
关于IDispatch,这取决于您的客户是否迟到.后期绑定客户端必须解析类型信息方面IDispatch并识别非平凡类型的正确定义.文件ITypeInfo并TYPEATTR建议可以,因为该cbAlignment字段提供了必要的信息.我怀疑大多数人永远不会改变或违反默认值,因为如果出现问题或者版本之间的包装期望必须改变,这将是繁琐的调试.此外,许多可以使用的脚本客户端通常不支持结构IDispatch.人们经常可以预期只oleautomation支持由IDL 关键字控制的类型.
IDispatch接口@ MSDN
IDispatch :: GetTypeInfo @ MSDN
ITypeInfo接口@ MSDN
TYPEATTR结构@ MSDN
是的,结构是COM中的一个问题.如果你使用基于IUnknown的接口,那么你将不得不使用适当的编译器设置来掷骰子.更改默认值的理由很少.
如果使用COM Automation,则必须在.IDL中使用typedef声明结构.这样客户端代码就可以使用IRecordInfo在类型库信息的引导下正确访问结构.您所要做的就是确保编译器的/ Zp设置与midl.exe的/ Zp设置相匹配.不难做到.
通过认识到任何结构都可以通过具有属性的接口来描述,您可以完全解决问题.现在没关系.