Documentation
Goal
The goal of Brutus is to provide platform independent, complete and open access to the entire MAPI subsystem for client applications. With reservations, this goal has been met.
The biggest reservation is that certain well-defined portions of this framework is really irrelevant for true client applications, i.e. applications that are not service providers in the MAPI sense. Fortunately, those parts of MAPI are really easy to implement, and we can get them covered fairly quickly, should someone need them.
MAPIForms has not been implemented either. I decided that MAPIForms is used be a minority of end users and that it wasn't worth the trouble to implement, unless someone should ask for the (mis-)feature. A functionally equivalent framework can be designed which is much cleaner and platform agnostic.
Introduction
Brutus is a (large) set of CORBA interfaces with appropriate methods. Each interface is defined so as to wrap the functionality and behavior of a corresponding MAPI interface or function. An example would be the BRUTUS::IMAPIFolder:: Brutus interface which is exposing the functionality of the IMAPIFolder:: MAPI interface.
All Brutus interfaces and types are within the BRUTUS:: namespace.
Build instructions
Brutus is a CORBA project, so the exact build instructions depends highly of what you want to do.
I can not give much of an assistance to your client development efforts without knowing the specifics (implementation language, development environment...), but you can use any standard text on CORBA application development. Another source of information would be the sample code that are distributed within brutus-idl. Please see the download page. You should always read the minimalistic design document.
It is an entirely different matter if you want to assist in the development of the Brutus server (servant) implementation.
Sample code
Sample code is always released with the IDL files. Please see the download page for available downloads of Brutus.
Brutus server implementation
Basically there are two papers that you should read:
- The MS Visual Studio C++ setup HOWTO. This document explains how to get going with MSVC++ and Brutus.
- The minimalistic design document. A very short, but condensed, design document on Brutus and the Brutus server implementation. A valuable read for client as well as servant developers.
Design decisions
The goal of Brutus, complete functional wrapping of the entire client application relevant parts of the MAPI subsystem, meant that decisions, sometimes hard ones, had to be made during the design of the interfaces and the server implementation. The following is a list of some of these design decisions.
Name clashing, type confusion
All Brutus interfaces and types are within the BRUTUS:: namespace. Constants generally have a BRUTUS_ prefixed so as to keep an easy correspondence with the native MAPI entities.
Arrays
Arrays are replaced with sequences unless their size is known beforehand.
Types
A lot of types in the native MAPI interface are absolutely identical. They have the same members and the same structure. In those cases I will define a standard type with some meaningful name. The actual types will then be defined by typedefs in the hope that the IDL compiler will be able to handle those cases in an implementation effective manner. Well, it is not certain that I will actually bother doing the typedef..
As for now I will adhere more or less to the following mapping of the basic types:
MAPI CORBA
BYTE == octet
LONG == long
ULONG == unsigned long
HRESULT == Enumerated type: BRESULT
LPTSTR == string
LPWSTR == wstring
WORD == unsigned short
WM * == unsigned short
LPVOID == any
Any is used unless stated explicitly in MSDN as being of a particular type. The sole place 'any' is used is in the ADRPARM structure which is currently unused.
Unknown direction parameters
Sometimes MSDN does not mention the direction of a parameter. This is mostly the case for reserved parameters with some mandatory input value, e.g zero. I have mostly decided to give those parameters the direction (in), as the parameter in no cases could be modified by the function. I will retain the type BDEFINE even In the case of reserved flags. Unsigned long has typically been chosen for other reserved parameters.
I will not force a mandatory input value even if it is required by MSDN, as there could be some magic values which are used inside Redmond and which could be reversed engineered.
Unknown return type
Clients and servers should use _narrow() to cast the interface or type from the most basic interface, BRUTUS::IUnknown, to the known derived interface or type.
Pointer to function
Pointers to functions are mapped to an interface with a single operation. The interface in question is prefixed with fn_. There is currently no uses of fn_ interfaces. They should probably just go away..
Functions
Functions are implemented as an interface with a similar named method or collected in a meta interface with related functions.
Array element count as function parameter
Parameters, such as the frequent cValue, which specifies the number of elements in an array function parameter, will not be a part of the function specification if the array is implemented as a sequence. Using length() internally instead.
Hungarian notation
Personally I think that Hungarian notation is absolutely disgusting, but I have kept the original parameter names to ease MSDN lookup.
Restructuring
I have moved the Logon() functionality to the BRUTUS::BrutusLogOn? interface. I think that I will reserve BRUTUS::BrutusSession? to management functions.
Logging
Added log reading functionality to BRUTUS::BrutusSession?.
Freeing resources
It is mandatory for all clients to call BRUTUS::IUnknown::Destroy() to properly release resources for all interfaces except BRUTUS::IMAPISession where LogOff?() optionally can be called (BRUTUS::IMAPISession::Destroy() calls BRUTUS::IMAPISession::Logoff()).
Thread synchronization
All access to individual MAPI interfaces are protected by a write lock. I do not trust MAPI to be thread-safe. This should not be a performance problem though, as individual MAPI objects are owned by different Brutus sessions and that any individual Brutus session is unlikely to use unsynchronized threading towards individual Brutus interfaces.
Memory management
MAPIFreeBuffer() will be called on all data returned from MAPI. The memory management rules for my own conversion functions are simple: BRUTUS to MAPI:
- All BRUTUS -> MAPI conversion functions are named: [MAPI Type]_brutus_to_mapi <_no_alloc>()
- All types of variable sized mapi types are allocated by the conversion function and must be freed by MAPIFreeBuffer() after use.
- All types of constant size have two *brutus_to_mapi conversion functions. One is just called [MAPI Type]_brutus_to_mapi() and the other is called [MAPI Type]_brutus_to_mapi_no_alloc(). The first function allocates all needed memory which must later be freed by MAPIFreeBuffer(). The *_no_alloc() variant assumes that all needed memory is preallocated and just fills in the converted values from the BRUTUS data type.
- All memory allocating brutus_to_mapi functions expects a void pointer to an optional parent buffer. MAPIAllocateMore() will be used if this parent pointer is non-nil. MAPIAllocateBuffer() will be used if the parent buffer is nil.
- Zero is returned if a failure is met and all MAPIAllocateBuffer() allocated memory is freed. The out parameter is guarantied to be either freed or memory bounded to the parent buffer parameter.
MAPI to BRUTUS:
- All MAPI -> BRUTUS conversion functions are named: [MAPI Type]_mapi_to_brutus()
- It is expected that all fixed size memory are preallocated for the BRUTUS parameter. Embedded strings and other variable sized types are allocated by the function.
- All [MAPI Type]_mapi_to_brutus() conversion functions expect an optional parameter, bool DeAlloc?. The original MAPI type will be freed with MAPIFreeBuffer() if DeAlloc? is true, otherwise not.
Exceptions
Exceptions will be avoided if at all possible. A sensible return value is preferred.
Return values
A direct translation "HRESULT ==> BRUTUS::BRESULT" will always be attempted. No exceptions will be thrown if MAPI returns a supported value. Of all exceptions only CORBA system exceptions will be thrown.
CORBA::NO_MEMORY is the only exception that is frequently thrown by the servant code, but BRUTUS::BRUTUS_E_NOT_ENOUGH_MEMORY will be returned if MAPI returned E_NOT_ENOUGH_MEMORY.
Memory allocation failures will result in CORBA::NO_MEMORY if an interface function are unable to allocate one or more return values. Otherwise BRUTUS::BRUTUS_E_NOT_ENOUGH_MEMORY will be returned if at all possible.
