Loading a declspec exported dll func in C++

Hey guys so I have been struggling with this for just about over 6 hours now. I am totally new to C++ and really trying to get started with this project. (Been watching jamie king on youtube, very in depth stuff).

Alas, I have come so far and yet I don't feel the last 3 hours has gotten me _any_ closer to a solution.

As the title suggest, I have built a Win32 DLL using VS2015 DLL MiniWizard.

Here is the code to that (I am not using DllMain at all, so it is default):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// cb_ff_dbi.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include <string>
using std::string;

extern "C" 
{ 
	__declspec (dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function); 
} 
void __stdcall RVExtension(char *output, int outputSize, const char *function) 
{ 
	strncpy_s(output, outputSize, "IT WORKS!", _TRUNCATE); 
}


This file is supposed to use that RVExtension function. (Signature of original function export _cannot_ change btw so please don't suggest it, maybe i got the type def wrong).
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
// Win32Project1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
#include <iostream>
#include <vector>
#include <string>
#include <functional>
using std::string;

// DLL Func Signature (Perhaps this is the culprit? I have tried various concoctions already)
typedef VOID(WINAPI*RVExtensionType)(char *output, int outputSize, const char *function);

int main()
{
	// Vars to be passed to DLL Func
	string outputArma;
	string functionArma = "test request";

	// Convert string to char *
	std::vector<char> writable(outputArma.begin(), outputArma.end());
	writable.push_back('\0');
	char *output = &writable[0];	

	int outputSize = 4096;
	const char *function = functionArma.c_str(); // convert string to const char*

	std::cout << "Begin" << std::endl;

	// ==============================

	// Attempt load inline
	HINSTANCE dllHandle = LoadLibrary(L"cb_ff_dbi.dll");
	RVExtensionType RVExtension = (RVExtensionType)GetProcAddress(dllHandle, "RVExtension");

	if (!RVExtension) {
		std::cerr << "Could not locate the function \"" << "RVExtension" << "\" in DLL\"" << std::endl;
	}
	else {
		RVExtension(output, outputSize, function);
		printf("Result: ", output, "\n");
	}

	std::cin.get();

	return 0;
}


All I get is: Could not locate the function "RVExtension" in DLL

I have gone over about 100 different pages looking through very complicated answers relating to explicit and implicit DLL importing and different methods of exporting functions. The fucntion MUST be exported this way (I know this works, the program I am building the DLL for uses this method and works flawlessly). I am yet to find a solution to this problem.

== SUMMARY ==
DLL builds fine.
DLL Loads fine.
Main cpp builds fine.
getProcAddress fails to find the RVExtension method.
depends.exe (MS) shows name of exported method as _RVExtension@12
Cannot change method of export or export signature.

I just want this to work :(. I am losing hair so fast! Thanks for any help you provide.
Please remember I am very new to C++ (though I doubt this is beginner stuff) - please explain any code you give in as much detail as possible.

Thanks!
Last edited on
If you can't change method of export or export signatur you have to use:
 
RVExtensionType RVExtension = (RVExtensionType)GetProcAddress (dllHandle, "_RVExtension@12");


which works on VS2013 Community.

However in the line printf("Result: ", output, "\n"); it triggers some Heap corruption error.
Hi thanks for heap notice ill switch it out once i fifure out why its causing heap errors.

I cannot change the dll at all really. I did try to call it using the mangled name before but didnt seem yo work either. Ill try it in vs2013 see if it helps.

Ill post back after i try it later after work.

Meanwhile if anyone can point me in a direction where i only need to use the actual func name eather than the mangled name would be awesome too although if the above works ill mark this as solved. Thanks.
1
2
3
4
5
6
7
8
9
10
#include <string>
using std::string;

extern "C" __declspec (dllexport) void RVExtension (char *output, int outputSize, const char *function);


void RVExtension (char *output, int outputSize, const char *function)
{
  strncpy_s (output, outputSize, "IT WORKS!", _TRUNCATE);
}


I am not sure if the __stdcall is necessary but without it the name doesn't get mangled.
I figurred I needed some practise at this, hadn't fooled with it for awhile, so I took a shot at it. I did all command line compiling with VC++ 9 from Visual Studio 2008. Did x86. Also I changed the code a lot to make it simpler, but I didn't alter your function signatures at all. Got it to work with implicit linking. Here is my dll, which I named the source dllMain.cpp

1
2
3
4
5
6
7
8
9
10
// dllMain.cpp
// cl dllMain.cpp /MT /O1 /Os /FeRVExtension.dll /LD
#include <string.h>
extern "C" __declspec(dllexport) void __stdcall RVExtension(char* output, int outputSize, const char* function); 

void __stdcall RVExtension(char* output, int outputSize, const char* function)
{
 strncpy(output, "IT WORKS!", outputSize); 
}
// End dllMain.cpp 


And here is the host, i.e., Host.cpp...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Host.cpp
// cl Host.cpp RVExtension.lib /MT /O1 /Os /FeHost.exe
#include <cstdio> 
#include <memory.h>                                           
extern "C" __declspec(dllimport) void __stdcall RVExtension(char* output, int outputSize, const char* function);

int main(void)
{
 char szBuffer[128];                           // create/acquire memory buffer

 memset(szBuffer,0,128);                       // zero out buffer
 RVExtension(szBuffer, 9, "Hello, World!");    // call imported function
 printf("szBuffer = %s\n", szBuffer);          // output szBuffer
 getchar();

 return 0;
}


Here is my full command line compilation screen showing successful execution at the end...

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
C:\Code\VStudio\VC++9\dllTests\dllTest2>dir
 Volume in drive C is Main
 Volume Serial Number is 60AD-E30B

 Directory of C:\Code\VStudio\VC++9\dllTests\dllTest2

12/14/2015  04:16 PM    <DIR>          .
12/14/2015  04:16 PM    <DIR>          ..
12/14/2015  04:02 PM               325 dllMain.cpp
12/14/2015  04:15 PM               608 Host.cpp
               2 File(s)            933 bytes
               2 Dir(s)  16,689,197,056 bytes free

C:\Code\VStudio\VC++9\dllTests\dllTest2>cl dllMain.cpp /MT /O1 /Os /FeRVExtension.dll /LD
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

dllMain.cpp
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:RVExtension.dll
/dll
/implib:RVExtension.lib
dllMain.obj
   Creating library RVExtension.lib and object RVExtension.exp

C:\Code\VStudio\VC++9\dllTests\dllTest2>dir
 Volume in drive C is Main
 Volume Serial Number is 60AD-E30B

 Directory of C:\Code\VStudio\VC++9\dllTests\dllTest2

12/14/2015  04:18 PM    <DIR>          .
12/14/2015  04:18 PM    <DIR>          ..
12/14/2015  04:02 PM               325 dllMain.cpp
12/14/2015  04:18 PM               829 dllMain.obj
12/14/2015  04:15 PM               608 Host.cpp
12/14/2015  04:18 PM            41,472 RVExtension.dll
12/14/2015  04:18 PM               626 RVExtension.exp
12/14/2015  04:18 PM             1,776 RVExtension.lib
               6 File(s)         45,636 bytes
               2 Dir(s)  16,688,873,472 bytes free

C:\Code\VStudio\VC++9\dllTests\dllTest2>cl Host.cpp RVExtension.lib /MT /O1 /Os /FeHost.exe
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

Host.cpp
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:Host.exe
Host.obj
RVExtension.lib

C:\Code\VStudio\VC++9\dllTests\dllTest2>dir
 Volume in drive C is Main
 Volume Serial Number is 60AD-E30B

 Directory of C:\Code\VStudio\VC++9\dllTests\dllTest2

12/14/2015  04:18 PM    <DIR>          .
12/14/2015  04:18 PM    <DIR>          ..
12/14/2015  04:02 PM               325 dllMain.cpp
12/14/2015  04:18 PM               829 dllMain.obj
12/14/2015  04:18 PM               608 Host.cpp
12/14/2015  04:18 PM            50,688 Host.exe
12/14/2015  04:18 PM             1,168 Host.obj
12/14/2015  04:18 PM            41,472 RVExtension.dll
12/14/2015  04:18 PM               626 RVExtension.exp
12/14/2015  04:18 PM             1,776 RVExtension.lib
               8 File(s)         97,492 bytes
               2 Dir(s)  16,688,816,128 bytes free

C:\Code\VStudio\VC++9\dllTests\dllTest2>Host.exe
szBuffer = IT WORKS!
A lot of other languages use __stdcall. Of course, the Win32 Api is all __stdcall, far as I know, as are COM components.
Thanks everyone for all your help!

So, after reviewing my code I now understand I know nothing about how to properly convert an empty string to a char pointer ha. So I just made the char pointer explicitly (char*t=new char[4096]). This solved the heap corruption error :).

Thomas nailed the solution in one with the use of the mangled name. (I thought the name could change on different builds but I guess it's directly related to the signatures arguments). This solved my problem and was able to load and execute successfully!

freddie, oh wow man that code looks sweetly efficient. No hinstance or getProcAddress? Or am I missing something? I must be since I can't get that code to compile (vs2015 pro).
I guess it is a stripped down version showing the essentials but I am quite new to this and still learning some basics :).

Feel free to fill me in on how to compile your code when the dll and host codes are separate projects, a few thing to be honest I don't understand there;
1. I don't see any explicit pointers used, I guess passing char as an argument assumes your passing a pointer?
2. The outputSize value passed as 9, I guess you assumed it would be variant depending on the returned data but even so surely it should be 10 to accommodate the char end byte \0 ?
3. How does Hello, World! string implicitly convert to a const char*? Is const assumed as it is an rvalue? and does the compiler assume you want to pass a pointer?

Thinking of it I guess they would be assumed pointers (I come from PHP background btw which would not assume a pointer but rather create a seperate writeable variable).

Thanks to the both of you for the help!

Marking as SOLVED.

No hinstance or getProcAddress?


Well, using an import lib is easier, I think. Let the linker do the work. It should work with Load Library()/GetProcAddress() though. I work back and forth between PowerBASIC and C++ a lot, and if I create a dll with PowerBASIC I need to do the explicit linking because I don't have the lib file created through the compilation of the dll by the other language.

Do you want me to post a LoadLibrary()/GetProcAddress() version?


Feel free to fill me in on how to compile your code when the dll and host codes are separate projects, a few thing to be honest I don't understand there;


I figurred I could do it quicker with command line compiling than fighting with Visual Studio ad infinitum with 87 levels of directories created all over the place with different dozens of files scattered randomly through them. I'm a minimalist by nature.


1. I don't see any explicit pointers used, I guess passing char as an argument assumes your passing a pointer?


In C or C++ this...

char szBuffer[128];

...creates a memory buffer of 128 bytes. And syntactilly, this...

szBuffer

...resolves to a pointer to the base address, and is equivalent to this...

&szBuffer[0]

(address of 1st byte of buffer).


2. The outputSize value passed as 9, I guess you assumed it would be variant depending on the returned data but even so surely it should be 10 to accommodate the char end byte \0 ?


strncpy() is one of those funny functions that's difficult to use. I always have to read up on it to get it right, and still usually mess it up. Sometimes it appends a null byte and sometimes not - I think. But I used memset() to zero out the whole szBuffer buffer, so there was bound to be a null out there somewhere to terminate it.


3. How does Hello, World! string implicitly convert to a const char*? Is const assumed as it is an rvalue? and does the compiler assume you want to pass a pointer?


Not sure how to answer that. I just wanted to fill up that parameter position with something cuz I simplified the _s thing away. I'm not a very secure person and I have lots of other faults too.

But "Hello, World" is a string literal that the compiler will store in static program memory in the data segment somewhere, and stick a pointer to that in the function call. I figurred it would work.

The whole thing comes to around 90 to 100 k. I did static linking against msvcrt.dll with the /MT compiler switch. So to the best of my knowledge the binaries wouldn't ever need any redistributables to run on any x86 Windows system. I'm pretty sure I could make both thoses binaries in under 25 k total with PowerBASIC, which compiles much smaller than C++. About like bare bones C.
VS 2008 is the last version I have. I get a new one every ten years, whether I need it or not. Before that I had VC6 circa 1998 or so. So I'll be due for another in 2018. But with some of the versions you have to specify somewhere in the IDE that the project is a dependency of the other. I'm sure it changes from version to version. I hate fooling with stuff like that. When I do dlls I tend to do the command line compiling thing. Might seem slower and like a lot of work but in the end I think I'm ahead! Everything's in the same directory and I don't have a gigabyte of files scattered all over the place. Visual Studio 2008 has a command prompt that sets up the build environment. All I have to do is use that, and navigate with the CD command to my directory where I have the stuff.
Rereading your reply where you said you were a bit confused about setting up the dll projects, it occurred to me you might be confused about some of the files created by the compilation of the dll and how they are used by the host. Here again is what was in my single directory after creation of the dll but before compilation of the host...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
C:\Code\VStudio\VC++9\dllTests\dllTest2>dir
 Volume in drive C is Main
 Volume Serial Number is 60AD-E30B

 Directory of C:\Code\VStudio\VC++9\dllTests\dllTest2

12/14/2015  04:18 PM    <DIR>          .
12/14/2015  04:18 PM    <DIR>          ..
12/14/2015  04:02 PM               325 dllMain.cpp
12/14/2015  04:18 PM               829 dllMain.obj
12/14/2015  04:15 PM               608 Host.cpp
12/14/2015  04:18 PM            41,472 RVExtension.dll
12/14/2015  04:18 PM               626 RVExtension.exp
12/14/2015  04:18 PM             1,776 RVExtension.lib
               6 File(s)         45,636 bytes
               2 Dir(s)  16,688,873,472 bytes free


Note when the dll was created, these files also showed up...

RVExtension.lib
RVExtension.exp

To do implicit linking against the function exported from the dll you need to tell the host project to use those files. Particularly RVExtension.lib. Note my command line compilation string for the host was this...

cl Host.cpp RVExtension.lib /MT /O1 /Os /FeHost.exe

If you are doing that through the IDE you need to go under 'Project Properties' in the IDE, then to 'Linker Settings' - possibly 'Command Line', and tell the IDE to use that *.lib for externals it encounters.
Thanks for the explanation guys learned a lot so far. The host app doesnt know anything about the dll, eg it could be compiled in any language that supports stdcall standard. I am under the impression that I cannot use the generated lib as it is speific to the compilation i guess.

Thanks again guys xD.

Now i just need to nail down when and why to use free/delete to clear orphaned mem blocks. Want to do it inline ofc so i learn. None of these smart pointers...yet... xD

I am under the impression that I cannot use the generated lib as it is speific to the compilation i guess.


Then you will have to do the LoadLibrary() / GetProcAddress() thing - explicit linking is the term I think. But that whole issue depends on what language you are using to access the dll. In the PowerBASIC programming language specifically, one uses 'Declare' statements to inform the language that a function in an external dll is going to be accessed. In that case the Declare statement will specify the calling convention, e.g., stdcall, cdecl, etc., the external dll name and path if needed, the function parameters in terms of the host language, and possibly a few other things. But any language that is capable of dealing with function pointers would be able to do the LoadLibrary() / GetProcAddress() thing too, and call through the address.

In terms of releasing memory, its conceptually fairly easy. Any resources you allocate yourself with malloc() or new need to be released. I didn't need to release any memory in the code I posted because my declaration of...

char szBuffer[128];

...caused the compiler to allocate that array of 128 bytes 'on the stack', so to speak, and the compiler would have generated code to automatically deallocate it after function termination. Its easier to do that than allocate a memory block then have to release it yourself. Of course, if you have no idea how much memory the program will need, then you need to allocate/deallocate yourself.
Topic archived. No new replies allowed.