Previous Up Next

6  Hints on writing IDL files

6.1  Writing an IDL file for a C library

When writing an IDL file for a C library that doesn't have an IDL interface already, the include files for that library are a good starting point: just copy the relevant type and functin declarations to the IDL file, then annotate them with IDL attributes to describe more precisely their actual behavior. The documentation of the library must be read carefully to determine the mode of function parameters (in, out, inout), the actual sizes of arrays, etc.

The type definitions in the IDL file need not correspond exactly with those in the include files. Often, a cleaner Caml interface can be obtained by omitting irrelevant struct fields, or changing their types. For instance, the Unix library functions for reading library entries may use the following structure:
        struct dirent {
            long int d_ino;
            __off_t d_off;
            unsigned short int d_reclen;
            unsigned char d_type;
            char d_name[256];
        };
Of those fields, only d_name and d_ino are of interest to the user; the other fields are internal information for the library functions, are not specified in the POSIX specs, and therefore must not be used. Thus, in the IDL file, you should declare:
        struct dirent {
            long int d_ino;
            char d_name[256];
        };
Thus, the Caml code will have type struct_dirent = {d_ino: int; d_name: string} as desired. However, the generated stub code, being compiled against the ``true'' definition of struct dirent, will find those two fields at the correct offsets in the actual struct.

Special attention must be paid to integer fields or variables. By default, integer IDL types are mapped to the Caml type int, which is convenient to use in Caml code, but loses one bit when converting from a C long integer, and may lose one bit (on 32-bit platforms) when converting from a C int integer. When the range of values represented by the C integer is small enough, this loss is acceptable. Otherwise, you should use the attributes nativeint, int32 or int64 so that integer IDL types are mapped to one of the Caml boxed integer types. (We recommend that you use int32 or int64 for integers that are specified as being exactly 32 bit wide or 64 bit wide, and nativeint for unspecified int or long integers.)

Yet another possibility is to declare certain integer fields or variables as double in the IDL file, so that they are represented by float in Caml, and all 32 bits of the integer are preserved in Caml. For instance, the Unix function to get the current type is declared as
        time_t time(time_t * t);
where time_t is usually defined as long. We can nonetheless pretend (in the IDL file) that time returns a double:
        double time() quote(" _res = time(NULL); ");
This way, time will have the Caml type unit -> float. Again, the stub code ``knows'' that time actually returns an integer, and therefore will insert the right integer-float coercions.

6.2  Sharing IDL files between MIDL and CamlIDL

The Microsoft software development kit provides a number of IDL files describing various libraries and components. In its current state, camlidl cannot exploit those files directly: they use many (often poorly documented) Microsoft IDL features that are not implemented yet in camlidl; symmetrically, camlidl introduces several new annotations that are not recognized by Microsoft's midl compiler. So, significant editing work on the IDL files is required.

The C preprocessor can be used to alleviate the camlidl-midl incompatibilities: camlidl defines the preprocessor symbol CAMLIDL when preprocessing its input files, while midl does not. Hence, one can bracket incompatible definitions in #ifdef CAMLIDL ... #else ... #endif. Along these lines, a C preprocessor header file, camlidlcompat.h, is provided: it uses #define to remove camlidl-specific attributes when compiling with midl, and to remove midl-specific attributes when compiling with camlidl. Thus, an IDL file compatible with both midl and camlidl would look like this:
        #include <camlidlcompat.h>

        #ifndef CAMLIDL
        import "unknwn.idl";    // imports specific to MIDL
        import "oaidl.idl";
        #endif
        import "mymodule.idl";  // imports common to MIDL and CamlIDL

        typedef [abstract,marshal_as(int)] void * ptr;

        ...

        #ifndef CAMLIDL
        [...] library MyTypeLib {
          importlib("stdole32.tlb");
          [...] coclass MyComponent { [default] interface IX; }
        }
        #endif
Notice that since camlidl doesn't handle type libraries, the type library part of an midl file must be enclosed in #ifndef CAMLIDL.

6.3  Dispatch interfaces and type libraries

A dispatch interface, in COM lingo, is an interface that supports dynamic, interpreted dispatch of method interfaces. This form of interpreted dispatch is used by Visual Basic and other scripting languages to perform calls to methods of COM components.

CamlIDL provides minimal support for dispatch interfaces. To equip a Caml component with a dispatch interface (thus making it callable from Visual Basic), you need to do the following:
  1. Use IDispatch instead of IUnknown as the super-interface of the component's interfaces.
  2. Write a type library for your component and compile it using midl. A type library is a run-time representation of the interfaces supported by an object. The midl compiler can generate a type library from the IDL description of the component, enriched with some special-purpose declarations (the library and coclass statements). Refer to the documentation of midl for more information.
  3. Pass the type library files (.tlb files) generated by midl as extra arguments to camlidldll when generating the DLL for your Caml component.

Previous Up Next