* Wrapping a C library in COM/ActiveX

Posted on April 8th, 2012 by John. Filed under articles, programming.


Introduction

Not very long ago my boss walked up to my desk, put a pile of books down beside me and said “Not it”. The books were all related to COM programming. The project he gave me was to create a COM wrapper for an existing C library we had developed. Windows is not my primary development platform, so I was tasked with learning COM and then writing a wrapper around our C library.

The following is based on the internal documentation I wrote during the development of our COM wrapper. I’m publishing it with permission from my employer, Main Street Softworks. Nothing contained within is specific to the project but general information about what I learned and how I completed the task.

The goals of the project are fairly simple.

  • Wrap a C library
  • Simple and easy to maintain
  • C++ (all projects are written in C and we use C++ when C is not a viable option)
  • Minimal dependencies
    • Do not use MFC
    • Do not use ATL
  • Accessible by legacy applications
  • Accessible by .Net applications
  • Accessible by Internet Explorer (downloads, installs and runs an ActiveX component form a remote server)

Accessing C libraries in Windows

One of the ways to allow a C library to be used in a dynamic and reusable way is by wrapping it in a COM object. This is particularly true if you need to access the library in a non-compiled, scripting language without providing implementation details. This guide shows a method of wrapping a C library in a COM object without using MFC or ATL. Both MFC and ATL simplify this process but they introduce additional dependencies and in some cases complexity.

Types of COM components

COM is a very diverse framework for creating reusable components. COM allows for an object created on one machine to be used across the network on another machine. The language the component was created in and the language of the caller do not matter. COM allows for static and dynamic interfaces. For example a static interface would be used at compile time in a similar manner to an ordinary DLL. A dynamic interface allows for the the calling language to use inferred information about the component to accessed at run time. COM allows for flexible use and reuse of components.

Static and dynamic interfaces are not mutually exclusive. COM allows for dual interfaces where both types are defined at the same time. Static interfaces should be preferred as there is a severe performance penalty when using dynamic interfaces. However, both interfaces are important because not all languages support utilizing a static interface. Javascript for instance can only use a dynamic interface. Unless there is a very clear reason against implementing both types of interfaces it a dual interface implementation should always be preferred. Future use of the component cannot always be anticipated and a dual interface gives the most flexibility for future use.

While not exclusive to COM, many COM components use run time linking when calling other DLLs on the system. This is accomplished by using the LoadLibrary function to load a DLL into the address space of the running process. The advantage of using run time linking is the DLL does not have to be present on the system at build time. The disadvantage is the definitions of the DLL must be defined before they can be used. Meaning if you have the .h file it’s easier just to use static linking.

Looking over available information about COM, the general design follows this pattern:

  • Core functionality written in C++.
  • COM interface written in Visual Basic or Pascal/Delphi.
    • Utalize run time linking for loading the C++ DLL instead of linking to it directly.

During my research I found that concise information on writing a simple COM DLL in C++ that wraps a C library and allows for dual interface use is severely lacking. This is only one small portion of what COM allows and this guide should not be taken as the de facto way to utilize COM. Our needs were very specific and not how a typical project would use COM.

Why COM

COM allows a DLL written in one language to be used by a variety of others. It also allows for the DLL to be used in a situation where build time linking is not an option. Further (while insecure) it allows for Javascript running in Internet Explorer to load and use system resources and hardware.

COM has pretty much been supplanted by .Net which solves many of the same problems COM solves in a safer way and with a modern design. Remember, COM was first introduced about a decade ago. However, .Net is not a simple alternative to COM. For example it is not easy to use a .Net component in a non-.Net project. COM still has an edge when it comes to the ability to use a component in a large number of other, external projects and languages.

COM or ActiveX. What’s the difference?

COM and ActiveX are the same thing. ActiveX is a marketing name for COM. When I refer to COM I also mean ActiveX. Most people think ActiveX is a plugin system that only runs in Internet Explorer. This is not true. The ActiveX components that run in IE are really just COM components. Any COM component (taking the correct design considerations into account) can be used by IE.

COM has a flexible interface

The most difficult part of writing a COM component is the flexibility of the interface. To be a COM component very few requirements need to be satisfied. The one an only requirement is the component must be a subclass of the IUnknown interface and implement the three IUnknown functions. However, just doing this doesn’t make a COM object that can be used ubiquitously.

Files Used in the Wrapper Exmaple

  • MeLib.idl
  • MeLib.rc
  • Exports.def
  • Registry.cpp
  • Exports.cpp
  • MeFactory.h
  • MeFactory.cpp
  • Me.h
  • Me.cpp

IDL

A COM project starts with a .idl file. IDL is a language agnostic format which allows for generating type information that can be used across different languages. This defines the component and its capabilities.

import "unknwn.idl";
 
[
	uuid(bdc79f30-81dc-11e1-b0c4-0800200c9a66),
	helpstring("..."),
	control
]
library MeLib
{
	importlib("stdole32.tlb")
	importlib("stdole2.tlb")
 
	[
		object,
		uuid(d1a4eb20-81dc-11e1-b0c4-0800200c9a66),
		version(1.0),
		helpstring("..."),
		dual
	]
	interface IMe : IDispatch
	{
		[id(1), propget] HRESULT RESULT_OK([out, retval] int *res);
		[id(2), propget] HRESULT version([out, retval, string] BSTR *ver);
 
		[id(10)] HRESULT do_thing([in] BSTR what, [out, retval] *res);
		[id(11)] HRESULT set_val_thing([in] BSTR what);
	}
 
	[
		uuid(db83c620-81dc-11e1-b0c4-0800200c9a66),
		helpstring("..."),
		control
	]
	coclass Me
	{
		[default] interface IMe;
				  interface IDispatch;
	}
}

