com on
1.0.0Utilities for dealing with COM interfaces.
About com-on
This is a small library to help work with COM interfaces under Windows. Specifically it handles initialising COM, creating and managing COM instances, GUIDs, and defining the necessary wrappers to access COM methods from Lisp.
How To
For the duration of this tutorial we will assume that the package org.shirakumo.com-on
has the local nickname com
.
For our purposes, let's suppose we want to bind the IMMDeviceEnumerator interface. We can look at the underlying definition in C by looking at the mingw headers:
MIDL_INTERFACE("a95664d2-9614-4f35-a746-de8db63617e6")
IMMDeviceEnumerator : public IUnknown
...
typedef struct IMMDeviceEnumeratorVtbl {
BEGIN_INTERFACE
/*** IUnknown methods ***/
HRESULT (STDMETHODCALLTYPE *QueryInterface)(
IMMDeviceEnumerator* This,
REFIID riid,
void **ppvObject);
ULONG (STDMETHODCALLTYPE *AddRef)(
IMMDeviceEnumerator* This);
ULONG (STDMETHODCALLTYPE *Release)(
IMMDeviceEnumerator* This);
/*** IMMDeviceEnumerator methods ***/
HRESULT (STDMETHODCALLTYPE *EnumAudioEndpoints)(
IMMDeviceEnumerator* This,
EDataFlow dataFlow,
DWORD dwStateMask,
IMMDeviceCollection **ppDevices);
HRESULT (STDMETHODCALLTYPE *GetDefaultAudioEndpoint)(
IMMDeviceEnumerator* This,
EDataFlow dataFlow,
ERole role,
IMMDevice **ppEndpoint);
HRESULT (STDMETHODCALLTYPE *GetDevice)(
IMMDeviceEnumerator* This,
LPCWSTR pwstrId,
IMMDevice **ppDevice);
HRESULT (STDMETHODCALLTYPE *RegisterEndpointNotificationCallback)(
IMMDeviceEnumerator* This,
IMMNotificationClient *pClient);
HRESULT (STDMETHODCALLTYPE *UnregisterEndpointNotificationCallback)(
IMMDeviceEnumerator* This,
IMMNotificationClient *pClient);
END_INTERFACE
} IMMDeviceEnumeratorVtbl;
...
class DECLSPEC_UUID("bcde0395-e52f-467c-8e3d-c4579291692e") MMDeviceEnumerator;
In order to translate this and make it usable from Lisp, we would write the following:
(com:define-guid IMM-DEVICE-ENUMERATOR "a95664d2-9614-4f35-a746-de8db63617e6")
(com:define-guid MM-DEVICE-ENUMERATOR "bcde0395-e52f-467c-8e3d-c4579291692e")
;; ...
(com:define-comstruct device-enumerator
(enum-audio-endpoints (data-flow e-data-flow) (state-mask :uint32) (devices :pointer))
(get-default-audio-endpoint (data-flow data-flow) (role role) (endpoint :pointer))
(register-endpoint-notification-callback (client :pointer))
(unregister-endpoint-notification-callback (client :pointer)))
Omitted from this are the declarations of the enums data-flow
and role
, which can be translated as usual for C. Note that we can give our struct, methods, and arguments any name we like. What's important is that the order of the methods is exactly the same as in C, and that we do not skip any methods. The methods inherited from IUnknown
are always automatically added by define-comstruct
and can thus be omitted. Similar for the this
pointer which is always the first argument. Finally, almost all methods return an hresult
, so the return type can be omitted from define-comstruct
as well.
In order to actually instantiate this interface now, we can use create
:
(com:create MM-DEVICE-ENUMERATOR IMM-DEVICE-ENUMERATOR)
If successful, this will return a pointer to the COM instance, on which you can now call methods:
(device-enumerator-enum-audio-endpoints * #| ... |#)
Often you will want to wrap these method calls in a check-hresult
to catch failure states and translate them into Lisp conditions.
When you are done with a COM instance, you must release
it in order to free the resource. After release
ing the instance you may not call any methods on it, or pass it anywhere else. You also must not release
it twice.
If you do not create the COM instance yourself, but rather get it through another API call, you must first call init
to ensure the COM interface is properly initialised. Similarly, once you are done with COM, you should call shutdown
to uninitialise it.
System Information
Definition Index
-
ORG.SHIRAKUMO.COM-ON
No documentation provided.-
EXTERNAL CLASS GUID
Encapsulation for a Windows GUID. GUIDs are 16 byte identifiers that are used for COM classes and COM instances. The :ID initarg determines the GUID's contents. See MAKE-GUID. A GUID instance may be passed as an argument to a C function where the argument expects a COM:GUID structure pointer. A GUID instance is usable as a literal and may be dumped to a FASL. See COM:GUID See BYTES See GUID-STRING See GUID (function) See DEFINE-GUID
-
EXTERNAL CONDITION WIN32-ERROR
Condition type for errors coming from the Windows API. This condition type is signalled whenever a Windows API call returns unsuccessfully. See WIN32-ERROR (function) See FUNCTION-NAME See CODE See MESSAGE See CHECK-LAST-ERROR See CHECK-HRESULT
-
EXTERNAL FUNCTION CREATE
- CLASS
- INSTANCE
Create an instance of a COM class. CLASS should be the GUID of the COM class. Typically named something like CLSID_... INSTANCE should be the GUID of the COM instance to access. Typically named something like IID_... Returns the pointer to the COM instance if successful, or signals an error otherwise. You must release this instance when you are done with it by calling RELEASE. Automatically calls INIT. See GUID See WIN32-ERROR See RELEASE See INIT See WITH-COM
-
EXTERNAL FUNCTION ERROR-MESSAGE
- &OPTIONAL
- ERRNO
Returns the error messag string for the given error code. Unless specifically supplied, the last caused error code is used. See COM:GET-LAST-ERROR
-
EXTERNAL FUNCTION GUID
- &REST
- ID
Create a new GUID instance. ID may either be multiple values, or a single value determining the GUID's actual ID values. The following ID types are allowed: STRING --- Parses the string representation of the UUID into its appropriate octets. Such a UUID is typically of the form XXXX-XX-XX-XX-XXXXXX LIST (16) --- Uses the 16 octets in the list to build the internal octet vector. LIST (11) --- Uses the 11 integers in the list to build the octet vector. Specifically, the list should contain integers of the following bit sizes: 32 16 16 8 8 8 8 8 8 8 8 This representation is sometimes found in C headers. CFFI:FOREIGN-POINTER --- Copies the contents from the supplied C pointer to a GUID into the internal byte vector. VECTOR (16) --- Uses the 16 octets in the vector to build the internal octet vector. NULL --- Fills the internal octet vector with 0s. Supplying any integer anywhere in these values outside of the specified ranges is an error. See GUID (type)
-
EXTERNAL FUNCTION GUID-STRING
- GUID
Returns a standard string representation of the GUID. The bytes of the GUID are represented in hex format as follows: 3 2 1 0 - 5 4 - 7 6 - 8 9 - 10 11 12 13 14 15 The reordering is due to the little-endian internal representation of the octets. See GUID
-
EXTERNAL FUNCTION INIT
Initialises the COM system if it has not yet been initialised. This will load OLE32 and initialise COM for a multi-threaded application. This function must be called before any COM operations are performed. Calling this function multiple times is safe. See SHUTDOWN
-
EXTERNAL FUNCTION RELEASE
- POINTER
-
EXTERNAL FUNCTION SHUTDOWN
Uninitialises the COM system if it has been initialised. After this you may not perform any further COM operations. Calling this function multiple times is safe. See INIT
-
EXTERNAL FUNCTION STRING->WSTRING
- STRING
Converts a Lisp string to a Windows 'wchar' string and returns the pointer to this freshly allocated string. See WSTRING->STRING
-
EXTERNAL FUNCTION WIN32-ERROR
- CODE
- &KEY
- FUNCTION-NAME
- MESSAGE
Signals an error of type WIN32-ERROR Requires the Windows error code. If no explicit MESSAGE is passed, the message is determined by ERROR-MESSAGE. See WIN32-ERROR (type) See ERROR-MESSAGE
-
EXTERNAL FUNCTION WSTRING->STRING
- POINTER
- &OPTIONAL
- CHARS
Converts a Windows 'wchar' string to a Lisp string and returns it. See STRING->WSTRING
-
EXTERNAL GENERIC-FUNCTION BYTES
- OBJECT
Returns a 16-octet vector describing the GUID. Note that the vector elements are in the order expected in the memory representation of the GUID, which may not be entirely intuitive. See GUID
-
EXTERNAL GENERIC-FUNCTION CODE
- CONDITION
Returns the windows error code associated with the problem. See WIN32-ERROR
-
EXTERNAL GENERIC-FUNCTION FUNCTION-NAME
- CONDITION
Returns the function name that caused the error, if known. See WIN32-ERROR
-
EXTERNAL GENERIC-FUNCTION MESSAGE
- CONDITION
Returns a descriptive message about the error. See WIN32-ERROR
-
EXTERNAL MACRO CHECK-HRESULT
- VALUE-FORM
- &REST
- EXPECTED
Convenience function to check the returned HRESULT and error on failure. If the return value of VALUE-FORM is not one of the supplied EXPECTED values, an error of type WIN32-ERROR is returned. If it is one of the EXPECTED values, the value is returned. If EXPECTED is not passed, it is assumed to be just (:OK). See COM:HRESULT See WIN32-ERROR (type)
-
EXTERNAL MACRO CHECK-LAST-ERROR
- PREDICATE
- &BODY
- CLEANUP
Convenience function to check the last error on failure. If PREDICATE returns NIL, CLEANUP forms are run. After this, the error code is retrieved through COM:GET-LAST-ERROR, and a WIN32-ERROR is signalled using this code. See WIN32-ERROR (type)
-
EXTERNAL MACRO DEFINE-COMFUN
- STRUCT
- METHOD
- &REST
- OPTIONS
- RETURN-TYPE
- &BODY
- ARGS
Define a method on a COM interface. ARGS should be a list of argument declarations, with each argument being composed of an argument name and a CFFI type. This will create a function with the name of STRUCT-METHOD with the declared arguments, which will attempt to call the related COM method on the supplied COM instance. This method must be accessible through a function called %STRUCT-METHOD to which a pointer to a VTBL can be passed. You will typically not use this macro by itself, and instead use DEFINE-COMSTRUCT to perform the definition of a COM interface. See COM:VTBL See DEFINE-COMSTRUCT
-
EXTERNAL MACRO DEFINE-COMSTRUCT
- NAME
- &BODY
- METHODS
Define a COM interface structure. NAME should be the name of the CFFI structure type as well as the standard prefix for all structure interface methods. You may choose this arbitrarily and there is no binding to any C functions or structures. METHODS should be a body of the following kinds of entries: METHODS ::= (method [return-value] ARGUMENT*)* ARGUMENT ::= (name type) method --- Name of the interface method. You may pick this to be whatever you like, there is no strict binding to any C function. return-value --- The return value of the method. If not passed explicitly, HRESULT is assumed. name --- The name of the argumetn. Again, the name may be arbitrarily chosen. type --- The CFFI type that the argument should be of. Note that the order of the methods /must/ be the same as in the actual C header you're mimicking. You also /must/ include all of the methods defined in the C header and cannot skip any. The order is what actually defines which method is used. The name is purely on the Lisp side. Each COM interface always has the following three methods at the beginning, which DEFINE-COMSTRUCT adds for you automatically: (QUERY-INTERFACE HRESULT (UID :POINTER) (OUT :POINTER)) (ADD-REF :ULONG) (RELEASE :ULONG) Also note that the THIS argument is always assumed for every method and should therefore be omitted from the declarations. For each method defined in the body, A DEFINE-COMFUN is generated, which in turn will generate a function of the name NAME-METHOD using the declared arguments and return type. Alongside the methods, a C structure is defined which constitutes the VTBL layout of the COM interface. Note that it does /not/ define the COM instance layout itself. Each COM instance is assumed to merely be a pointer to a structure with a pointer to a VTBL. None of this should concern you terribly much, however. All you need to know is that you can just pass a COM instance pointer to the method functions defined by DEFINE-COMSTRUCT. See DEFINE-COMFUN
-
EXTERNAL MACRO DEFINE-GUID
- NAME
- &REST
- ID
Define a GUID instance. This is a shorthand for DEFCONSTANT of a GUID instance created from the given ID argument. See GUID (type) See GUID (function)
-
EXTERNAL MACRO WITH-COM
- VAR
- INIT
- &BODY
- BODY
-
EXTERNAL MACRO WITH-DEREF
- VAR
- TYPE
- &BODY
- INIT
Shorthand to initialise a value by dereferencing. Binds VAR to a pointer to a memory region of size fitting for TYPE, then evaluates INIT. INIT should return a COM:HRESULT. If this result is not :OK, an error is signalled. Otherwise, the memory region bound to VAR is dereferenced as a value of TYPE, which is then returned. Seee CHECK-HRESULT
-