Star date: 2014.151
Here's the source code: github.com/graeme-hill/crossguid
Over time I will like make updates and bug fixes to the git repository, but I will probably not update the snippets in this article.
Minimalist: The amount of code will have a negligible affect on compile
time and deployment size. It should also be trivial to source embed the module
instead of linking a .so
, .a
, .dll
, etc. The implementation should also
avoid unnecessary abstraction so that there are fewer lines of code and anyone
can quickly read the implementation to see clearly what it is doing.
Cross platform: Works on all major operating systems. I am going for Windows, Linux, Mac OS, Android, and iOS.
UUID/GUID: UUID stands for Universally Unique Identifier and GUID stands for Globally Unique Identifier. Although the names imply that a GUID is not guaranteed to be unique of we combine values from across the galaxy, the real differentiation is that GUID refers to various implementations of the UUID standard. For our purposes we can consider them synonyms. In any case, the values themselves are 128 bits of data that are guaranteed to be unique among other properly generated unique identifiers. They are not cryptographically random numbers. If ID conflicts can break your system or create security concerns then GUIDs are not for you unless you can complete control over their generation. For example, if you have an API where you can create entities with GUIDs for identifiers and an ID collision would break the system, then you probably don't want third party consumers of your API to be allowed to generate their own GUIDs; you want the server to do all the GUID generation.
C++: I mean actual modern C++ where possible, not just C with classes.
Create a very small implementation that utilizes the best, most standardized method of UUID generation on each system instead of using any risky, untested, half-baked implementations that may be flawed. This means that we will need to use some form of conditional compilation so that we can have slightly different code depending on the OS, and we will need some wrapper so that external code which utilizes this UUID library can ignore the underlying system implementation. There should also be no dependencies on any non-standard libraries.
I am implementing this from scratch because I couldn't find any existing implementation that was sufficiently generic and minimal. I generally avoid boost because boost modules tend to depend on other boost modules and then all of a sudden you have a ton of extra dependencies.
First let's define an operating system agnostic class for our identifiers. Note that there is no way for me to be completely consistent in my use of either GUID or UUID since the Windows world tends to use the term GUID while the unix world uses the term UUID. In the generic interface I will use GUID because it is easier to pronounce and I think it looks nicer in pascal case.
// Class to represent a GUID/UUID. Each instance acts as a wrapper around a
// 16 byte value that can be passed around by value. It also supports
// conversion to string (via the stream operator <<) and conversion from a
// string via constructor.
class Guid
{
public:
// create a guid from vector of bytes
Guid(const vector<unsigned char> &bytes);
// create a guid from array of bytes
Guid(const unsigned char *bytes);
// create a guid from string
Guid(const string &fromString);
// create empty guid
Guid();
// copy constructor
Guid(const Guid &other);
// overload assignment operator
Guid &operator=(const Guid &other);
// overload equality and inequality operator
bool operator==(const Guid &other) const;
bool operator!=(const Guid &other) const;
private:
// actual data
vector<unsigned char> _bytes;
// make the << operator a friend so it can access _bytes
friend ostream &operator<<(ostream &s, const Guid &guid);
};
The implementations of all the functions prototyped above can be generic, but
there will be another class named GuidGenerator
which will need to have some
custom code for each operating system. To keep things simple and avoid coupling
this code with any build system, I'll just use a series of super ugly #ifdef
s
to split out platform specific code. Now let's look at how we generate a guid
on each operating system...
Pretty much all linux systems have built-in UUID implementations. In fact, if you just run a command like this in the terminal it should print out a guid:
cat /proc/sys/kernel/random
Or you can use a command line tool available on most systems:
uuidgen
uuidgen
depends on a C library called libuuid. We could try invoking one of
the above commands from our C++ program, but it's much better to directly use
the library that uuidgen
depends on. In order to do that we'll need the header
files to be available on the system instead of just the compiled library
itself. If you're on Ubuntu you can install it with this command:
sudo apt-get install uuid-dev
Then you can test it out by making a simple C++ program that generates a UUID
and prints it to the console (the file is called linux_uuid_test.cpp
):
#include <uuid/uuid.h>
#include <iostream>
int main(int argc, char *argv[])
{
uuid_t id;
uuid_generate(id);
char *string = new char[100];
uuid_unparse(id, string);
std::cout << string << std::endl;
return 0;
}
To compile the program make sure you supply the -luuid
argument:
g++ linux_uuid_test.cpp -o linux_uuid_test -luuid
And then run it:
chmod +x linux_uuid_test
./linux_uuid_test
Okay so that was easy. Now that we know how to use uuid_generate()
we can
implement the linux version of Guid::newGuid()
as follows:
#ifdef GUID_LIBUUID
Guid Guid::newGuid()
{
uuid_t id;
uuid_generate(id);
return id;
}
#endif
Apple is nice enough to have strong API parity between MacOS and iOS so we only
need one implementation for the two operating systems. Since CoreFoundation
framework contains a C function named CFUUIDCreate
we don't even have to do
any Objective-C++ magic. We can just call a plain old C function. Here's a
little hello world program that creates a guid and prints it on the screen.
Since it gives each byte as a separate variable we need to write some long
winded code to write them all:
#include <CoreFoundation/CFUUID.h>
#include <iostream>
void printByte(unsigned char byte)
{
printf("%02x", byte);
}
int main(int argc, char *argv[])
{
auto guid = CFUUIDCreate(NULL);
auto bytes = CFUUIDGetUUIDBytes(guid);
CFRelease(guid);
printByte(bytes.byte0);
printByte(bytes.byte1);
printByte(bytes.byte2);
printByte(bytes.byte3);
std::cout << "-";
printByte(bytes.byte4);
printByte(bytes.byte5);
std::cout << "-";
printByte(bytes.byte6);
printByte(bytes.byte7);
std::cout << "-";
printByte(bytes.byte8);
printByte(bytes.byte9);
std::cout << "-";
printByte(bytes.byte10);
printByte(bytes.byte11);
printByte(bytes.byte12);
printByte(bytes.byte13);
printByte(bytes.byte14);
printByte(bytes.byte15);
std::cout << std::endl;
return 0;
}
If you call this file mac_uuid_test.cpp
then you can compile it with:
clang++ mac_uuid_test.cpp -o mac_uuid_test -std=c++11 -framework CoreFoundation
And then run it:
chmod +x mac_uuid_test
./mac_uuid_test
Now that we've proven the basic functions work as expected, it can be converted
to an actual implementation of Guid::newGuid()
:
#ifdef GUID_CFUUID
Guid GuidGenerator::newGuid()
{
auto newId = CFUUIDCreate(NULL);
auto bytes = CFUUIDGetUUIDBytes(newId);
CFRelease(newId);
const unsigned char byteArray[16] =
{
bytes.byte0,
bytes.byte1,
bytes.byte2,
bytes.byte3,
bytes.byte4,
bytes.byte5,
bytes.byte6,
bytes.byte7,
bytes.byte8,
bytes.byte9,
bytes.byte10,
bytes.byte11,
bytes.byte12,
bytes.byte13,
bytes.byte14,
bytes.byte15
};
return byteArray;
}
#endif
In Windows there is a function called CoCreateGuid
in objbase.h
that
creates a GUID
value. The function is pretty simple to use, but it's a bit of
a pain to extract the actual bytes. If we make a completely empty C++ project
in Visual Studio then we can test it with one simple file that looks like this:
#include <iostream>
#include <objbase.h>
int main(int argc, char *argv)
{
GUID guid;
CoCreateGuid(&guid);
printf("%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
guid.Data1, guid.Data2, guid.Data3,
guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
}
And this can be incorporated in the GuidGenerator
class like so:
#ifdef GUID_WINDOWS
Guid GuidGenerator::newGuid()
{
GUID newId;
CoCreateGuid(&newId);
const unsigned char bytes[16] =
{
(newId.Data1 >> 24) & 0xFF,
(newId.Data1 >> 16) & 0xFF,
(newId.Data1 >> 8) & 0xFF,
(newId.Data1) & 0xff,
(newId.Data2 >> 8) & 0xFF,
(newId.Data2) & 0xff,
(newId.Data3 >> 8) & 0xFF,
(newId.Data3) & 0xFF,
newId.Data4[0],
newId.Data4[1],
newId.Data4[2],
newId.Data4[3],
newId.Data4[4],
newId.Data4[5],
newId.Data4[6],
newId.Data4[7]
};
return bytes;
}
#endif
This is definitely where it gets tricky. The correct way to generate a guid in
Android is using the Java API, not any native C or C++ functions. What we'll
do is use JNI (Java Native Interface) to invoke a Java function from C++. Since
Android apps always have a Java entry point and then optionally delegate to a
C++ library, we can take the JNIEnv
pointer that is given at startup and use
it to call randomUUID()
on java.util.UUID
. For this case I had to make a
special constructor for GuidGenerator
. The implementation is as follows:
#ifdef GUID_ANDROID
GuidGenerator::GuidGenerator(JNIEnv *env)
{
_env = env;
_uuidClass = env->FindClass("java/util/UUID");
_newGuidMethod = env->GetStaticMethodID(_uuidClass, "randomUUID", "()Ljava/util/UUID;");
_mostSignificantBitsMethod = env->GetMethodID(_uuidClass, "getMostSignificantBits", "()J");
_leastSignificantBitsMethod = env->GetMethodID(_uuidClass, "getLeastSignificantBits", "()J");
}
Guid GuidGenerator::newGuid()
{
jobject javaUuid = _env->CallStaticObjectMethod(_uuidClass, _newGuidMethod);
jlong mostSignificant = _env->CallLongMethod(javaUuid, _mostSignificantBitsMethod);
jlong leastSignificant = _env->CallLongMethod(javaUuid, _leastSignificantBitsMethod);
unsigned char bytes[16] =
{
(mostSignificant >> 56) & 0xFF,
(mostSignificant >> 48) & 0xFF,
(mostSignificant >> 40) & 0xFF,
(mostSignificant >> 32) & 0xFF,
(mostSignificant >> 24) & 0xFF,
(mostSignificant >> 16) & 0xFF,
(mostSignificant >> 8) & 0xFF,
(mostSignificant) & 0xFF,
(leastSignificant >> 56) & 0xFF,
(leastSignificant >> 48) & 0xFF,
(leastSignificant >> 40) & 0xFF,
(leastSignificant >> 32) & 0xFF,
(leastSignificant >> 24) & 0xFF,
(leastSignificant >> 16) & 0xFF,
(leastSignificant >> 8) & 0xFF,
(leastSignificant) & 0xFF,
};
return bytes;
}
#endif
Actually handling the guids was quite easy, but the JNI calls are a bit tricky
and so is the project setup. In the git repository there is a subfolder called
android
which contains the project scaffolding and everything needed to build
the project. There is also a custom source file called jnitest.cpp
which
contructs a GuidGenerator
and then invokes the exact same tests that run on
every other platform. There is also a script (android.sh
) that will compile
the library for every CPU supported by android and run an app on your emulator
that displays test output.
For the full source code and more info on how to use or build it, check out the repository on GitHub: github.com/graeme-hill/crossguid