The above IDL code implements a library, interface and class. These are the minimum components used by our COM component. The IMe interface is a subclass of the IDispatch interface. We do not need to subclass IUnknown because IDispatch is already a subclass of IUnknown.

The dual property on the IMe interface as well as implementing IDispatch are required to create a dual interface.

The IDL file will compile to Me_h.h and Me_c.i. These are the translated IDL to C++ for use in Me.h. Do not edit Me_h.h and Me_i.c by hand. Since these are auto generated any changes will be wiped out next time you compile. These two files define the interface in C++ that was written in IDL.

IDL Components

UUIDs

UUIDs are very important when dealing with COM objects. UUIDs are how the system represents objects. It is possible to reference an object by name but names are mapped to UUIDs internally. Do not reused UUIDs from other components! The UUID must be unique.

id

The id is used in conjunction with IDispatch for dynamic interfaces. The id will be mapped to its textual name. When the textual name is called the id is used and the corresponding function is called by id. Ids should not start with 0 and they do not need to be sequential.

propget

This denotes that the value is a property. Properties while not used in C++ are used by Visual Basic as well as other languages and are parameter-less functions that are typically used for getting or setting a value or flag. While not used in this example there is a corresponding propset.

HRESULT and Return Values

All of the functions defined in the IDL file return HRESULT. This is by design and the return value of the function is used by COM to determine the status of the function call. See here for a list of possible return values.

A function returning a value needs to do so though a parameter. Use out and retval properties on the parameter to denote that the parameter is for returning a value and that is should be treated as the “return value” of the function. These properties are not simply informational. They are used when using the component with a language other than C/C++. For example Visual Basic will hide the HRESULT and instead return the value denoted by retval.

Strings

For compatibility between various languages BSTR (B Strings) need to be used when dealing with string data. A BSTR is a null terminated wide character array that also has the length prepended. _com_util:: provides static functions for converting between BSTRs and cstrings (char *).

When using _com_util the library comsupp.lib needs to be added as a link reference. On VS 2005+ it is called comsuppw.lib.

The “string” property should be used when a BSTR is the retval of the function. As with other properties this informs languages that deal with string data how to handle the value.

TLB File

During compilation of the DLL a TLB file will be produced. This file is a binary representation of the IDL file and is used by other languages to understand and use the DLL. The TLB can be embedded into the DLL itself so it does not need to be distributed separately. This is done by adding it as a resource to the project. Since the rc file references a single path it is a good idea to change the output location of the TLB file to be placed in the same directory in both Debug and Release modes.

The TLB file contains the classes and functions contained in the DLL. Think of it like a non-language specific .h file. This is what allows Visual Basic to load the component and know what functions it contains.

RC File (MeLib.rc)

The standard RC needs to be edited using a text editor and not the Visual Studio resource editor in order to embed the TLB file. Embedding the TLB is not required but it makes distribution much easier. The TLB file is necessary for languages such as Visual Basic to infer the functions and attributes exposed by the object. The TLB can be distributed separately from the DLL but, again, embedding makes the process simpler.

Add the following references into the RC if it doesn’t already exist.

// TYPELIB
1 TYPELIB DISCARDABLE "MeLib.tlb"

In addition to embedding the Type Info the RC also includes version information. This is the information exposed when viewing the properties of the DLL in Explorer.

VS_VERSION_INFO VERSIONINFO
FILEVERSION	 1,0,0,0
PRODUCTVERSION  1,0,0,0
 
BEGIN
	BLOCK "StringFileInfo"
	BEGIN
		BLOCK "040904E4"
		BEGIN
			VALUE "CompanyName",		"Company Name"
			VALUE "FileDescription",	"File Description"
			VALUE "FileVersion",		"1.0.0.0"
			VALUE "InternalName",	   "MeLib.dll"
			VALUE "LegalCopyright",	 "TODO: Copyright (c) 2012 Company Name"
			VALUE "LegalTrademarks1",   "All Rights Reserved"
			VALUE "OriginalFilename",   "MeLib.dll"
			VALUE "ProductName",		"Product Name"
			VALUE "ProductVersion",	 "1.0.0.0"
		END
	END
 
	BLOCK "VarFileInfo"
	BEGIN
		VALUE "Translation", 0x409, 1252
	END
END

Be sure the file includes windows.h.

Exports.def

This file defines functions in the DLL that should not be mangled. These are the entry points into the DLL. Four entry points need to be defined which are implemented in other files. These are entry points defined by our COM object and are required for other applications to properly use the DLL as a COM object.

LIBRARY "MeLib"
 
EXPORTS
	DllGetClassObject   PRIVATE
	DllCanUnloadNow	 PRIVATE
	DllRegisterServer   PRIVATE
	DllUnregisterServer PRIVATE

The LIBRARY value needs to be the same the library referenced in the IDL file.

Registry.cpp / Registering the Object

COM objects needs to be registered with the system. Registration provides information about the object including its location and the location of its associated TLB. By registering an object it can be referenced by its UUID or name and the system can determine where the object is located and load it properly. Registration also provides information to the system about the object such as types of interfaces it uses.

The registry is used for registration (as well as may other things). There are a number of predefined locations that will be searched by the system when a specific COM object is requested. With COM only imposing a thin layer of required functions and being “flexible” to the point of not providing any implementation it’s up to the object itself to write the appropriate values to the registry to register itself with the system. Also, the object is responsible for unregistering itself.

Calling regsvr32 Me.dll will call the appropriate registration functions provided the by the object. Conversely, calling regsvr32 /U Me.dll will call the unregistrater function.

The following values are used by Registry.cpp:

  • LIBID_MeLib = {bdc79f30-81dc-11e1-b0c4-0800200c9a66}
  • IID_IMe = {d1a4eb20-81dc-11e1-b0c4-0800200c9a66}
  • CLSID_Me = {db83c620-81dc-11e1-b0c4-0800200c9a66}

