Call Function in DLL from C++ Program?

I'm attempting to call a function inside a shared library DLL file using a C++ program. I'm using C++ 17 for all files in question.

The DLL file in question was compiled from the following C++ file called test.cpp, containing a single function called test:

1
2
3
4
5
6
7
8
#include <iostream>

int test()
{
    std::cout << "test\n";
    
    return 0;
}


Compiled with the following Visual Studio 2022 Native Command Prompt command:

 
cl.exe /LD test.cpp


Which provides me with a DLL file called test.dll.

I now want to call the test function inside the DLL file. I used the a related post answer (https://stackoverflow.com/questions/8696653/dynamically-load-a-function-from-a-dll ) and the official GetProcAddress function documentation (https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress ) to write a program for loading functions from a DLL file as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <string>
#include <windows.h>
#include <locale>
#include <codecvt>

std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> StringConverter;
typedef int(__stdcall* DLLFx)();

std::string GetWorkingDir()
{
    TCHAR buffer[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, buffer, MAX_PATH);
    std::wstring::size_type pos = std::wstring(buffer).find_last_of(L"\\/");

    return StringConverter.to_bytes(std::wstring(buffer).substr(0, pos).c_str()) + "\\";
}

bool LoadDLLFunction(std::string libPath, std::string fxName)
{
    HINSTANCE hGetProcIDDLL = LoadLibrary((WCHAR*)StringConverter.from_bytes(libPath.c_str()).data());

    if (!hGetProcIDDLL)
    {
        std::cout << "ERROR: Could not locate DLL file\"" + libPath + "\"!\n";

        return false;
    }

    DLLFx fx = (DLLFx)GetProcAddress(hGetProcIDDLL, (CHAR*)StringConverter.from_bytes(fxName.c_str()).data());

    if (!fx)
    {
        std::cout << "ERROR: Could not locate function \"" + fxName + "\" in DLL file \"" + libPath + "\"!\n";

        return false;
    }

    return true;
}

int main()
{
    std::string workDir = GetWorkingDir();
    bool result = LoadDLLFunction(workDir + "test.dll", "test");

    if (result)
    {
        std::cout << "Result of loaded DLL function call: " << DLLFx() << "\n";
    }

    std::cout << "\n";

    system("pause");

    return 0;
}


Where:

- The global variable StringConverter is the converter for converting the regular std::string to the std::wstring types that many Windows API functions accept (deprecated in C++ 17 and later).
- The global variable DLLFx is the pointer to the loaded function from the DLL.
- The function GetWorkingDir serves for acquiring the current working directory of the program.
- The function LoadDLLFunction serves for loading a function from a DLL file.

I then simply attempt to call the loaded function, and display the returned result (which should just be a 0, and a test string printed to the console as per the definition of the test function in the loaded DLL).

The issue is the fact that the program above finds & loads the DLL, but fails to load the function inside the DLL, resultant printed console message always being ERROR: Could not locate function "test" in DLL file "test.dll"!.

One of the possible issues I imagined could be causing a problem is the string conversions when passing the paths of the DLL file and/or the name of the DLL function, where I pass the two with WCHAR* and CHAR* types respectivelly. Those two appear to represent wchar_t and char respectivelly, which should be the types of the two strings I'm passing, but I could be missing some underlying string mechanics that are taking place here.

What am I missing here? What is the proper way to load & call a DLL function from a C++ program?

Thanks for reading my post, any guidance is appreciated.
You need to declare any functions to be exported from the DLL with the __declspec(dllexport) attribute.

Either that, or use an explicit module-definition (.def) file.

Also, if the DLL is compiled from C++ code (not pure C), then you also may want to add extern "C" to disable C++ name mangling 😏
https://en.wikipedia.org/wiki/Name_mangling


Use a tool like Dependencies to check which symbols (functions, etc.) are actually exported from your DLL:
https://github.com/lucasg/Dependencies

(be sure that "View" → "Undecorate C++ Functions" is disabled, i.e. not checked, as GetProcAddress() needs "raw" unprocessed name)


Try something like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C"
{
    int __declspec(dllexport) test(void)
    {
        std::cout << "test\n";
        return 0;
    }
}

Result:
https://i.imgur.com/LeTtcT3.png


BTW, if you use the import library of your DLL, then you don't need to use the LoadLibrary() plus GetProcAddress() machinery:
https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-creation#creating-an-import-library
Last edited on
Where do you specify the imports/exports for the .dll?

All functions exported from a .dll need to be specified as such (usually using c style to prevent c++ name mangling). Then in the program that uses these exported functions you need to declare them as imported.

https://learn.microsoft.com/en-us/cpp/build/importing-into-an-application-using-declspec-dllimport?view=msvc-170

PS. If you use GetProcAddress() then you don't need to specify an import (although you still need to define exports).
Last edited on
A bit of a tutorial/walkthrough on creating and consuming a dynamic link library, DLL, using Visual Studio might be helpful. In the walkthrough you create a DLL and a client console app to use the DLL "as a unit" together.

https://learn.microsoft.com/en-us/cpp/build/walkthrough-creating-and-using-a-dynamic-link-library-cpp

Creating a DLL is not as simple as creating a stand-alone console app, it requires a lot more preparation in code and compiling/linking.

https://learn.microsoft.com/en-us/cpp/build/linking-an-executable-to-a-dll

Sometimes using the IDE makes a lot more sense than using command-line compiling. When creating a linked DLL and client app using the IDE is a lot less stress and work.

If'n wanting to create a static library there's:

https://learn.microsoft.com/en-us/cpp/build/walkthrough-creating-and-using-a-static-library-cpp

A static library is linked into your client app so no need to distribute a DLL. The downside is it bloats up the size of the client.

FYI, one rather neat feature the MS Learn website has IMO is downloading a topic as a PDF. You can download the entire "tree" of VS projects and build system that include building and using a DLLs for offline reading.
Last edited on
Use:


dumpbin /exports test.dll


to see what exports have been defined for the specified dll.
Creating a LIB file from a DLL on the command-line that can be used by a client app when compiling....you could use the LIB build tool:

https://learn.microsoft.com/en-us/cpp/build/reference/overview-of-lib

TBH this is something I don't know a lot about, command-line builds using the Visual Studio build tools. This topic pushed me to poke my nose into the dark recesses of the bowels of Visual Studio using the documentation.

I knew it was possible to do command-line builds using VS build tools, I simply am not all that clued in how to do such builds. Mostly because I've never really done it.

Maybe I should expend some time and skull sweat to cram the info into my cranium. It certainly couldn't hurt to know how.

I downloaded the entire topic, C/C++ projects and build systems in Visual Studio, to PDF. Easier to make the learning gears grind and squeal on this overall and important subject.
Use a tool like Dependencies

That is a really useful tool! With or without the PE Viewer.
Thanks for all the replies everyone;

To try out these suggestions, I first changed my test.cpp file that gets compiled to a DLL according to kigar64551's sugggestions (adding the extern "C" and __declspec(dllexport) designations), as such:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C"
{
    int __declspec(dllexport) test()
    {
        std::cout << "test\n";
        return 0;
    }
}


Which I then compiled to test.dll, and ran a dumpbin /exports test.dll command, resulting in the following output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Dump of file test.dll

File Type: DLL

  Section contains the following exports for test.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00001180 test

  Summary

        3000 .data
        3000 .pdata
       12000 .rdata
        1000 .reloc
       23000 .text
        1000 _RDATA


Given the presence of a test in the console output above, I was hopeful that the function would now be callable.

Unfortunately, my program still fails to locate the test function inside the DLL...

I also tried to simplify the function name argument supplied to GetProcAddress by just doing fxName.c_str() instead of (CHAR*)StringConverter.from_bytes(fxName.c_str()).data(), to no avail.

I also tried changing the name of the test function to something other than the filename, test_fx for example, yet it still remained undiscovered by my program.

I will try and get the Dependencies tool working and check the DLL through that, however given that I can see test when I use the dumpbin command, I'm assuming that the issue is somewhere else.
Last edited on
Yes, if exported symbol "test" shows up with dumpbin or in Dependencies, then the DLL-side looks good.

Maybe something with your String conversion goes wrong? Have you tried to just call GetProcAddress("test") right away?

Are you 100% sure you load the "correct" DLL file, not some old version or whatever?


BTW, I think you should change/simplify your code to:
1
2
3
4
5
6
7
8
9
10
11
12
bool LoadDLLFunction(const std::wstring &libPath, const std::string fxName)
{
    HINSTANCE hGetProcIDDLL = LoadLibraryW(libPath.c_str());
    /* ... */
    GetProcAddress(hGetProcIDDLL, fxName.c_str());
    /* ... */
}

int main(void)
{
    LoadDLLFunction(L"test.dll", "test");
}
Last edited on
Thanks for the reply;

Ok to update, it does appear that the string conversions for the DLL path and the function name were part of the problem. After adjusting the program code like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <string>
#include <windows.h>
#include <locale>
#include <codecvt>

std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> StringConverter;
typedef int __declspec(dllexport) (__stdcall* DLLFx)();

std::string GetWorkingDir()
{
    TCHAR buffer[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, buffer, MAX_PATH);
    std::wstring::size_type pos = std::wstring(buffer).find_last_of(L"\\/");

    return StringConverter.to_bytes(std::wstring(buffer).substr(0, pos).c_str()) + "\\";
}

bool LoadDLLFunction(std::string libPath, std::string fxName)
{
    std::wstring libPathWide = StringConverter.from_bytes(libPath);

    HINSTANCE hGetProcIDDLL = LoadLibraryW(libPathWide.c_str());

    if (!hGetProcIDDLL)
    {
        std::cout << "ERROR: Could not locate DLL file\"" + libPath + "\"!\n";

        return false;
    }

    DLLFx fx = (DLLFx)GetProcAddress(hGetProcIDDLL, fxName.c_str());

    if (!fx)
    {
        std::cout << "ERROR: Could not locate function \"" + fxName + "\" in DLL file \"" + libPath + "\"!\n";

        return false;
    }

    return true;
}

int main()
{
    std::string workDir = GetWorkingDir();
    bool result = LoadDLLFunction(workDir + "test.dll", "test_fx");

    if (result)
    {
        std::cout << "Result of loaded DLL function call: " << DLLFx() << "\n";
    }

    std::cout << "\n";

    system("pause");

    return 0;
}


Where I simplified the conversions of libPath and fxName, I'm now able to acquire a value that is not null from GetProcAddress. I also changed the name of the test DLL function to test_fx for distinction.

While I'm now supposedly able to "call" test_fx, it appears that it's not called correctly, as the test string doesn't get printed to the console, and the return value always appears to be 0.
I always get the following output, regardless of what the return value of test_fx is:

 
Result of loaded DLL function call: 0000000000000000


Now I shifted my attention to the typedef function pointer declaration at the top of my program:

 
typedef int __declspec(dllexport) (__stdcall* DLLFx)();


I wanted to make sure that this definition is correct. I see that if I try to remove the __stdcall* statement here, the GetProcAddress line is not compileable anymore.

Are there any other statements I should include in the definition of DLLFx?
The __stdcall calling convention is for Win32 API functions. Normal C/C++ functions use the __cdecl calling convention.

So, unless the function exported from your DLL explicitly uses the __stdcall calling convention – which it normally should not do (and, by default, does not do) – then the "main" application must not use the __stdcall calling convention either when calling that function!

Note that it is not necessary to explicitly specify __cdecl, because that is the default 😏

Also, you use __declspec(dllexport) inside the DLL code, in order to export a function from that DLL. But you use __declspec(dllimport) on the caller's side, in order to call a function from a DLL. The latter is optional, but makes calling the DLL function more efficient.

https://learn.microsoft.com/en-us/cpp/build/importing-into-an-application-using-declspec-dllimport?view=msvc-170
Last edited on
Thanks for your reply;

I think I finally figured it out.
For some reason, I only realized that in the final print statement inside the main function I call the TYPE DLLFx, and NOT an actual function of that type... Stupid oversight on my part, the following full code now produces the desired results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <string>
#include <windows.h>
#include <locale>
#include <codecvt>

std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> StringConverter;
typedef int __declspec(dllimport) (__stdcall* DLLFx)(void);
DLLFx FX;

std::string GetWorkingDir()
{
    TCHAR buffer[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, buffer, MAX_PATH);
    std::wstring::size_type pos = std::wstring(buffer).find_last_of(L"\\/");

    return StringConverter.to_bytes(std::wstring(buffer).substr(0, pos).c_str()) + "\\";
}

bool LoadDLLFunction(std::string libPath, std::string fxName)
{
    std::wstring libPathWide = StringConverter.from_bytes(libPath);

    HINSTANCE hGetProcIDDLL = LoadLibraryW(libPathWide.c_str());

    if (!hGetProcIDDLL)
    {
        std::cout << "ERROR: Could not locate DLL file\"" + libPath + "\"!\n";

        return false;
    }

    FX = (DLLFx)GetProcAddress(hGetProcIDDLL, fxName.c_str());

    if (!FX)
    {
        std::cout << "ERROR: Could not locate function \"" + fxName + "\" in DLL file \"" + libPath + "\"!\n";

        return false;
    }

    return true;
}

int main()
{
    std::string workDir = GetWorkingDir();
    bool result = LoadDLLFunction(workDir + "test.dll", "test_fx");

    if (result)
        std::cout << "Result of loaded DLL function call: " << FX() << "\n";

    std::cout << "\n";

    system("pause");

    return 0;
}


Alongside the following code compiled into test.dll:

1
2
3
4
5
6
7
8
#include <iostream>
#include <windows.h>

extern "C" __declspec(dllexport) int __stdcall test_fx(void)
{
    std::cout << "test\n";
    return 1;
}


Thanks for all the help!
Last edited on
Why use __stdcall ???

IMHO, there are only two reasons to use __stdcall calling convention: When you need to call an existing third-party DLL (e.g. Win32 API) that uses __stdcall calling convention; or if you need to build a DLL that will be called by an existing third-party App that is known to use __stdcall calling convention when calling into your DLL. If all is under your control, simply stick with __cdecl calling convention.

Note that it is not necessary to explicitly specify __cdecl, because that is the default 😏



Also, you should be using Unicode (wide) strings, i.e. std::wstring or wchar_t* (aka LPCWSTR), all the way, whenever you are dealing with file names or paths! And then, of course, you should be calling the "Unicode" (wide-string) version of LoadLibrary, i.e. LoadLibraryW().

The same holds true for any other file-related functions, e.g. GetModuleFileNameW() and so on.

That is because, on Windows, file names (paths) support the full Unicode character set, whereas an ANSI string, i.e. std::string or char*, can only represent characters that happen to exist in the local ANSI (8-Bit) Codepage. So, by using ANSI strings for file names or paths, your code is very likely to break as soon as some "non US-English" characters happen to appear in the path! Be aware that the "ANSI" (narrow-string) versions of the Win32 API functions only exist for backward-compatibility to "legacy" code. Don't use them in new code.

Converting to UTF-8 does not work, because the "ANSI" (narrow-string) versions of the Win32 API functions do not support UTF-8, but instead assume that the given char*-based string is encoded in the local ANSI (8-Bit) Codepage! Actually, that is not entirely true, because the latest builds of Windows 10/11 can enable experimental UTF-8 support for "ANSI" functions, but you can not rely on that!

IMHO, there is no excuse to not use wide-strings (wchar_t* or std::wstring) all the way.

A good test is to rename your DLL file to, e.g., Ölrückstoßabdämpfung-Григорий-Βαρουφάκης-서울특별시.dll and see if it still loads 😏



BTW, a proper GetBaseDirectory() function would look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/* search from MSB to LSB for the first set (1) bit */
#if defined(_WIN64)
#   pragma intrinsic(_BitScanReverse64)
#   define BITSCAN_REVERSE(X,Y) _BitScanReverse64((X),(Y))
#else
#   pragma intrinsic(_BitScanReverse)
#   define BITSCAN_REVERSE(X,Y) _BitScanReverse((X),(Y))
#endif

/* size of "size_t", in bits */
#define SIZE_BITS (sizeof(size_t) * 8U)

/* maximum length of a path, on modern Windows, including the terminating NULL char */
#define PATH_LIMIT ((size_t)32768U)

static std::wstring ToString(std::vector<WCHAR>& buffer, const size_t len = SIZE_MAX)
{
    if (len != SIZE_MAX)
    {
        return std::wstring(buffer.data(), len);
    }
    buffer[buffer.size() - 1U] = L'\0';
    return std::wstring(buffer.data());
}

static size_t NextPowerOf2(const size_t value)
{
    unsigned long index;
    if (BITSCAN_REVERSE(&index, value))
    {
        return (index < (SIZE_BITS - 1U)) ? (((size_t)1U) << (index + 1U)) : SIZE_MAX;
    }
    return 1U;
}

template<typename T>
static void IncreaseBufferSize(std::vector<T> &buffer, const size_t limit, const T value)
{
    const size_t currentSize = buffer.size();
    buffer.resize(std::min(NextPowerOf2(currentSize), limit), value);
}

std::wstring GetExecutableFileName(void)
{
    // note: The module path can actually be longer than MAX_PATH characters!
    std::vector<WCHAR> executablePath(MAX_PATH, L'\0');
    for (;;)
    {
        const DWORD len = GetModuleFileNameW(NULL, executablePath.data(), (DWORD)executablePath.size());
        if ((len > 0U) && (len < executablePath.size()))
        {
            return ToString(executablePath, len);
        }
        else if ((len == executablePath.size()) && (executablePath.size() < PATH_LIMIT))
        {
            IncreaseBufferSize(executablePath, PATH_LIMIT, L'\0');
        }
        else
        {
            return std::wstring(); /* GetModuleFileNameW() has failed */
        }
    }
}

std::wstring GetDirectoryPart(const std::wstring &fullPath)
{
    // note: The "directory" part can actually be longer than _MAX_DIR characters!
    std::vector<WCHAR> drive(_MAX_DRIVE, L'\0'), directory(_MAX_DIR, L'\0');
    for (;;)
    {
        switch (_wsplitpath_s(fullPath.c_str(),
            drive.data(), drive.size(), directory.data(), directory.size(), NULL, 0U, NULL, 0U))
        {
        case 0:
            return ToString(drive) + ToString(directory);
        case ERANGE:
            if (directory.size() < PATH_LIMIT)
            {
                IncreaseBufferSize(directory, PATH_LIMIT, L'\0');
                continue;
            }
        default:
            return std::wstring();
        }
    }
}

std::wstring GetBaseDirectory(void)
{
    const std::wstring executablePath = GetExecutableFileName();
    if (!executablePath.empty())
    {
        return GetDirectoryPart(executablePath);
    }
    return std::wstring();
}
Last edited on
The difference between _cdecl and _stdcall is going to determine which function cleans up the stack: With _stdcall the callee cleans up the parameters on the stack. With _cdecl, the caller cleans up the parameters on the stack.

https://devblogs.microsoft.com/oldnewthing/20040108-00/?p=41163
https://stackoverflow.com/questions/3404372/stdcall-and-cdecl
https://www.codeproject.com/Articles/1388/Calling-Conventions-Demystified
Topic archived. No new replies allowed.