4
\$\begingroup\$

I need to implement some functionality of the otherwise monolithic application via DLL that is loaded at runtime. (Think about a customized DLL -- different for each customer.)

The Visual C++ from VS 2013 is used.

The application gets the full name name of the DLL. The function to be called has a fixed name (convert in my case) and fixed number and type of the arguments (a reference to a constant vector of strings -- see the cref_vs below). This means the function is implemented in C++.

I have modified the example from the MSDN doc, and it seems to work. However, I must be sure I did not overlooked something important. Here is the code (simplified) to load the DLL and call the function:

typedef const std::vector<std::string> & cref_vs;
typedef int(*CONVERTPROC)(cref_vs vs);

Notice that unlike in the official example that mixes calling a C code in DLL from a C++ function, I do not use __cdecl. I guess that this way I can reliably pass a C++ object (like a vector of strings). Is it correct?

int call_convert_from_dll(const std::string & dllname, 
                          const std::vector<std::string> & vs)
{
    HINSTANCE hinstLib;
    CONVERTPROC convert;
    BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;

    // Get a handle to the DLL module.
    hinstLib = LoadLibrary(dllname.c_str());

    // If the handle is valid, try to get the function address.
    if (hinstLib != nullptr) {
        convert = (CONVERTPROC)GetProcAddress(hinstLib, "convert");

        // If the function address is valid, call the function.
        if (convert != nullptr) {
            (convert)(vs);
        }

        // Free the DLL module.
        fFreeResult = FreeLibrary(hinstLib);
    }

    if (!fRunTimeLinkSuccess)
        return 1;

    return 0;
}

Now, because I want the unmangled name of the function inside the DLL, I need to use extern "C", and it must be exported via __declspec(dllexport). However, I do not want to use __cdecl calling convention:

#include <windows.h>
#include <iostream>

using namespace std;

extern "C" {

__declspec(dllexport) int convert(const std::vector<std::string> & vs)    
{
    cout << "my.dll -- convert called with arguments:\n";
    for (int i = 0; i < argc; ++i)
         cout << argv[i] << endl;
    return 0;
}


} // extern "C"

Can you confirm that I am doing it correctly?

\$\endgroup\$
3
\$\begingroup\$

Short answer: No, but I won't hold it against you. ;)

I'd suggest reading up on using C++ with DLLs in a compatible fashion, and would suggest this excellent article as a good place to start. This article also touches on exporting STL types, which your code is doing.

So, the question is, how portable/robust do you need/expect your approach to be? If you need developers from anywhere to be able to plug into your system, stick to a C interface if possible (avoiding STL types -- pass a char** across the boundary, and convert that on the C++ side to an STL vector<string>). Otherwise, upgrade to a virtual class based system -- and make sure that you don't directly use STL, but instead use virtual class wrappers for those types as well. Note that this carries on -- e.g., if you want to use an iterator class used by the STL type you're wrapping, you'll need another abstract (iterator) class that your abstract (iterable) class references, so it can get a little ugly.

Minor code notes...

  • fFreeResult is assigned, but never used.
  • fRunTimeLinkSuccess is not set (after initialization).
  • The "convert" string might better be contained in a macro (or a small set of macros), so that your plugin clients can declare a convert function, and you can refer to that function name without the possibility of typo.
  • Your example plugin refers to argc and argv that don't exist.
\$\endgroup\$
  • 1
    \$\begingroup\$ @LokiAstari Good catch on fRunTimeLinkSuccess never changing from its initialized value. \$\endgroup\$ – Travis Snoozy Apr 26 '14 at 16:07
  • \$\begingroup\$ For the fRunTimeLinkSuccess -- corrected. I did remove some other lines. This one should stay there. For the argc and argv. This was actually copied from another version of the code (my fault), that was passed the arguments from the command line (for testing). The newer version parses the string from a file (StringTok to vector<string>. So, I should do the opposite way -- to convert vector<string> to the int and char*[], right? Thanks, I will be back after reading the article. \$\endgroup\$ – pepr Apr 26 '14 at 16:35
  • \$\begingroup\$ Thanks for the article. It seems that it will be better to pass the char* (the string from a file) and wrap the function body to the try/catch to avoid exceptions bubble from DLL to the caller (returning integer error code). \$\endgroup\$ – pepr Apr 26 '14 at 17:22
  • \$\begingroup\$ Please, have a look at the second version codereview.stackexchange.com/q/48260/16189 \$\endgroup\$ – pepr Apr 26 '14 at 19:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.