These correspond to the UUIDs defined in the IDL file. The LIBID_, IID_ and CLSID_ variables defined in the files generated when compiling the IDL file. One thing to note is the above variables are UUID variables and not strings so they aren’t directly usable by the following registry code.

#include <windows.h>
#include <objbase.h>
 
#define OBJECT_ID "MeLib.Me"
#define OBJECT_DESCRIPTION "MeLib Me Interface"
#define OBJECT_MAJOR_VERSION "1"
#define OBJECT_MINOR_VERSION "0"
#define CLASS_DESCRIPTION "Me Contoller"
#define INTERFACE "IMe"
#define LIB_ID "{bdc79f30-81dc-11e1-b0c4-0800200c9a66}"
#define IFCE_ID "{d1a4eb20-81dc-11e1-b0c4-0800200c9a66}"
#define CLS_ID "{db83c620-81dc-11e1-b0c4-0800200c9a66}"
 
extern HMODULE g_module;
 
const char *g_RegTable[][3] = {
	{ OBJECT_ID, 0, OBJECT_DESCRIPTION },
	{ OBJECT_ID "\\" OBJECT_DESCRIPTION, 0, OBJECT_MAJOR_VERSION "." OBJECT_MINOR_VERSION },
	{ OBJECT_ID "\\Clsid", 0, CLS_ID },
 
	{ OBJECT_ID "." OBJECT_MAJOR_VERSION, OBJECT_DESCRIPTION },
	{ OBJECT_ID ".1\\" OBJECT_DESCRIPTION, 0, OBJECT_MAJOR_VERSION "." OBJECT_MINOR_VERSION },
	{ OBJECT_ID ".1\\Clsid", 0, CLS_ID },
	{ OBJECT_ID ".1\\TypeLib", 0, CLS_ID },
 
	{ "Interface\\" IFCE_ID, 0, INTERFACE },
	{ "Interface\\" IFCE_ID "\\TypeLib", 0, LIB_ID },
 
	{ "CLSID\\" CLS_ID, 0, CLASS_DESCRIPTION },
	{ "CLSID\\" CLS_ID "\\ProgID", 0, OBJECT_ID "." OBJECT_MAJOR_VERSION },
	{ "CLSID\\" CLS_ID "\\VersionIndependentProgIDProgID", 0, OBJECT_ID },
	{ "CLSID\\" CLS_ID "\\InprocServer32", 0, (const char *)-1 },
	{ "CLSID\\" CLS_ID "\\TypeLib", 0, CLS_ID },
 
	/* Regestier the Type Info. */
	{ "TypeLib\\" LIB_ID "\\" OBJECT_MAJOR_VERSION "." OBJECT_MINOR_VERSION, 0, OBJECT_ID OBJECT_MAJOR_VERSION "." OBJECT_MINOR_VERSION " Library" },
	{ "TypeLib\\" LIB_ID "\\" OBJECT_MAJOR_VERSION "." OBJECT_MINOR_VERSION "\\HELPDIR", 0, "" },
	{ "TypeLib\\" LIB_ID "\\" OBJECT_MAJOR_VERSION "." OBJECT_MINOR_VERSION "\\9\\win32", 0, (const char *)-1 },
 
	/* Interface registration. */
	{ "Interface\\" IFCE_ID, 0, INTERFACE },
	{ "Interface\\" IFCE_ID "\\TypeLib", 0, LIB_ID },
	/* IDispatch derived interface*/
	{ "Interface\\" IFCE_ID "\\ProxyStubClsid32", 0, "{00020400-0000-0000-C000-000000000046}" },
 
	/* Register as Safe for Scripting. */
	{ "Component\\Categories\\{7DD95801-9882-11CF-9FA9-00AA006C42C4}", 0, NULL },
	{ "CLSID\\" CLS_ID "\\Implemented Categories\\{7DD95801-9882-11CF-9FA9-00AA006C42C4}", 0, NULL },
	{ "Component\\Categories\\{7DD95802-9882-11CF-9FA9-00AA006C42C4}", 0, NULL },
	{ "CLSID\\" CLS_ID "\\Implemented Categories\\{7DD95802-9882-11CF-9FA9-00AA006C42C4}", 0, NULL },
};
 
STDAPI DllUnregisterServer(void)
{
	HRESULT hr = S_OK;
	int nEntries = sizeof(g_RegTable)/sizeof(*g_RegTable);
	for (int i = nEntries - 1; i >= 0; --i) {
		const char *pszKeyName = g_RegTable[i][0];
 
		long err = RegDeleteKeyA(HKEY_CLASSES_ROOT, pszKeyName);
		if (err != ERROR_SUCCESS) {
			hr = S_FALSE;
		}
	}
	return hr;
}
 
STDAPI DllRegisterServer(void)
{
	HRESULT hr = S_OK;
	char szFileName[MAX_PATH];
	GetModuleFileNameA(g_module, szFileName, MAX_PATH);
	int nEntries = sizeof(g_RegTable)/sizeof(*g_RegTable);
	for (int i = 0; SUCCEEDED(hr) && i < nEntries; ++i) {
		const char *pszKeyName = g_RegTable[i][0];
		const char *pszValueName = g_RegTable[i][1];
		const char *pszValue = g_RegTable[i][2];
 
		/* -1 is a special marker which says use the DLL file location as
		 * the value for the key */
		if (pszValue == (const char *)-1) {
			pszValue = szFileName;
		}
 
		HKEY hkey;
		long err = RegCreateKeyA(HKEY_CLASSES_ROOT, pszKeyName, &hkey);
		if (err == ERROR_SUCCESS) {
			if (pszValue != NULL) {
				err = RegSetValueExA(hkey, pszValueName, 0, REG_SZ, (const BYTE *)pszValue, (strlen(pszValue) + 1));
			}
			RegCloseKey(hkey);
		}
		if (err != ERROR_SUCCESS) {
			DllUnregisterServer();
			hr = E_FAIL;
		}
	}
	return hr;
}

