Creating C++ Shared Libraries and DLLs
Guest blogger Peter Webb returns with another in an occasional series of postings about application deployment.
Contents
A previous post demonstrated how to use MATLAB Compiler to create standalone executables. In this article, I'll demonstrate how to use MATLAB Compiler to create C and C++ shared libraries or DLLs.
You create a shared library or DLL to add MATLAB-based functionality to an application you're developing. You select one or more MATLAB-based functions to include in the shared library, and MATLAB Compiler generates a binary file that you link against your application. Shared libraries generated by MATLAB Compiler are compatible with the Microsoft Visual Studio development environment on Windows and with the GNU C/C++ tool suite on most UNIX platforms. The list of supported compilers changes from one release to the next; be sure to check that list before starting your project.
Building a Shared Library
To illustrate the process of creating and using shared libraries, I'll use a cryptographic algorithm, the Vigenere cipher. The program I've written consists of two parts:
- libvingenere: A shared library containing two MATLAB functions encrypt and decrypt.
- vigenere.cpp: A C++ main program that calls the functions in libvigenere.
The source code for the MATLAB functions and the main program is available on MATLAB Central. The download package also includes VigenereDetails.html, which describes the implementation of the Vigenere cipher in MATLAB.
The mcc command invokes MATLAB Compiler both from within interactive MATLAB and at the system (DOS or UNIX shell) prompt. Use mcc to build a shared library from the MATLAB encrypt and decrypt MATLAB functions:
mcc -v -W cpplib:libvigenere -T link:lib encrypt decrypt
This mcc command breaks down into four parts:
- -v: Turn on verbose output.
- -W cpplib:libvigenere: Generate C++ wrapper code. Name the generated library libvigenere.
- -T link:lib: Invoke a C/C++ compiler to create a platform-specific binary shared library file from the generated code.
- encrypt decrypt: Place the encrypt and decrypt functions in the shared library. Generate C++ wrapper functions for each.
This command generates several files. Two of them are relevant here:
- libvigenere.dll: The platform-specific binary: the shared library itself. On most Unix systems, this file ends with a .so extension: libvigenere.so.
- libvigenere.h: Declarations of the C++ wrapper functions and C++ type conversion classes and utilities.
The Generated Interface
MATLAB Compiler generates many different kinds of functions: initialization, termination, error and print handling and, of course, the functions you selected to compile into the library.
In our example, MATLAB Compiler generates a C++ entry point for encrypt and decrypt. Stripped of some bookkeeping annotation, these functions look very much like the corresponding MATLAB functions.
void encrypt(int nargout, mwArray& ciphertext, const mwArray& plaintext, const mwArray& key);
void decrypt(int nargout, mwArray& plaintext, const mwArray& ciphertext, const mwArray& key);
The generated functions differ from the MATLAB functions in two significant ways:
- The C++ functions explicitly declare the types of their arguments.
- The C++ functions return void, passing back results in output parameters provided in the argument list.
Let's look at the encrypt function in detail. For comparison, here's encrypt's MATLAB function signature:
function ciphertext = encrypt(plaintext, key)
The encrypt MATLAB function has one output and two inputs. The encrypt C++ function has zero outputs (the void return type) and four inputs. The first C++ input indicates the number of outputs requested by the caller. As indicated by the MATLAB function, the number of outputs may be zero or one. Passing any other value will result in an error. The second C++ input is the output argument, passed by reference. encrypt will overwrite any data in this argument with the encrypted message. The third and fourth C++ input arguments are the function inputs, the plaintext and the encryption key. They are passed by constant reference which means the encrypt function cannot change their contents.
Unlike MATLAB, C++ requires all variables have declared, immutable types. Variables in MATLAB have types as well (matrix, cell array, structure, etc.), but the type of a MATLAB variable can change at any time. To accommodate this dynamic behavior in C++, the MATLAB Compiler provides the mwArray data type. All the functions generated by the MATLAB Compiler take mwArray inputs and return mwArray outputs. The mwArray API allows you to create mwArray objects from most C++ data types and to extract native C+++ data from a returned mwArray.
Calling Functions in a Shared Library
Now that I've created a shared library, I need to write a program to call the library's public functions. The program performs six tasks:
Below, I demonstrate how these steps translate into the code of the example's main program, vigenere.cpp.
Step 1: All programs that use a MATLAB Compiler-generated shared library must include the library's header file. The header file has the same base name as the compiled library, libvigenere in this case, and a .h extension.
// vingenere.cpp: Encrypt and decrypt using the Vigenere cipher. #include "libvigenere.h" #include <iostream>
Step 2: The main program parses the command line. The first argument, a switch, determines the type of action: -e means encrypt, -d decrypt. The second argument is the message, and the third, the key.
int main(int ac, const char *av[]) { // Encrypt or decrypt? Determined by command line switch bool enc = strcmp(av[1], "-e") == 0;
Step 3: Initialize the runtime and start the library before calling any functions exported from the runtime or the library. Failure to initialize will cause your program to crash. Always check for success (the initializers return false if they fail) and issue error messages as necessary.
// Initialize the MATLAB Compiler Runtime global state if (!mclInitializeApplication(NULL,0)) { std::cerr << "Could not initialize the application properly." << std::endl; return -1; }
// Initialize the Vigenere library if( !libvigenereInitialize() ) { std::cerr << "Could not initialize the library properly." << std::endl; return -1; }
Step 4: Convert the C++ strings from the command line (the message and the key) into MATLAB strings by creating mwArray objects. These declarations cannot appear before the initialization calls in Step 3.
// Must declare all MATLAB data types after initializing the // application and the library, or their constructors will fail. mwArray text(av[2]); mwArray key(av[3]); mwArray result;
Step 5: Invoke the exported functions. Encrypt or decrypt as indicated by the command line switch. Note that the C++ functions have the same name as their MATLAB counterparts, and that all return values must be passed in by reference. The mwArray class defines
// Initialization succeeded. Encrypt or decrypt.
if (enc == true) { // Encrypt the plaintext text with the key. // Request one output, pass in two inputs encrypt(1, result, text, key); } else { // Decrypt the ciphertext text with the key. // Request one output, pass in two inputs decrypt(1, result, text, key); } std::cout << result << std::endl;
Step 6: Shut down the library and the runtime, in the opposite order of initialization (library first, then runtime).
// Shut down the library and the application global state. libvigenereTerminate(); mclTerminateApplication(); }
Creating and Running the Application
MATLAB Compiler uses the mbuild function to compile the code it generates into a shared library. mbuild knows how to invoke the C/C++ compiler with the correct switches so the compiler can find the required include files and libraries. You can use mbuild to create your own executables and link them with MATLAB Compiler-generated shared libraries. On Windows, for example, issue this command:
mbuild vigenere.cpp libvigenere.lib
On UNIX, you link against a .so file instead of a .lib file:
mbuild vigenere.cpp libvigenere.so
In both cases, mbuild produces an executable called vigenere.
Encrypt the message with the secret key.
>> !vigenere -e "Algebra, the Music of the Reason" "MATLAB" MLZPBSM LSEAYUKTCA FSDHFLRXLSPZ
The first argument is the message, the second argument the key. Note the leading exclamation point -- this command runs the vigenere executable from within MATLAB, using the system command. You can run these commands on the UNIX or DOS command line as well, but you have to configure your environment correctly.
Decrypt to verify the encryption worked:
>> !vigenere -d "MLZPBSM LSEAYUKTCA FSDHFLRXLSPZ" "MATLAB" ALGEBRA THE MUSIC OF THE REASON
Because the alphabets in the Vigenere square only include letters and the space character, the decrypted message lacks punctuation marks. Extending the algorithm to handle punctuation is an exercise left to the reader.
And extra points if you can figure out why I chose that phrase (aside from its poetic merit, which is in the eye of the beholder).
Next: Using Multiple Shared Libraries
I've shown you how to combine several MATLAB-based functions into a single shared library, and how to incorporate that library into a host application. In a later posting, I'll demonstrate how to use multiple shared libraries from a single host application.
What else can I tell you about shared libraries? Let me know here.
- 类别:
- Deployment