Specify struct packing in C# implementation of a COM interface
Is it possible to specify the struct packing size in a C# implementation of a COM interface?
(I know how to do it when the struct is defined on the managed side, but my question is about when it's defined on the unmanaged side and implemented on the managed side.)
I've got a COM type library that defines a struct type and an interface method that returns an array of those structs. I've got a C# server and an unmanaged C++ server that both implement this interface, and a C++ client that consumes it. The C++ serv开发者_开发问答er and the C++ client both pack the struct to 4 bytes in a 32-bit build, and to 8 bytes in a 64-bit build.
But the C# server always packs to 4 bytes, regardless of platform (x86, x64, AnyCPU). Is this normal? Can it be overridden?
The struct looks like this:
typedef [v1_enum] enum { blah... } HandlerPriority;
struct HandlerInfo { BSTR MessageName; HandlerPriority Priority; }
The Visual Studio C++ and MIDL compilers use a default packing of /Zp8. In a 32-bit build both members of the struct are 4 bytes wide, and so they are not padded. In a 64-bit build the string pointer is 8 bytes and the enum 4, so the enum is padded. Naturally, this causes problems when the C# client sends unpadded data.
I can fix (work around?) the problem by specifying /Zp4 to remove the padding, and everything seems to work fine. But I wonder whether that's the best solution.
I imagine the default packing is /Zp8 for performance reasons only. As I understand it, by default on x64 the hardware traps and handles alignment exceptions and so at least we won't crash. And in this particular situation I don't care about the performance penalty because the interface function is only called at system startup. And even if I did care I might still accept it as the cost of COM/.NET interop. But I'm a little uneasy because it feels wrong (coming from a C++ background, I suppose.)
On the other hand, if it's simply not possible to change the packing on the managed side, then I'll live with it.
Can anyone give me some advice?
The packing size is specified by tlbimp.exe in the RCW that it generates. If I pass /platform:x64 on the command line then the RCW says ".pack 8", but if I say /platform:x86 or /platform:agnostic then it says ".pack 4".
I do want /platform:agnostic so that I can use the same RCW on both 32- and 64-bit platforms. Normally I find that AnyCPU is more trouble than it's worth, but this project is an SDK and I don't want to impose my views on that topic on my users if I can avoid it.
I also want 8-byte packing because 4-byte packing on x64 can be expensive. See http://msdn.microsoft.com/en-us/library/aa290049%28v=vs.71%29.aspx.
The solution I've settled on is to decompile the RCW, change the .pack directive in the generated source code, and recompile.
Will the Pack attribute work here for you? Here's an example from my own code:
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct TOKEN_PRIVILEGES
{
public int privilegeCount;
public LUID_AND_ATTRIBUTES privileges;
}
I have used this when configuring services and I use the Pack=1 attribute to align the privileges field exactly after the privilegeCount field when marshalling this struct to Win32.
We had a similar problem, some structures, when passed from C# to C++ through COM, worked in 32-bit COM, but were corrupted in 64-bit.
I spent some time researching and this thread was also helpful.
It looks like TlbImp.exe tries to pack every structure as 4-bytes aligned, and only if it does not meet certain criteria, packs a structure as 8-bytes aligned. It does this both for 32-bit and 64-bit mode.
But it has some strange rules to determine it. What I found, for x64, if a structure only has 4-byte integers, enums and safearrays, it is packed as 4-bytes aligned, otherwise it is packed as 8-bytes alinged. For x86, a structure is only packed as 8-bytes aligned when a 64-bit variable is present in it, otherwise, it is packed as 4-bytes aligned. Maybe there are other variants, but this is only what we found in our product.
C++, on the other hand, with its default packing (8 bytes), packs each type aligned by a minimum of 8 bytes or the size of a member.
So, for 32-bit mode, there is no problem since there is no difference between 4 and 8 bytes aligning if there are no 8-bytes sized members in the structure. But if there are, TlbImp.exe packs the structure as 8-bytes aligned, so this also matches C++.
For 64-bit mode, however, there is a case, when a structure has 8-bytes member, but is packed as 4-bytes aligned. In our case it is when it has SAFEARRAYs. If a structure has only SAFEARRAYs and 32-(or less?)-bit integers, it is packed as 4-bytes aligned for x64 platform. But, since SAFEARRAY is a 64-bit pointer on C++ side, it is packed there differently, padding areas before SAFEARRAYs to 8-byte margins. I don't know, whether it is a bug or a feature.
To fix this problem, we injected C++ pragma pack instructions before such 4-bytes aligned structures in 64-bit. We identified such structures by decompiling the assembly with ildasm.exe and searching for ".pack 4" (thanks Ciaran for the hint).
Such a structure in IDL looks like this:
// some of our structures must be 4 bytes aligned because TlbImp packs them this way
cpp_quote("#pragma pack(push, 4)")
typedef [uuid("3F253C09-D7F5-3BE-9698-00CB49A7005C"),
helpstring("SOME structure"),
version(5.1)] struct SOMEINFO
{
long ItemsActive;
SAFEARRAY(BSTR) ItemIDs; // without pack 4 it would be 8 bytes aligned on x64
SAFEARRAY(long) ItemRuns;
SAFEARRAY(SOMEFunctions) ItemFuncList;
SAFEARRAY(VARIANT) ItemFactors;
long MIndex;
SAFEARRAY(VARIANT) DeltaItems;
} SOMEINFO;
cpp_quote("#pragma pack(pop)") // matches pack(push, 4) placed before the structure
Alternatively, we could just add to this structure a 64-bit dummy variable, or BSTR to force it into .pack 8, but we didn't want to change the interface.
So, we are basically following TlbImp it its strange logic. But there are just a few of such structures in our code, so, it is fine for us. This way, both 32 and 64 bit COM pass all the structures correctly.
精彩评论