Exports.cpp

In this file we’re going to define three export functions.

#include <objbase.h>
#include "Me.h"
#include "MeFactory.h"
 
HMODULE g_module = NULL;
long g_objsInUse = 0;
 
BOOL APIENTRY DllMain(HANDLE module, DWORD reason, void *reserved)
{
	if (reason == DLL_PROCESS_ATTACH) {
		g_module = (HMODULE)module;
	}
	return TRUE;
}
 
STDAPI DllGetClassObject(const CLSID &clsid, const IID &iid, void **pv)
{
	if (clasid == CLAID_Me) {
		MeFactory *pMeFact = new MeFactory;
		if (pMeFact == NULL) {
			 return E_OUTOFMEMORY;
		} else {
			return pMeFact->QueryInterface(iid, pv);
		}
	}
	return CLASS_E_CLASSNOTAVAILABLE;
}
 
STDAPI DllCanUnloadNow()
{
	if (g_objsInUse == 0) {
		return S_OK;
	} else {
		return S_FALSE;
	}
}

The g_objsInUse will be incremented in the constructors of Me and MeFactory and deincremented in their desturctors. This ensures that the COM DLL is only loaded once and will not be unloaded until there isn’t anything using it. InterlockedIncrement and InterlockedDecrement should be used for changing the object count.

MeFactory.h

The Exports.cpp does not directly reference the Me object. Instead a factory is retrieved which later can be used to create a Me object. This provides an additional layer of separation. The caller only needs to know a CLSID. Calling DllGetClassObject and passing in the CLSID will result in the appropriate factory object deriving the IFactory interface being returned. At no point is type information necessary to complete this call. The is due to the use of the factory that abstracts the process of getting the class object.

#ifndef MEFACTORY_H
#define MEEFACTORY_H
 
#include "unknwn.h"
 
class MeFactory : public IClassFactory
{
public:
	DeviceFactory();
	~DeviceFactory();
 
	HRESULT __stdcall QueryInterface(REFIID rrid, void **pv);
	ULONG __stdcall AddRef();
	ULONG __stdcall Release();
 
	HRESULT __stdcall CreateInstance(IUnknown *outer, const IID &iid, void **pv);
	HRESULT __stdcall LockServer(BOOL lock);
 
private:
	long m_refCount;
};
 
#endif // MEFACTORY_H

MeFactory.cpp

#include <objbase.h>
#include "MeFactory.h"
#include "Me.h"
 
extern long g_objsInUse;
 
MeFactory::MeFactory()
: m_refCount(1)
{
	InterlockedIncrement(&g_objsInUse);
}
 
MeFactory::~MeFactory()
{
	InterlockedDecrement(&g_objsInUse);
}
 
HRESULT __stdcall MeFactory::QueryInterface(REFIID riid, void **ppv)
{
	if (riid == IID_IUnknown || riid == IID_IClassFactory) {
		*ppv = static_cast<IClassFactory *>(this);
	} else {
		*ppv = 0;
		return E_NOINTERFACE;
	}
	reinterpret_cast<IUnknown *>(*ppv)->AddRef();
	return S_OK;
}
 
ULONG __stdcall MeFactory::AddRef()
{
	return InterlockedIncrement(&m_refCount);
}
 
ULONG __stdcall MeFactory::Release()
{
	LONG res = InterlockedDecrement(&m_refCount);
	if (res == 0) {
		delete this;
	}
	return res;
}
 
HRESULT __stdcall MeFactory::CreateInstance(IUnknown *outer, const IID &iid, void **pv)
{
	if (outer != NULL) {
		return CLASS_E_NOAGGREGATION;
	}
 
	Me *m = new Me;
	if (m == NULL) {
		return E_OUTOFMEMORY;
	}
 
	return m->QueryInterface(iid, pv);
}
 
HRESULT __stdcall MeFactory::LockServer(BOOL lock)
{
	return E_NOTIMPL;
}

One thing to note is LockServer always return E_NOTIMPL. Implementing this function is not strictly necessary. Per MSDN, “Most clients do not need to call this method. It is provided only for those clients that require special performance in creating multiple instances of their objects.”

Me.h

#ifndef ME_H
#define ME_H
 
#include "Me_h.h"
 
class Me : public IMe
{
public:
	Me();
	~Me();
 
	HRESULT __stdcall get_RESULT_OK(int *res);
	HRESULT __stdcall get_version(BSTR *ver);
 
	HRESULT __stdcall do_thing(BSTR what, *res);
	HRESULT __stdcall set_val_thing(BSTR what);
 
	// IUnknown
	HRESULT __stdcall QueryInterface(REFIID riid, void **ppv);
	ULONG __stdcall AddRef();
	ULONG __stdcall Release();
 
	// IDispatch
	HRESULT __stdcall GetTypeInfo(UINT it, LCID lcid, ITypeInfo **ppti);
	HRESULT __stdcall GetTypeInfoCount(UINT *pit);
	HRESULT __stdcall GetIDsOfNames(REFIID riid, OLECHAR **pNames, UINT cNames, LCID lcid, DISPID *dispids);
	HRESULT __stdcall Invoke(DISPID id, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pd, VARIANT *pVarResult, EXCEPINFO *pe, UINT *pu);
 
private:
	LONG m_refCount;
	ITypeInfo *m_typeInfo;
	int m_thing_count;
};
 
#endif // ME_H

There are a few things to pay special attention to here. First we’re including Me_h.h which is the header file created when the IDL file is compiled. Me_h.h defines the IMe interface in a way C++ understands. All function calls take the form HRESULT __stdcall. Properties defined in the IDL file need to be prefixed with either get_ (propget) or set_ (propset).

Me.cpp

