LevelDB DLL for Windows - A New Approach to Exporting C++ Classes from a DLL

LevelDB DLL for Windows - A New Approach to Exporting C++ Classes from a DLL

By John Bandela, 28 Mar 2013
 

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();

Calls a function in the DLLCreateLevelDBStaticFunctions to create the class factory interface. The create_unknown returns IUknown. So we call QueryInterface to get ILevelDBStaticFunctions

// Open a scope so db goes out of scope so we can delete the database
{ 

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);
 
// Set cache of 1MB
options.set_block_cache(creator.NewLRUCache(1024*1024));
 
// Set bloom filter with 10 bits per key
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.

// Open the db        
auto db = creator.OpenDB(options,"c:/tmp/testdb");
 
auto wo = creator.CreateWriteOptions();
wo.set_sync(false);
 
// Add a few key/value pairs in a batch
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();

// Save a snapshot
auto snapshot = db.GetSnapshot();
 
// Add more stuff to db
db.PutValue(wo,"AfterSnapshot1","More Value1");
 
        
// Use the snapshot
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";
 
// Clear the snapshot
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.

}
 
// Delete the db 
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

John Bandela
Software Developer
United States United States

你可能感兴趣的:(模块化,c/c++,开源)