2012-04-13 4 views
3

Ich arbeite mit einem COM-Server von Drittanbietern mit einer eigenen benutzerdefinierten Schnittstelle, die Strukturen als einige seiner Eigenschaften setzt und abruft. Wie es passiert, verwende ich C++ für den Client. Ich habe einige Vertreter-Code aus der IDL-Datei unten mit Namen geändert und GUIDs entfernt.Ist das Packen von Strukturen in COM-Schnittstellen definiert?

Ist das Packen der Struktur definiert oder ist es nur Glück, dass mein Client-Code die gleichen Packeinstellungen verwendet, mit denen der COM-Server gebaut wurde? Würde es in Projekten, in denen die standardmäßigen C++ - Compiler-Packeinstellungen geändert wurden, wahrscheinlich schief gehen? Gibt es eine Pragma-Pack-Einstellung, die ich verwenden könnte, um sicherzustellen, dass die Client-Compiler-Verpackungseinstellungen korrekt sind?

Ich kann keine Verpackung Pragmas oder Aussagen in der IDL oder der Header-Datei von MIDL generiert sehen. Was würde passieren, wenn der Client stattdessen in C# oder VB wäre? Ist das Packungsverhalten klarer spezifiziert, wenn es über den IDispatch-Mechanismus aufgerufen wird?

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 */ 
}; 

Antwort

6

Die Standardverpackung ist entlang 8-Byte-Grenzen, nach dem MIDL-Befehlszeilenschalter Referenz hier:

/Zp switch @ MSDN (MIDL Language Reference)

Andere Teile des Codes sind eher zuerst, wenn die Packung brechen Der Wert wird geändert, da die IDL-Datei in der Regel vorab kompiliert wird, und es ist selten, dass jemand die Befehlszeilenschalter für MIDL absichtlich ändert (aber nicht so selten, dass jemand mit dem C-scope #pragma pack herumspielen und vergessen kann um den Standardzustand wiederherzustellen). Wenn Sie einen guten Grund haben, die Einstellung zu ändern, können Sie das Packing explizit mit einer pragma pack-Anweisung festlegen.

pragma Attribute @ MSDN (MIDL Language Reference)

Es ist ziemlich Glück, dass keine Partei eine Einstellung geändert hat, die mit der Standard-Verpackung stören würde. Kann es schief gehen? Ja, wenn jemand die Standardeinstellungen ändert.

Bei Verwendung einer IDL-Datei werden die Details in der Regel in eine typelib (.tlb) kompiliert, und es wird davon ausgegangen, dass die Plattform für Server und Clients bei Verwendung derselben Typelib identisch ist. Dies wird in den Fußnoten für den Schalter /Zp vorgeschlagen, da bestimmte Werte bei bestimmten Nicht-x86- oder 16-Bit-Zielen fehlschlagen. Es kann auch 32bit < -> 64bit Umwandlung Fälle geben, die Erwartungen brechen könnten. Leider weiß ich nicht, ob es noch mehr Fälle gibt, aber die Standardeinstellungen funktionieren mit minimalem Aufwand.

C# und VB haben kein intrinsisches Verhalten, um Informationen in einer .tlb zu behandeln; Stattdessen wird normalerweise ein Tool wie tlbimp verwendet, um COM-Definitionen in Definitionen zu konvertieren, die von .NET verwendet werden können. Ich kann nicht überprüfen, ob alle Erwartungen zwischen C#/VB.NET und COM-Clients und -Servern erfolgreich sind; Ich kann jedoch überprüfen, dass die Verwendung einer anderen Pragma-Einstellung als 8 funktioniert, wenn Sie auf eine TLB verweisen, die aus einer IDL erstellt wurde, die unter dieser Einstellung kompiliert wurde. Obwohl ich nicht empfehlen würde, gegen das Standard-Pragma-Pack zu gehen, hier sind die Schritte auszuführen, wenn ein funktionierendes Beispiel als Referenz verwendet werden soll. Ich habe ein C++ - ATL-Projekt und ein C# -Projekt zur Überprüfung erstellt.

Hier sind die C++ Seiten Anweisungen.

  1. ich ein ATL-Projekt SampleATLProject mit den Standardeinstellungen in Visual Studio 2010, keine Felder geändert genannt erstellt. Dies sollte ein DLL-Projekt für Sie erstellen.
  2. Das Projekt wurde kompiliert, um sicherzustellen, dass die richtigen C-seitigen Schnittstellendateien erstellt werden (SampleATLProject_i.c und SampleATLProject_i.h).
  3. Ich habe ein ATL Simple Object namens SomeFoo zum Projekt hinzugefügt. Auch hier wurden keine Standardeinstellungen geändert. Dies erstellt eine Klasse namens CSomeFoo, die zu Ihrem Projekt hinzugefügt wird.
  4. Kompilieren Sie SampleATLProject.
  5. Ich habe mit der rechten Maustaste auf die SampleATLProject.idl-Datei, dann unter den MIDL-Einstellungen, setzen Sie die Struct Member Alignment auf 4 Bytes (/ Zp4).
  6. Kompilieren Sie SampleATLProject.
  7. Ich habe die IDL geändert, um eine Strukturdefinition namens 'BarStruct' hinzuzufügen. Dies beinhaltete das Hinzufügen einer C-style-struct-Definition mit dem MIDL-uuid-Attribut und eines Eintrags im Bibliotheksabschnitt, der auf die struct-Definition verweist. Siehe Schnipsel unten.
  8. Kompilieren Sie SampleATLProject.
  9. aus der Klasse Ansicht, ich rechts geklickt hat auf ISomeFoo und ein Verfahren hinzugefügt FooIt, genannt, die eine struct BarStruct als [in] Parameter TheBar genannt nimmt.
  10. Kompilieren Sie SampleATLProject.
  11. In SomeFoo.cpp, ich habe etwas Code hinzugefügt, um die Größe der Struktur auszugeben und werfen Sie eine Message Box mit den Details.

Hier ist meine IDL für das ATL-Projekt.

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; 
    }; 
}; 