#include <comuntil.h>
#include <string.h>
#include <windows.h>
#include "Me.h"
#include "Me_i.c"
 
extern HMODULE g_module;
extern long g_objsInUse;
 
Me::Me()
: m_refCount(0),
  m_typeInfo(0),
  m_thing_count(0);
{
	InterlockedIncrement(&g_objsInUse);
 
	ITypeLib *typeLib = 0;
	char szFileName[MAX_PATH] = {0};
	GetModuleFileNameA(g_module, szFileName, MAX_PATH);
	HRESULT hr = LoadTypeLib(_com_util::ConvertStringToBSTR(szFileName), &typeLib);
	if (SUCCEEDED(hr)) {
		hr = typeLib->GetTypeInfoOfGuid(IID_IMe, &m_typeInfo);
		if (!SUCCEEDED(hr)) {
			m_typeInfo = 0;
		}
	}
}
 
Me::~Me()
{
	m_typeInfo->release();
	InterlockedDecrement(&g_objsInUse);
}
 
HRESULT __stdcall get_RESULT_OK(int *res)
{
	*res = 0;
	return S_OK;
}
 
HRESULT __stdcall get_version(BSTR *ver)
{
	*version = _com_util::ConvertStringToBSTR("1.0");
	return S_OK;
}
 
HRESULT __stdcall do_thing(BSTR what, *res)
{
	char *s = NULL;
 
	s = _com_util::ConvertBSTRToString(what);
	if (s != NULL) {
		*res = strlen(s);
	} else {
		*res = 0;
	}
 
	return S_OK;
}
 
HRESULT __stdcall set_val_thing(BSTR what)
{
	char *s = NULL;
 
	s = _com_util::ConvertBSTRToString(what);
	if (s != NULL) {
		m_thing_count = strlen(s);
	} else {
		m_thing_count = 0;
	}
 
	return S_OK;
}
 
HRESULT __stdcall Me::QueryInterface(REFIID riid, void **ppv)
{
	if (riid == IID_IUnknown) {
		*ppv = static_cast<IUnknown *>(this);
	} else if (riid == IID_IDispatch) {
		*ppv = static_cast<IDispatch *>(this);
	} else if (riid == IID_IMe) {
		*ppv = static_cast<IMe *>(this);
	} else {
		*ppv = 0;
		return E_NOINTERFACE;
	}
	reinterpret_cast<IUnknown *>(*ppv)->AddRef();
	return S_OK;
}
 
ULONG __stdcall Me::AddRef()
{
	return InterlockedIncrement(&m_refCount);
}
 
ULONG __stdcall Me::Release()
{
	LONG res = InterlockedDecrement(&m_refCount);
	if (res == 0) {
		delete this;
	}
	return res;
}
 
 
HRESULT __stdcall Me::GetTypeInfo(UINT it, LCID lcid, ITypeInfo **ppti)
{
	if (!m_typeInfo) {	
		return E_NOTIMPL;
	}
	if (!ppti) {
		return E_INVALIDARG;
	}
 
	if (it != 0) {
		return DISP_E_BADINDEX;
	}
 
	*ppti = NULL;
 
	m_typeInfo->AddRef();
	*ppti = m_typeInfo;
 
	return S_OK;
}
 
HRESULT __stdcall Me::GetTypeInfoCount(UINT *pit)
{
	if (!m_typeInfo) {	
		return E_NOTIMPL;
	}
	if (!pit) {
		return E_NOTIMPL;
	}
 
	*pit = 1;
	return S_OK;
}
 
HRESULT __stdcall Me::GetIDsOfNames(REFIID riid, OLECHAR **pNames, UINT cNames, LCID lcid, DISPID *pdispids)
{
	if (!m_typeInfo) {	
		return E_NOTIMPL;
	}
 
	return DispGetIDsOfNames(m_typeInfo, pNames, cNames, pdispids);
}
 
HRESULT __stdcall Me::Invoke(DISPID id, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pd, VARIANT *pVarResult, EXCEPINFO *pe, UINT *pu)
{
	if (!m_typeInfo) {	
		return E_NOTIMPL;
	}
 
	return DispInvoke(this, m_typeInfo, id, wFlags, pd, pVarResult, pe, pu);
}

The constructor loads the type info that is embedded into the DLL. This saves us from having to distribute the TLB file separately. It is possible to load the type info by querying the COM system using LoadRegTypeLib(LLIBID_, 1, 0, 0, &typeLib). Since we know the type info is embedded in the DLL we bypass querying the registry for the location of the type info and load it directly from the DLL. This allows the DLL to be used even if it’s not registered.

Also, note that g_objsInUse is incremented and deincremented here as well as in the MeFactory.

The rest of the about Me class implements the functions required by IUnknown and IDispatch. The the implmentation is fairly generic and will be similar if not the same across a wide varity of objects.

Manifests

When wrapping a C library it needs to have an embedded manifest. This manifest will detail the dependencies for the DLL. In this instance it will specify the C run time version the DLL was compiled against. The first thing that needs to be done is adding the appropriate LD FLags to have the manifest generated. Once the DLL is build mt.exe is used to embed the manifest into the library.

Dependency Info

LDFLAGS = $(LDFLAGS) /MANIFEST /MANIFESTFILE:"MeLib.dll.intermediate.manifest"
mt.exe /nologo /manifest MeLib.dll.intermediate.manifest /outputresource:MeLib.dll;2

To add the manifest to the C DLL. Note the 2 at the end. This is very important and signafies that this is a DLL.

As of Visual Studio 2005 (may have been 2003), the manifest with dependency info is required for all components, C DLL, COM DLL, EXE… The above commands will auto generate and then embed for you so you don’t need to worry about writing the manifest by hand.

Registration Free COM

