Defining Custom Interfaces in IDL

 < Free Open Study > 



Every interface definition must at the very least be specified with the [object] attribute (which marks it as a COM interface, not a DCE IDL interface) and the [uuid()] attribute (which specifies, once and for all, the GUID for this interface). As we are building COM interfaces, you must derive your custom interfaces from IUnknown or some other interface ultimately deriving from IUnknown. The interface itself is declared using the interface keyword. Here is a basic skeleton for a custom IDL interface definition:

// Your custom IDL interface definitions will take on the following generic format. import "oaidl.idl";                 // Bring in definition of IUnknown & friends. [      object,                        // Marks interface as a COM interface.      uuid( my IID )                 // Get a GUID with guidgen.exe ] interface IMyInterface : IUnknown   // Or any IUnknown derived interface { }; 

Note 

DCE IDL uniquely specifies items with universally unique IDs, or UUIDs. A GUID is just a UUID, and therefore Microsoft IDL recycled the existing DCE IDL (uuid), rather than introducing a new and redundant (guid) attribute.

To generate a new IID for your custom interfaces in IDL, use guidgen.exe. This time, however, do not select DEFINE_GUID but instead opt for the Registry Format option and remove the curly brackets ({ }) from the paste. If you already have an existing GUID declared using the DEFINE_GUID macro which you wish to define in IDL, you may simply paste in the guidgen.exe generated commented line as the [uuid()] attribute parameter:

// {442F32E2-E7EE-11d2-B8D2-0020781238D4} DEFINE_GUID(IID_IShapeEdit, 0x442f32e2, 0xe7ee, 0x11d2, 0xb8, 0xd2, 0x0, 0x20, 0x78, 0x12, 0x38, 0xd4);

When you are creating IDL interface definitions, be aware that the COM macros are not part of an IDL interface definition, but are rather a result of the MIDL compilation. Here is the IDL definition for IShapeEdit (without any interface methods):

// ShapeServer.idl [object, uuid(442F32E2-E7EE-11d2-B8D2-0020781238D4)] interface IShapeEdit : IUnknown          // No 'public' keyword in IDL. { };

Note how we specify we are derived from IUnknown without use of a public keyword. If we mistakenly write an IDL definition for IShapeEdit as so:

// An incorrect IDL definition... interface IShapeEdit : public IUnknown     // Nope! { };