Innerhalb der CSomeFoo Klasse, hier ist die Implementierung für 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; 
} 

Als nächstes auf der C# Seite:

  1. Gehen Sie auf die debug oder gewünschte Ausgabeverzeichnis für SampleATLProject und tlbimp.exe auf der TLB-Datei als Teil des C++ Projekt erzeugte Ausgabe laufen. Die folgenden für mich gearbeitet:

    tlbimp SampleATLProject.tlb /out:Foo.dll /namespace:SampleATL.FooStuff

  2. Als nächstes ich eine C# Konsolenanwendung erstellt, und fügte hinzu, einen Verweis auf die Foo.dll Projekt.

  3. Wechseln Sie im Ordner "References" zu den Eigenschaften für Foo, und deaktivieren Sie Interop-Typen einbetten, indem Sie ihn auf false setzen.
  4. Ich habe eine using-Anweisung hinzugefügt, um den Namespace SampleATL.FooStuff als tlbimp zu referenzieren, fügte das [STAThread]-Attribut zu Main() hinzu (die COM-Apartmentmodelle müssen für pro-proc Verbrauch übereinstimmen) und fügten Code hinzu, um die COM-Komponente aufzurufen.

Tlbimp.exe (Type Library Importer) @ MSDN

Hier ist der Quellcode für die Konsolenanwendung.

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); 
     } 
    } 
} 

Schließlich läuft es und ich bekomme eine modale Popup mit der folgenden Zeichenfolge angezeigt:

Size: 12, Values: 1 127 255 128 

Um sicher zu sein, dass ein Pragma Pack Wechsel (als 4/8 Byteverpackung ist gemacht werden kann, die am häufigsten Ausrichtungen verwendet), gefolgt ich diese Schritte auf 1 zu ändern:

  1. ich zum C++ Projekt zurückkehrte, ging für SampleATLProject.idl auf die Eigenschaften und verändert die Struct Mitglied Ausrichtung auf 1 (/ Zp1).
  2. SampleATLProject erneut kompilieren
  3. Führen Sie tlbimp erneut mit der aktualisierten .tlb-Datei aus.
  4. Ein Warnsymbol wird in der .NET-Dateireferenz auf Foo angezeigt, kann jedoch verschwinden, wenn Sie auf die Referenz klicken. Ist dies nicht der Fall, können Sie den Verweis auf das C# -Konsolenprojekt entfernen und erneut hinzufügen, um sicherzustellen, dass die neue aktualisierte Version verwendet wird.

Ich lief es von hier und bekam diese Ausgabe:

Size: 12, Values: 1 1551957760 129 3 

Das ist seltsam. Wenn wir jedoch das C-Level-Pragma in SampleATLProject_i.h mit Nachdruck bearbeiten, erhalten wir die korrekte Ausgabe.

#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 hier neu kompiliert wird, keine Änderungen an der TLB oder .NET-Projekt, und wir erhalten die folgenden:

Size: 7, Values: 1 127 255 128 

In Bezug auf IDispatch, es hängt davon ab, ob Ihr Kunde ist spät gebunden. Spät gebundene Clients müssen die Typinformationsseite von IDispatch analysieren und die richtigen Definitionen für nicht-triviale Typen erkennen. Die Dokumentation für ITypeInfo und TYPEATTR schlägt vor, dass es möglich ist, da das Feld cbAlignment die erforderlichen Informationen bereitstellt. Ich vermute, die meisten werden niemals die Standardeinstellungen ändern oder gegen die Standardeinstellungen gehen, da dies mühsam wäre, zu debuggen, wenn etwas schief gelaufen wäre oder wenn sich die Erwartungen des Pakets zwischen den Versionen geändert hätten. Außerdem werden Strukturen normalerweise nicht von vielen Skript-Clients unterstützt, die IDispatch konsumieren können. Es kann häufig erwartet werden, dass nur die Typen unterstützt werden, die durch das Schlüsselwort IDL oleautomation gesteuert werden.

IDispatch interface @ MSDN
IDispatch::GetTypeInfo @ MSDN
ITypeInfo interface @ MSDN
TYPEATTR structure @ MSDN

oleautomation keyword @ MSDN

5

Ja, structs ein Problem in COM. Wenn Sie IUnknown-basierte Schnittstellen verwenden, müssen Sie mit den richtigen Compiler-Einstellungen würfeln. Einige Gründe, den Standard zu ändern.

Wenn Sie COM-Automatisierung verwenden, müssen Sie die Struktur mit einem Typedef in der IDL deklarieren. Damit der Client-Code IRecordInfo verwenden kann, um auf die Struktur ordnungsgemäß zuzugreifen, wird er von der Typbibliotheksinfo geleitet. Alles, was Sie tun müssen, ist sicherzustellen, dass die/Zp-Einstellung Ihres Compilers der Einstellung von/zp von midl.exe entspricht. Nicht schwer zu tun.

Sie umgehen das Problem vollständig, indem Sie erkennen, dass jede Struktur durch eine Schnittstelle mit Eigenschaften beschrieben werden kann. Jetzt ist es egal.