As of Windows XP SP2 COM components no longer need to be registered before they can be used. This also goes by the name Side-by-Side Assembly or SxS for short. This allows for COM DLLs bundled with an application to be used without having the component information written to the registry. The idea is to provide the registration information as part of the manifest embedded in the DLL. One advantage is multiple versions of a COM component can be installed without interfering with one another. This is not required but it is a good idea to add to the COM DLL.

mt.exe can be used to generate the registration information manifest using the TLB file. The DLL does not need to be built at this point. We can generate the manifest at any point once we have the TLB (after running midl).

mt.exe /nologo /tlb:MeLib.tlb /dll:MeLib.dll /out:MeLib.dll.embed.manifest

Sine we have two manifest files (one with the dependency info and one with the registration info) we will provide both to mt and have it merge them before embedding into the final DLL.

mt.exe /nologo /manifest "MeLib.dll.intermediate.manifest" "MeLib.dll.embed.manifest" /outputresource:MeLib.dll;2

One thing to note. I was not able to get registration free COM to work properly. I’m guessing I’m missing something that is not obvious in the documentation but even going though the above steps I still had to register the COM DLL before it could be used.

Creating an Assembly from the Type Library For .Net Interoperability

.Net cannot directly use a COM DLL. However, it is possible to to create a wrapper library which will allow .Net to access COM DLLs without resorting to PInvoke. You use the Type Library Importer (tlbimp.exe) to create an assembly from Type Info. Be sure to use the disrep transform option to turn [out, retval] parameters of methods on IDispatch derived interfaces into return values.

Since we already imbed the Type Info into the DLL we are going to have tlbimp read Type Info from the DLL.

tlbimp.exe MeLib.dll /nologo /transform:dispret /out:Interop.MeLib.dll

Please note that the output name is Interop.MeLib.dll. This is not required and can be named anything. Interop was chosen because it’s a commonly used convention in this case.

A .Net project would add the Interop.MeLib.dll as a reference and be able to access the COM object using (in this case) Interop.MeLib.Me. The DLL file name becomes part of the reference in the project. Also, note that the COM object needs to be registered in order to be used.

Creating an interop DLL is not strictly necessary as Visual Studio will create it if a COM object is added to the project as a reference. However, it may be convenient to distribute the interop DLL instead of relying on this behavior.

Packaging

CAB

One common way to package a COM DLL is via a CAB file. This is especially useful when distributing via Internet Explorer (IE). When IE prompts that the web page wants to use an ActiveX control the object along with all associated dependencies can be referenced as a CAB file which IE will download and install.

We need to create an INF file which defines the files that will be included in the CAB.

[version]
signature="$CHICAGO$"
AdvancedINF=2.0
 
[Deployment]
InstallScope=user|machine
 
[Add.Code]
MeLib.dll=MeLib.dll
Interop.MeLib.dll=Interop.MeLib.dll
other.dll=other.dll
Microsoft.VC80.CRT.manifest=Microsoft.VC80.CRT.manifest
msvcr80.dll=msvcr80.dll
 
[MeLib.dll]
file=thiscab
clsid={db83c620-81dc-11e1-b0c4-0800200c9a66}
RegisterServer=yes
RedirectToHKCU=yes
 
[Interop.MeLib.dll]
file=thiscab
RegisterServer=no
 
[other.dll]
file=thiscab
RegisterServer=no
 
[Microsoft.VC80.CRT.manifest]
file=thiscab
RegisterServer=no
 
[msvcr80.dll]
file=thiscab
RegisterServer=no

The file key can also be written as file-win32-x86 if it is necessary to specify the OS and architecture (Internet Explorer and ActiveX was supported by PPC Macs at one point for example).

Order matters when dealing with a INF/CAB. We are using the Deployment section with an InstallScope set to user and machine. This allows Windows Vista+ to do a user install of the component instead of system wide. Machine is also included because XP does not implement the user attribute.

Also, not that RegisterServer is only set to yes on our COM DLL. Only COM components need to be registered. Further the VC80 CRT is being included in the package. This allows it to be used on systems that do not have the redistributable installed. There are other ways to handle this, such as having the CAB reference the CRT installer url or reference the file included in another CAB. However, it is bundled for simplicity. Don’t forget to change the CRT version to correspond to the version associated with the compiler version your using.

Finally, we need to package everything into a CAB using cabarc. cabarc.exe was distributed as part of the Microsoft Cabinet SDK. This is no longer available and cabarc has been merged into the Windows SDK. If you have Visual Studio installed you have cabarc and do not need to take any additional steps.

cabarc.exe n MeLib.cab MeLib.dll MeLib.clr.dll other.dll Microsoft.VC80.CRT.manifest msvcr80.dll MeLib.inf

Debugging

The easiest way I’ve found to debug a COM DLL is to use Visual Basic to load the DLL. Make sure you register a debug version of the DLL. If VB encounters a problem it will offer to debug. The debug screen will open and you will be taken to the exact line that had the problem arose.

References

Tags: , , , , , , .



* Sigil BookView Changes Preview

Posted on March 10th, 2012 by John. Filed under Sigil.


The next release of Sigil is shaping up nicely. There is so much going into it that the next release will be 0.6.0. Unfortunately, EPUB 3 will not be one of the features making it into 0.6.0. One major change coming will be a new BookView (BV) editor. Here is an unfished preview of what it might look like.

This is only a concept preview of the new editor. One issue that needs to be resolved is the double tool bar. I haven’t decided yet if I’m going to use the one in the BV pane or the global one in the window itself.

Tags: , , , .

    Comments Off


* Sigil 0.5.3 Released

Posted on February 25th, 2012 by John. Filed under Sigil.


Sigil 0.5.3 is now available for download. This is a maintenance release and does not include any new features. Just bug fixes.

Tags: , .

    Comments Off


* Sigil 0.5.2 Released

Posted on February 9th, 2012 by John. Filed under Sigil.