we are issued errors from the MIDL compiler. As well, interface methods defined in IDL do not prefix virtual and suffix =0. All methods defined in IDL are considered pure virtual (as they must be, as IDL won't let you implement anything anyway).

Directional Parameter Attributes

Method parameters are also decorated with IDL attributes. IDL uses the [in], [out], and [in,out] attributes to tag a given parameter in an interface method. These attributes are used to help streamline the transmission of data between processes and machines during the marshaling process. As well, these same attributes help COM clients and servers understand exactly who is responsible for the allocation and deallocation of memory during the method invocation:

Parameter Attribute

Meaning in Life

[in]

Sent from the client to the server. The client allocates and frees the memory for this parameter. All parameters are defaulted to [in] unless specified otherwise.

[out]

Sent from the server to the client. The client is responsible for freeing the retrieved data. [out] parameters are always pointers to some IDL type.

[in, out]

Sent from the client to the server. The client allocates and destroys the memory for this parameter; however, the server may optionally reallocate the memory during the method invocation.

With this, we can now define the methods of IShapeEdit in IDL:

// The complete IDL definition of IShapeEdit. [object, uuid(4B475690-DE06-11d2-AAF4-00A0C9312D57)] interface IShapeEdit : IUnknown {      HRESULT Fill( [in] FILLTYPE fType);      HRESULT Inverse();      HRESULT Stretch( [in] int factor); };

When designing COM interfaces, we should always return HRESULTs as the physical return value of the method (when you wish to create logical return values, simply configure a number of [out] method parameters). Here is another example of a custom IDL- defined interface:

// IDraw in IDL syntax. [ object, uuid(4B475690-DE06-11d2-AAF4-00A0C9312D57) ] interface IDraw : IUnknown {      HRESULT Draw();          // no params. };

Creating Enumerations in IDL

To fully port IShapeEdit into IDL syntax, we need to establish a custom FILLTYPE enumeration. IDL enumerations will correctly map into the C, C++, Visual Basic, and Java language mappings. IDL allows us to define enumerations in a C-like manner, and therefore IDL enums must make use of typedef syntax. IDL enumerations may take a GUID attribute to mark them as unique in all space and time. Here is the FILLTYPE enum:

// Creating enums in IDL is just about identical as C syntax. [uuid(442F32E0-E7EE-11d2-B8D2-0020781238D4), v1_enum] typedef enum FILLTYPE {      HATCH      = 0,      SOLID      = 1,      POLKADOT   = 2 } FILLTYPE;

Enumerations in IDL are defaulted as 16-bit. To transmit your IDL enums as 32-bit entities, be sure to declare the enumeration with the [v1_enum] attribute. Using the [v1_enum] attribute increases the efficiency of marshaling and unmarshaling data when the enumeration is embedded within an IDL structure or union. To that end, [v1_enum] is an optional attribute.

Self-Documenting IDL

IDL provides a number of ways to allow your IDL definitions to be self-describing. The first is an IDL attribute called [helpstring]. This attribute takes a text literal that can be used by various COM object browsers (as found in the VB and J++ IDEs) to help document what a given item is all about. This helpful text information becomes part of the binary type library (*.tlb) file.

The second way to document your IDL is by using the cpp_quote() IDL keyword. cpp_quote() also takes a string literal. However, these strings don't end up in your *.tlb file; they end up in the MIDL-generated C++ source code. Recall that the *.h file generated by MIDL holds the C and C++ language bindings. While you could add code for conditional compilation, pragma settings, and even "real code," it is safer to simply use cpp_quote() to add verbose comments to the MIDL-generated *.h file. For example, here is IShapeEdit making use of these new IDL conventions:

// Using the cpp_quote keyword and the helpstring attribute. cpp_quote("// This is my custom enum") [      uuid(442F32E0-E7EE-11d2-B8D2-0020781238D4), v1_enum,      helpstring("This is the FILLTYPE enumeration used with IShapeEdit") ] typedef enum FILLTYPE {      HATCH = 0,      SOLID = 1,      POLKADOT = 2 } FILLTYPE; [      object, uuid(442F32E2-E7EE-11d2-B8D2-0020781238D4),      helpstring("IShapeEdit allows you to modify a shape") ] interface IShapeEdit : IUnknown {      [helpstring("Fill a shape")] HRESULT Fill([in] FILLTYPE fType);      [helpstring("Invert a shape")] HRESULT Inverse();      [helpstring("Stretch a shape by n")] HRESULT Stretch([in] int factor); };

These [helpstrings] will be very useful in Visual Basic and Java client projects, as they will be integrated into the various Object Browser tools used by the IDE. This will allow your COM objects to be well documented, and hopefully more likely to be used (and purchased) by folks other than yourself. Get in the habit of using [helpstrings] and keep the COM universe a more understandable place.

Defining Library Statements in IDL

Now that you are familiar with defining COM interfaces, methods, and parameters in IDL, we need to learn how to define the coclasses that live inside the binary COM server. Remember that COM development languages and tools can make use of type information to discover the set of COM objects living in a component house (DLL or EXE server). In order to generate type information, we must define what is commonly called a library statement using the IDL library keyword.

Every library statement must have a [uuid] attribute and should have [version] and [helpstring] attributes as well. The name of the library is irrelevant as far as MIDL is concerned; however, it should have some bearing on the DLL or EXE itself. This said, here is an initial library statement for a server named Shapes.dll :

// A library statement contains all the coclasses found in your server, and // the interfaces supported by each coclass. [      uuid(442F32E1-E7EE-11d2-B8D2-0020781238D4),      version(1.0),      helpstring("The Shapes Library") ] library ShapesLibrary {      importlib("stdole32.tlb");     // importlib() must be the first item in a library. };

The importlib keyword is very similar to the import keyword. However, rather than bringing in existing IDL code, importlib imports the compiled (binary) type libraries. Every library statement needs to import the standard OLE type library stdole32.tlb at a minimum, and must do so as the very first line of the library statement. When you include stdole32.tlb in your library statement, you ensure that the core COM definitions come along with your custom information. The [version] attribute allows you to incrementally add more and more functionality to a server—simply adjust the major and minor version with each new release.

Defining Coclasses in IDL

Every coclass in our server must be listed within the library statement. The only necessary IDL attribute a coclass must have is [uuid], which serves as the CLSID of the COM class. To declare a COM class in the library, use the coclass keyword. To inform the world that Shapes.dll contains a COM object named CoHexagon, we can write:

// The CoHexagon coclass is part of the shapes library. [      uuid(442F32E1-E7EE-11d2-B8D2-0020781238D4),     // LIBID      version(1.0), helpstring("The Shapes Library") ] library ShapesLibrary {      importlib("stdole32.tlb");      [uuid(442F32E3-E7EE-11d2-B8D2-0020781238D4)]    // CLSID      coclass CoHexagon      {      }; };

So far so good. However, we have not yet specified which interfaces the CoHexagon coclass implements. To specify that CoHexagon implements IDraw and IShapeEdit, use the interface keyword within the library statement:

// IDL uses the interface keyword to (a) define the interface and // (b) bind it to a given coclass. [uuid(442F32E3-E7EE-11d2-B8D2-0020781238D4)] coclass CoHexagon {      interface IDraw;      interface IShapeEdit; }; 

Specifying the Default Interface

Some COM language mappings support what is known as a default interface. This simply means "What interface do I get for free when I create you?" and is only found in lazy COM language such as VB. C++ developers do not get a free interface when they create a coclass using the COM library—we need to ask up front using CoCreateInstance() or through a valid IClassFactory pointer returned by CoGetClassObject(). Every coclass defined in your IDL file should specify exactly one default interface using the [default] attribute. For example, if we were to assign IDraw as the default interface of CoHexagon, we could write:

// Marking a default interface of the coclass.  [uuid(442F32E3-E7EE-11d2-B8D2-0020781238D4)] coclass CoHexagon {      [default] interface IDraw;      interface IShapeEdit; };

VB developers access the default interface of a coclass automatically when they declare a new instance of the COM class. Under the hood, the hex variable really points to the default interface as defined by our type library. The VB developer may now use the dot operator to access any methods defined in IDraw:

' VB client code creating the CoHexagon. ' Dim hex as New CoHexagon         ' I now have access to IDraw. hex.Draw                         ' I want to draw the hexagon!

If we defined the IShapeEdit interface as the coclass default, the VB developer would have initial access to those methods from the hex object. When deciding which interface should be defaulted, use common sense and ask yourself which interface captures the "essence" of a coclass. If you do not speak your mind, MIDL will automatically designate the first interface listed by the coclass as the [default].

Physical and Logical Return Values: The [retval] Attribute

Interface methods should return an HRESULT which serves as the "physical" return value of the given method (by this, I mean the literal function return type). COM clients can test this return value to determine the success or failure of a given method invocation. But what if you need to return some "logical" values to your client, such as the result of a string concatenation or the result of adding two numbers? When you wish to return a logical return value you may decide to define [out] parameters (which are always pointers). Assume you are creating a very simple interface named IBasicMath that adds two numbers, and sends the result back to the client as an [out] parameter:

// Add has a physical return value of HRESULT, and a logical return value // of int* [ object, uuid(709EF655-E8A3-11d2-B8D2-0020781238D4) ] interface IBasicMath : IUnknown {      HRESULT Add([in] int x, [in] int y, [out] int* answer); }; 

C++ clients may utilize logical and physical return values as so:

// C and C++ clients have no problem using [out] parameters. HRESULT hr; int ans = 0; ... hr = pIBasicMath->Add(20, 20, &ans); if(SUCCEEDED(hr))      cout << " 20 + 20 = " << ans << end;

In addition to [out] parameters, some COM mappings, such as Visual Basic, support the [retval] attribute which allows you to mark the final parameter of a method to serve as the physical return value. Here is the Add() method of IBasicMath now with a specific [out, retval] parameter:

// By marking a parameter as [out, retval] we enable a way to return useful // information to [out] challenged COM languages mappings. [ object, uuid(709EF655-E8A3-11d2-B8D2-0020781238D4) ] interface IBasicMath : IUnknown {      HRESULT Add([in] long x, [in] long y, [out, retval] long * answer); };

The final parameter of Add() will now behave as a physical return in COM language mappings that support [retval]. Assume we have a coclass named CoCalculator that specifies IBasicMath as the default interface. A VB client would work with IBasicMath as so:

' VB COM client code. ' Dim c as New CoCalculator          ' Assess to [default] IBasicMath. Dim ans as Integer ans = c.Add (20, 20)               ' [out, retval] is mapped to physical return.

Languages that do not support [out, retval] parameters simply ignore [retval], and treat the parameter as a logical return (e.g., [out] only). A C++ client will make use of this interface as before:

// Raw C++ clients ignore [out, retval] parameters. HRESULT hr; int long = 0; ... hr = pIBasicMath->Add(20, 20, &ans); if(SUCCEEDED(hr))      cout << " 20 + 20 = " << ans << end;



 < Free Open Study > 



Developer's Workshop to COM and ATL 3.0
Developers Workshop to COM and ATL 3.0
ISBN: 1556227043
EAN: 2147483647
Year: 2000
Pages: 171

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net