Introduction
LevedDB is a key/value store that is developed by Google. Getting it to build on Windows can be painful.
Exporting a C++ class from a DLL can be hard if you want it be to be able to be used by different compilers. Alex Bleckhman as an excellent article here on Code Project titled HowTo: Export C++ classes from a DLL. However, doing that can still be a pain as you cannot use exceptions, C++ types such as
std::string
. In addition, if you want to make a COM interface so you can have memory management and interface management, you still have a lot of code to write.
This article uses the free library at https://github.com/jbandela/cross_compiler_call to build a wrapper library for leveldb. The full code can be found here.
I packaged the needed files in the attached zip file. You can also get the file from here
Note, while there is a C wrapper for leveldb that I could have used, I decided to do it this way to try out the above library in developing something with real world use.
In this article, I will be talking about how the use the package. This will not be a tutorial on using cross_compiler_call to build something like this. If there is enough interest in the comments, I will write another article providing a walk-through of how this package was built.
Background
First I had to build the leveldb library. Finding a version of leveldb to build on Windows proved to be a pain. I tried both https://code.google.com/p/leveldbwin/ and https://code.google.com/r/kkowalczyk-leveldb/. However, they were older versions of leveldb. I then found the bitcoin repository on GitHub. I figured that that would be pretty well maintained. In the source, they have a distribution of leveldb with Windows support. However, there is no Visual C++ project. To build it I used
MinGW G++ obtained from nuwen.net and used msys as the shell to build the .a file. Then I compiled leveldb_cc_dll.cpp into a
DLL with G++ and linked the to the .a file from previously.
Using the code
For an example of using this code, take a look at
example.cpp. You will need C++11 support with variadic templates. If you use g++ you will need -std=c++11 in the command line or you will get a lot of errors. To build with Visual C++, you will need the November CTP of Visual Studio 2012. You can download it from here.
Once you have all that, you just compile
example.cpp. Make sure the level_db_cc.dll is in the same directory as the exe file and run the exe file. There is nothing to link.
We will be going through parts of
example.cpp to show how this is done
#include <iostream>
#include "leveldb_cc/level_db_interfaces.h"
The second line includes the file that defines our interfaces
using namespace leveldb_cc;
The leveldb interfaces are in namespace leveldb_cc
. In addition, there is a bug in the MSVC compiler that affects name lookup. If you do not include this, you will get a compiler error in Visual C++.
int main(){
cross_compiler_interface::module m("leveldb_cc_dll");
This creates a module that will load the specified
DLL. Note you leave off the DLL extension. In Windows the library adds .dll and in Linux the code adds .so.
The module will automatically unload the library when it goes out of scope:
auto creator = cross_compiler_interface::create_unknown(m,"CreateLevelDBStaticFunctions")
.QueryInterface<leveldb_cc::ILevelDBStaticFunctions>();
Calls a function in the DLL
CreateLevelDBStaticFunctions
to create the class factory interface. The
create_unknown
returns IUknown
. So we call QueryInterface
to get
ILevelDBStaticFunctions
.
{
We want to delete the database in the end, but we cannot delete the database if it is open. So we open a scope so that the db object will go out of scope closing the database.
auto options = creator.CreateOptions();
options.set_create_if_missing(true);
options.set_write_buffer_size(8*1024*1024);
options.set_block_cache(creator.NewLRUCache(1024*1024));
options.set_filter_policy(creator.NewBloomFilterPolicy(10));
The code creates the options for opening the database. We set it to create the database if it is not present. We also set up the
LRUCache
and BloomFilterPolicy
.
auto db = creator.OpenDB(options,"c:/tmp/testdb");
auto wo = creator.CreateWriteOptions();
wo.set_sync(false);
auto wb = creator.CreateWriteBatch();
wb.Put("Key1","Value1");
wb.Put("Key2","Value2");
wb.Put("Key3","Value3");
wb.Put("Key4","Value4");
wo.set_sync(true);
db.WriteBatch(wo,wb);
auto ro = creator.CreateReadOptions();
auto snapshot = db.GetSnapshot();
db.PutValue(wo,"AfterSnapshot1","More Value1");
ro.set_snapshot(snapshot);
auto iter = db.NewIterator(ro);
std::cout << "Iterator with snapshot\n";
for(iter.SeekToFirst();iter.Valid();iter.Next()){
std::cout << iter.key().ToString() << "="
<< iter.value().ToString() << "\n";
};
std::cout << "\n\n";
ro.set_snapshot(nullptr);
db.ReleaseSnapshot(snapshot);
auto iter2 = db.NewIterator(ro);
std::cout << "Iterator without snapshot\n";
for(iter2.SeekToFirst();iter2.Valid();iter2.Next()){
std::cout << iter2.key().ToString() << "="
<< iter2.value().ToString() << "\n";
};
std::cout << "\n\n";
db.DeleteValue(wo,"Key1");
auto iter3 = db.NewIterator(ro);
std::cout << "Iterator after delete Key1 snapshot\n";
for(iter3.SeekToFirst();iter3.Valid();iter3.Next()){
std::cout << iter3.key().ToString() << "="
<< iter3.value().ToString() << "\n";
};
The code sets up a WriteBatch
and writes some keys and values as a batch. Then the code saves a snapshot. Then it adds another key and iterates with and without
the snapshot. The code also deletes a value and iterates to show it is deleted.
}
auto s = creator.DestroyDB("c:/tmp/testdb",creator.CreateOptions());
After the db goes out of scope at the closing brace, we then destroy the database.
Notes and Points of Interest
Note: This code is not fully tested. Use at your own risk. If you find any bugs, please let me know and I will try to fix them.
Even better, fork the repository and make it better.
I think this code is a good exercise for making an interface that works across different compilers (even ones as different as MSVC and G++). The cross_compiler_call library makes it a lot easier as it supports handing strings and vectors across the DLL boundary. If there is interest, I would be glad to discuss how the
DLL was created.
Please let me know what you think.
History
- Initial version: 3/28/2013.