0.5.2 is now available for download! This is only a bug fix release but it fixes the “add existing file” feature that broke with 0.5.1.

There is no Linux x64 build at this time because my build machine died while building this package. I’ll be looking into options to replace this machine but Linux x64 users will have to make due with the x86 Linux release for the time being. I’ve been having problems with my build machine (since the 0.5.0 release) but this time it looks like it’s might be beyond reviving.

Tags: , .

    Comments Off


* Sigil 0.5.1 Released

Posted on February 5th, 2012 by John. Filed under Sigil.


This is primarily a maintenance release with a large number of fixes. Find and replace (F&R), GUI tweaks and spell check were the major focus. Highlights include: F&R now supports wrapping and the ability to search within selected files. The GUI now (hopefully) remembers cursor position when switching views. en_GB dictionary was added, all dictionaires were updated and hyphenation dictionaries are now included by default. For a full list of all changes for this release please see the Changelog.

This release wouldn’t have come off as smoothly, quickly or with as many fixes and new features without the help of meme from MobileRead. Thank you for the time and effort you put into making this release possible.

Starting with this release I want to start making smaller releases with less time between them. I plan to target approximately 4 weeks between releases give or take two weeks. I believe this will make development more fluid and allow for greater user input.

My short term plans for upcoming releases are (not all of these will be in the next release):

  • Make book view editing more robust.
  • Firebug style element inspection.
  • Make code view editing more robust.
  • Plugin interface.
  • Move auto cleaning and restructuring into plugins.

My long term plans are EPUB 3. This is not a short term goal but I plan / want to make this a reality by the end of this year.

Tags: , .

    Comments Off


* How Find Searches in Sigil 0.5.0

Posted on January 29th, 2012 by John. Filed under Sigil.


There have been some confusion about how find works in, the now released, 0.5.0. The confusion stems from the the 0.4.90x betas. One method was used in the early betas and it was changed later on. This all stems from the regular expression engine being changed from QRegExp to PCRE. The issue at hand is how and when the cursor is taken info account when running a find. In this regard 0.5.0 works no different than 0.4.2.

When doing a count the cursor is ignored. The entire document is taken into account from start to end.

When doing a find next the find starts from the cursor location. Everything before the cursor is ignored and not taken into account. This can, in some cases when using a regular expression, lead to the number of matches being different from the total returned by count. Again, this can only happen when using a regular expression. The reason is a regular expression can have matches that match the expression within a single match. For example:

Expression:

<div>.+</div>

Text:

<div>blah <div> blah </div> blah </div>

The expression will match the text from beginning to end. If you put the cursor to the right of the first < then the math will start from the second div and go to the end. This is because regular expressions can match a variable amount of text. Unlike a fixed expression like “abc” which will always match “abc”.

Finding backwards will match from the start of the document up until the cursor position. This is done by finding all matches from the start to the cursor then using the last match. Again, in the case of regular expressions, a backward find can match different text than a forward find.

Find forward and backward find from the cursor so its position in the document taken into account. In the majority of instances find backward, forward and count will all match the same exact text. However, it is possible, due to their nature, to construct a regular expression that can match differently segments of text within a segment of text depending on where the cursor is located.

The above also applies to replace as a find is run to find the text to replace.

Tags: .

    Comments Off


* Consolidation of Sigil Help Forums

Posted on January 27th, 2012 by John. Filed under Sigil.


For some time now Sigil has had two different help forums. One at MobileRead and the other as a Google Group. This has caused quite a bit of confusion because people don’t know the best place to go for help.

I’ve decided to close (it’s already done so don’t ask me to reconsider) the Google Group. This was an easy decision because 1) MobileRead’s sub forum gets more traffic, 2) I use MobileRead and I don’t use the Google Gorup, 3) Most posts on the Google Group were unanswered making it a poor place to go for help.

Tags: .

    Comments Off


* Sigil 0.5.0 Released

Posted on January 21st, 2012 by John. Filed under Sigil.


I’m happy to announce the release of Sigil version 0.5.0.

0.5.0 comes with a number of bug fixes and some major new features:

  • Inline spell check in code view
  • Support for PCRE in search and replace
  • Translations into 15 languages

Please see the changelog for a full list of changes in this release.

One smaller change is I’ve decided to drop OpenCandy. Surprisingly I’ve only encountered one complaint about OpenCandy and it was directed at bundling offers for other software inside of an installer not at OpenCandy in particular. I want to make it clear that this change is not due to user request or opposition to OpenCandy but my own decision.

While I respect what OpenCandy the company is doing and I don’t see anything wrong with the offerings they provide I don’t think their system is right for Sigil. The big thing I don’t like is OpenCandy’s installer components are distributed as closed source binary modules. While my understanding is this can be used without running afoul of the GPL I fully believe it goes against the spirit of the GPL and open source in general.

Thank you to everyone who provided feedback and helped during the beta process.

Tags: , .

    Comments Off


* Ebook Creation Workflow

Posted on December 13th, 2011 by John. Filed under Uncategorized.


Introduction

A question I often get asked is what kind of workflow is best for creating ebooks. The short answer is there is no best, perfect, or standard way to create ebooks. There are as many workflows as there are people. I have regular contact with a comercial company (Booknook.biz) that provides digital file to ebook creation servies (I am not affiliated with or work for this company). The company is broken down into teams and each team has a very different work flow, uses very different tools and they all produce the same quality (high) ebooks. Suffice it to say there is no one way that works best. What I want to outline is a few different ways that I have seen work well for people in common situations.

The big thing to think about is what you want to do with the final look of the ebook. There are applications like Jutoh and Atlantis Word Processor that bill themselves as the equivalent of Word for ebook creation. Atlantis even goes as far as being able to directly open Word documents. These two are not free but compared to the cost of Word itself they are not over priced. These are tools with strong WYSIWYG formatting support.

