Plugin DevelopmentHow to develop plugins for KeePass 1.x. |
This documentation applies to KeePass 1.x plugins (all versions ≥ 1.15). 1.x plugins are fundamentally different from 2.x plugins. 2.x plugins cannot be loaded by KeePass 1.x.
A detailed SDK documentation is available here: Plugin SDK Documentation.
- Requirements
- Step-by-Step Tutorial
- Plugin Conventions and Recommendations
- Upgrading Plugins from ≤ 1.14 to ≥ 1.15
- Detailed Documentation of All Interfaces
- Plugin Framework for C++
Requirements
Before you can start developing a KeePass plugin, you need the following prerequisites:
- Latest KeePass source code package. You can get it from the KeePass website.
- A C++ development IDE / compiler.
- Windows Platform SDK.
The KeePass plugin API uses some concepts of the Component Object Model (COM) standard. If you don't have experience with COM, the following pages are recommended for reading:
- Wikipedia: IUnknown.
- Reference counting section on Wikipedia: Component Object Model.
- MSDN: COM Interfaces and Interface Implementations.
- MSDN: COM Interface Pointers and Interfaces.
- MSDN: COM QueryInterface: Navigating in an Object.
Step-by-Step Tutorial
Start your favorite IDE and create a new Win32 Project (application
type DLL, empty project).
In this tutorial the example plugin we're developing is called TestPlugin
.
Create two files in the new project: a C++ source file (TestPlugin.cpp)
and a header file (TestPlugin.h).
In order to access the KeePass interfaces, you have to include a header
file from the KeePass SDK: put an #include
statement in the
TestPlugin.h file, which includes the file
KeePassLibCpp/SDK/KpSDK.h
from the KeePass source code.
Windows DLLs may optionally implement a DllMain
function. So
if you want one (not required by KeePass though),
implement a default one now in the TestPlugin.cpp file (just always return
TRUE
):
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { UNREFERENCED_PARAMETER(hinstDLL); UNREFERENCED_PARAMETER(fdwReason); UNREFERENCED_PARAMETER(lpvReserved); return TRUE; }
The next step is to create a plugin class, which must implement the
IKpPlugin
interface. So, lookup the IKpPlugin
interface
(abstract C++ class) and design a class that implements all these methods.
Details about the methods can be found in the
Plugin SDK Documentation.
Now export a function, which KeePass will use to create
an instance of your plugin class:
KP_EXPORT HRESULT KP_API KP_I_CREATEINSTANCE_DECL(REFIID riid,
void** ppvObject, IKpUnknown* pAPI);
In this function, you'll need to create an instance of your plugin class
and store an interface pointer of the type requested by KeePass (riid
)
in the ppvObject
parameter. The pAPI
parameter is
an interface pointer to the KeePass API, which you should store for later use
in case you're able to return a valid plugin interface.
KeePass is currently offered only as ANSI application, not Unicode. Therefore, go to Project -> Test Plugin Properties and choose Multi Byte as character set.
It is recommended (but not required) to statically link with the runtime library (and MFC, if you're using it). To do this, go to Project -> Test Plugin Properties -> C/C++ -> Code Generation and choose a runtime library not ending with '-DLL'.
The last step before building your plugin is to add a version information
resource. So, go to the 'Resources' tab of the plugin project
and add a resource of type 'Version'. Here, set the product name to
KeePass Plugin
. All other fields can be freely set to strings
of your choice.
Example. You can find a fully documented and extended version of this simple plugin on the KeePass plugins web page ("Test Plugin").
Plugin Conventions and Recommendations
Conventions:
- The DLL file must have a version information resource, in which
the product name is set to
KeePass Plugin
. KeePass uses this to determine whether the DLL is a KeePass plugin or not (i.e. if you don't create a version information resource with this string, KeePass will not load your DLL file). - A KeePass plugin must export the following function:
KP_EXPORT HRESULT KP_API KP_I_CREATEINSTANCE_DECL(REFIID riid, void** ppvObject, IKpUnknown* pAPI);
KeePass will call this function to create an instance of your plugin class. You have to return an interface of typeriid
in theppvObject
parameter, if your plugin class supports this interface (returnS_OK
). Otherwise setppvObject
toNULL
and returnE_NOINTERFACE
. You may store thepAPI
interface pointer for later use. KeePass guarantees that the pointer is valid as long as it has a pointer to your plugin class instance.
Important: explicitly check for which interface KeePass is asking (riid
), otherwise your plugin is not upward-compatible and might crash in future KeePass versions. - A KeePass plugin may optionally export the following functions:
KP_EXPORT HRESULT KP_API KP_I_INITIALIZELIB_DECL(IKpUnknown* pAPI);
KP_EXPORT HRESULT KP_API KP_I_RELEASELIB_DECL(IKpUnknown* pAPI);
KeePass will call the first function after the DLL is loaded, and the second one shortly before the DLL is unloaded.
You should not store thepAPI
interface pointer for later use. Consider the pointers as temporary; they might become invalid as soon as you return from theKP_I_INITIALIZELIB_DECL
orKP_I_RELEASELIB_DECL
function. ThepAPI
interface pointer values passed toKP_I_INITIALIZELIB_DECL
andKP_I_RELEASELIB_DECL
are not guaranteed to be the same as each other, or as the pointer value passed toKP_I_CREATEINSTANCE_DECL
.
- The protocol is
DllMain
(if present),KP_I_INITIALIZELIB_DECL
(if present),KP_I_CREATEINSTANCE_DECL
, plugin interface methods,KP_I_RELEASELIB_DECL
(if present).
- KeePass is using the multi-byte character set. Therefore, make sure that you're also compiling your plugin in multi-byte character set mode, not Unicode.
Recommendations:
- All plugin files should begin with a common prefix. For example, if your
plugin is called VariousImport, the DLL file might be named
VariousImport.dll and its help file VariousImport.html.
If you don't use a common prefix, users might run into overwriting problems
when installing multiple plugins, because all plugins must be copied into
the KeePass application directory. For example, if there's a plugin that ships
with a ReadMe.txt file and another plugin that also ships with such a file,
the latter overwrites the readme file of the first, or the user chooses not to
overwrite and the readme file of the second plugin is not available. Using
a common prefix avoids this problem.
- The version information block should at least be available in English
(USA) language.
- There are two implementations of the
IKpConfig
interface. One implementation is identified byCLSID_KpConfig
, the other byCLSID_KpConfig_ReadOnly
. The first one supports both reading and writing, the second one only reading. It is highly recommended that you use the second implementation, if you only want to read configuration items.
Trying to write using theCLSID_KpConfig_ReadOnly
implementation will assert if KeePass is compiled in Debug mode, and will fail in Release mode (and might possibly destroy parts of the current configuration).
Upgrading Plugins from ≤ 1.14 to ≥ 1.15
When upgrading a plugin from KeePass ≤ 1.14 to ≥ 1.15, it is highly recommended that you create a new project file, start from scratch and copy / fill out the interface methods with the old code.
Notes:
- There is no
KeePass.lib
file anymore. The new plugin architecture is based on interfaces. Including theKpSDK.h
header file is all you need to do.
Do not compile with any of the files in the KeePass source code or include any other header file thanKpSDK.h
.
- Previously a command line prefix was registered by setting the
cmdLineArgPrefix
member of the plugin information structure. In the new architecture, the command line prefix must be returned by theGetProperty
member method of the plugin interface when being called with theKPPS_COMMANDLINEARGPREFIX
parameter.
YourGetProperty
might look like this:
STDMETHODIMP_(LPCTSTR) CYourPluginImpl::GetProperty(LPCTSTR lpName) { if(lpName == NULL) return NULL; if(_tcscmp(lpName, KPPS_COMMANDLINEARGPREFIX) == 0) return _T("mypluginprefix."); return NULL; }
The plugin should not access the KeePass command line until after all plugins have been loaded. This is because at that time KeePass of course can't call the
GetProperty
method of your plugin yet and consequently doesn't know the prefix yet (and this will lead to 'unknown command line option' warnings). Instead, perform command line dependent initialization when KeePass calls yourOnMessage
handler method with theKPM_DELAYED_INIT
code.
Detailed Documentation of All Interfaces
See the Plugin SDK Documentation.
Plugin Framework for C++
Thanks to Bill Rubin, there's an optional Plugin Framework available, facilitating the development of KeePass plugins in C++. The features in detail:
- Complete implementation of the
IID_IKpUnknown
interface. - Implementation of the
IID_IKpPlugin
interface, except for theOnMessage
member function, which is always application-specific. - Ability to get smart pointers to any other KeePass interface,
including
IKpAPI
,IKPCommandLine
,IKpCommandLineOption
,IKpConfig
,IKpDatabase
,IKpFullPathName
, andIKpUtilities
. - Implementation of the
KpCreateInstance
function, which the plugin must export from its DLL. - Comprehensive error checking of COM handshaking for Items 3 and 4. If an error is found, PFK displays a message box containing all information about the error. Without this feature, a plugin will, in most cases, silently fail to load. In other cases, it will fail to perform its function.
- Convenience utilities to display a message box, translate a Windows error code into a natural language string, and declare a standard string independently of character type.
- Compile-time checking of constructor invocations for smart COM pointers. Without this feature, it is difficult for a developer to interpret compiler error messages caused by using the wrong smart COM pointer constructor.
- The PFK code avoids defining macros. Instead templates, inline functions, typedefs, and other C++ constructs maintain safe design practices, with no runtime penalty.
There's also a Test Plugin available using the Plugin Framework.