Sigil fits into the ebook creation picture in cases where a high degree of control is desired. While Sigil does have some WYSIWYG features, it excels at working on code that comprises an ePub internally. Sigil is very much for manual, do it yourself, or fine grain control needs. That said, many users of Sigil use it solely for its WYSIWYG features. Sigil also has the advantage of being free (price) and open source.

In all cases I recommend getting your book into the ePub format. As noted there are many tools for working with ePub. The internals of the format are HTML and CSS which means it’s easy to learn how to do advanced formatting. Also, ePub is an open, fully documented, and standardized format. Further, it’s very easy to convert an ePub into a variety of other ebook formats such as mobi for use with Amazon’s Kindle.

Authors starting with a Word document who don’t want to know about HTML or CSS that makes up the inside of an ePub

Often times an author has already written their book. They have it saved as a Word document or in some other similar format. The challenge in this case is going from their favored text editor to an ebook. In this case I recommend either copy and pasting the text from Word into Jutoh, Sigil or opening the Word document with Atlantis Word Processor. Use the inbuilt features to add the desired formatting and save as an ePub.

This works and can produce a functional ebook. However you are limited by what you can do with the formatting and dealing with quirks in various reading systems.

The case for manually working on an ePub’s internals

Using Sigil to actually work on the internals of the ePub allow for quirks in different reading systems to be accommodated. Two major examples I can think of that illustrate this are thumbnail images on the Nook Touch and the Kobo iPad/iPod apps. Both have very specific requirements in order for them to appear properly. Calibre, for example, takes issues like these into account when it creates an ePub but this doesn’t mean calibre handles every quirk in every reader out there. Manual intervention is sometimes required. Also, this goes back to professional digitization services (like I mentioned earlier) because they know about the little quirks of the most popular reading devices and can ensure an ebook looks the same across all devices.

Three stage ebook creation

What I can say is the best processes in my experience when using Sigil is three stage: write, convert, finish.

  1. Write the book in what ever format you’re most confortable using what ever tools you’re most confortable with. The caveat is you really need to be working in a digital medium. At some point you will have to digitize and scanning in pages of paper then correcting errors is going to be a long and arduous process. In most cases simply typing your book into Word is going to save you time after you’ve finished writing if you go the pen and paper route. Most authors are going to use Word which lets them, write, edit and do basic formatting. At this point leave the formatting pretty basic.
  2. Use an automated conversion tool like calibre to get your book into a format that is a bit more editable as an ebook. Word is a great editor but Word files are complex (and older versions do not have official format specifications). For Word the best advise is to have Word save as HTML. This will lead to it’s own issues but they can be worked around. HTML produced by Word will have basic formatting intact and the beauty of using an automated conversion tool is a lot will come through when converting to ePub.
  3. Take that mostly formatted ePub put it into Sigil and make the necessary changes to make it look perfect. Since Sigil allows for direct access to the XHTML and CSS you can use a number of different techniques to ensure it looks exactly how you want. Also, if you are working on a number of book and want to ensure consistant formatting you can do things like easily use a default stylesheet. The best way I can sum up how the majority of people use and how I view Sigil is, Sigil is for people who know XHTML, CSS, and regular expressions and want to use their knowledge.

Three stage variation: Markdown and Textile with plain text

I personally use a slight variation of the above. I prefer to use plain text with either Markdown or Textile for basic formatting. Then conversion with calibre and final adjustments with Sigil. I use both depending on what I want to accomplish as I find Markdown more intuitive but Textile allows for things like margins and text alignment. Even without using Markdown or Textile converting using calibre with heuristic processing will get most novels about 75-90% formatted.

Three stage variation: Starting with HTML and CSS

The biggest difference with this variation is it skips right to step three. It is possible to still use calibre to take the HTML and produce an ePub but in this case I would advise against doing so. calibre is an automated tool and will make changes to the HTML and CSS. Visually it should look the same but the HTML and CSS will be different. This can be good if you have HTML and CSS that is so horrible you need it cleaned as calibre does some normalization but if you’re using your own hand crafted HTML and CSS you probably don’t want it modified.

Once you have all of your files ready, open Sigil and have a blank document open. Right click in the book browser on the text folder. Choose the Add Existing Files. Select your HTML files and import them. You should now see the Text section includes all of those files. Do the same with your CSS.

You many need to edit the HTML files to ensure that the CSS is referenced from the correct location. All stylesheets are located in the Styles directory which is one step above the Text directory. For example:

<head>
<link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css">
</head>

If you have any images add them in the same way you added the HTML and CSS files. Also, make sure they’re referenced properly just like you did with the CSS.

Check the look in the book view to make sure everything looks correct. Also don’t forget to delete the Section0001.xhtml that is left over from it being a new file.

As you added each file the filename is perserved and it is added to the TOC based on the file itself. You have two options for working with the TOC. If you want to leave it file based you’re pretty much done. If you don’t like the text that displays in the TOC you can edit the toc.ncx and change the text.

You’re other option is to generate the TOC from headings. In the TOC browser (should be on the right hand side) click generate TOC from headings. Automatic TOC generation is based on h1 – h3 tags. h2 are nested under h1 and h3 are nested under h2. The only other way to deal with the TOC is to manually edit the toc.ncx by hand.

Conclusion

What I would recommend to authors is take the easiest path. If you’re  happy with the results of using Jutoh or Atlantis then your done. If you want to get a bit more involved use a variation of the three stage work flow I outlined. If all this seems like too much to deal with, pay someone to do it for you.

.



* 0.4.902 (0.5 beta) Avaliable

Posted on December 12th, 2011 by John. Filed under Sigil.


The first beta for 0.5 (0.4.902) is now available.

There are a few new features I’m most interested in getting feedback on. Inline spell check, translations, and the new PCRE engine. Of course crashes and major issues will be looked into and hopefully fixed before the final release.

.

    Comments Off