How to return value from DLL using parameter of function as a pointer in C++?
我有一个简单的DLL:
dllmain.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #define MYDLLDIR #include"pch.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { ... } void callByPtr(int *i) { (*i)++; } |
pch.h
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include"framework.h" #ifdef MYDLLDIR #define DLLDIR __declspec(dllexport) __stdcall #else #define DLLDIR __declspec(dllimport) #endif extern"C" { DLLDIR void callByPtr(int *i); }; |
客户:
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 | typedef void(__stdcall* callByPtr)(int*); int main() { HINSTANCE hDLL; hDLL = LoadLibrary(_T("MyDll.dll")); if (NULL != hDLL) { callByPtr myCall = (callByPtr)GetProcAddress(hDLL,"callByPtr"); if (!myCall) { return EXIT_FAILURE; } int i = 10; int* ptri = &i; std::cout <<"i" << i << std::endl; std::cout <<"ptri" << ptri << std::endl; myCall(ptri); std::cout <<"---- After Call ----\ "; std::cout <<"i" << i << std::endl; std::cout <<"ptri" << ptri << std::endl; } } |
结果:
----通话前----
i = 10
ptri = 0025FB40
----通话后-----
i = 11286192
ptri = 0025FB3C
ptri的地址已更改,值不是11。
如何正确实现此功能,以便可以使用上述方法从DLL获取值?
谢谢!
根据[MS.Docs]:__stdcall
Syntax
return-type
__stdcall function-name[( argument-list )]blockquote>
调用约定说明符位于函数返回类型之后。定义它的方式是以前的,所以(可能)编译器忽略了它????,最终出现在.dll中,将该函数导出为__cdecl(默认值),而当.exe将该函数称为__stdcall时,砰! ->堆栈损坏,并且您认为指针实际上是完全不同的,因此输出很奇怪。有趣的是,最后,当我尝试使用您的表单(
#define DLL00_EXPORT_API __declspec(dllexport) __stdcall )构建.dll时,编译器(VS2017)吐出error C2062: type 'void' unexpected 。下面是一个工作示例(我修改了文件名和内容)。
dll00.h:
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 #pragma once
#if defined(_WIN32)
# if defined(DLL00_EXPORTS)
# define DLL00_EXPORT_API __declspec(dllexport)
# else
# define DLL00_EXPORT_API __declspec(dllimport)
# endif
#else
# define DLL00_EXPORT_API
#endif
#if defined(CALL_CONV_STDCALL)
# define CALL_CONV __stdcall
#else
# define CALL_CONV
#endif
#if defined(__cplusplus)
extern"C" {
#endif
DLL00_EXPORT_API void CALL_CONV callByPtr(int *pI);
#if defined(__cplusplus)
}
#endifdll00.cpp:
1
2
3
4
5
6
7
8
9 #define DLL00_EXPORTS
#include"dll00.h"
void CALL_CONV callByPtr(int *pI) {
if (pI) {
(*pI)++;
}
}main00.cpp:
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 #include <iostream>
#include <Windows.h>
#include"dll00.h"
#if defined(CALL_CONV_STDCALL)
# define FUNC_NAME"_callByPtr@4"
#else
# define FUNC_NAME"callByPtr"
#endif
using std::cout;
using std::endl;
typedef void(CALL_CONV *CallByPtrFunc)(int*);
int main() {
HMODULE hDLL;
hDLL = LoadLibrary("dll00.dll");
if (!hDLL) {
std::cout <<"LoadLibrary failed" << std::endl;
return -1;
}
CallByPtrFunc callByPtr = (CallByPtrFunc)GetProcAddress(hDLL, FUNC_NAME);
if (!callByPtr) {
std::cout <<"GetProcAddress failed" << std::endl;
CloseHandle(hDLL);
return EXIT_FAILURE;
}
int i = 10;
int *ptri = &i;
std::cout <<"i" << i << std::endl;
std::cout <<"ptri" << ptri << std::endl;
callByPtr(ptri);
std::cout <<"---- After Call ----\
";
std::cout <<"i" << i << std::endl;
std::cout <<"ptri" << ptri << std::endl;
CloseHandle(hDLL);
return 0;
}输出:
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 [cfati@CFATI-5510-0:e:\\Work\\Dev\\StackOverflow\\q063951075]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
[prompt]>"c:\\Install\\pc032\\Microsoft\\VisualStudioCommunity\\2017\\VC\\Auxiliary\\Build\\vcvarsall.bat" x86
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.27
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x86'
[prompt]>
[prompt]> dir /b
dll00.cpp
dll00.h
main00.cpp
[prompt]> :: Build the .dll (passing /DCALL_CONV_STDCALL)
[prompt]> cl /nologo /MD /DDLL /DCALL_CONV_STDCALL dll00.cpp /link /NOLOGO /DLL /OUT:dll00.dll
dll00.cpp
Creating library dll00.lib and object dll00.exp
[prompt]>
[prompt]> :: Build the .exe (also passing /DCALL_CONV_STDCALL)
[prompt]> cl /nologo /MD /W0 /EHsc /DCALL_CONV_STDCALL main00.cpp /link /NOLOGO /OUT:main00.exe
main00.cpp
[prompt]>
[prompt]> dir /b
dll00.cpp
dll00.dll
dll00.exp
dll00.h
dll00.lib
dll00.obj
main00.cpp
main00.exe
main00.obj
[prompt]>
[prompt]> main00.exe
i 10
ptri 00F5FCC8
---- After Call ----
i 11
ptri 00F5FCC8
[prompt]> :: It worked !!!
[prompt]>
[prompt]> dumpbin /EXPORTS dll00.dll
Microsoft (R) COFF/PE Dumper Version 14.16.27043.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file dll00.dll
File Type: DLL
Section contains the following exports for dll00.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 00001000 _callByPtr@4
Summary
1000 .data
1000 .rdata
1000 .reloc
1000 .text注意事项:
很显然,所提供的代码不是生成您的输出的代码:
- 它不会编译:
- 我在开始时提到的错误(尽管使用某些较旧的编译器(或(怀疑地是?)编译器标志可以避免),但是可以避免该错误)
- .exe代码中缺少#includes和usings
"次要"代码问题:
- 取消引用前的NULL指针测试
- 关闭手柄
- DLLDIR定义之间的区别(也由@Petr_Dokoupil提到):
__declspec(dllexport) __stdcall 与__declspec(dllimport) ,但是由于您正在动态加载.dll而不是链接到它,因此它与错误无关为什么需要使用__stdcall? (注释中提供的答案:"与其他语言兼容"):
仅在32位上有意义(在64位上它会被忽略)
它引入了许多其他问题(其中一些您甚至没有遇到过),例如函数名称处理(检查dumpbin输出),只能使用.def文件来避免。
总而言之,这似乎是一个XY问题。您应该使用默认值(完全摆脱__stdcall):
- 使用此版本的代码,只需在构建.dll和.exe时不将/ DCALL_CONV_STDCALL参数传递给编译器
- 您不太可能遇到(细微)问题(至少直到您在该领域获得更多经验之前)
- 删除所有与调用约定相关的代码,将使其更短,更整洁
所有列出的语言(Delphi,Python,C#等)都支持__cdecl(毕竟,我认为那里用完的大多数机器代码仍然是用C编写的)。
有关整个区域的更多详细信息,您可以检查(包括(递归)引用的URL):
[SO]:Python Ctypes-加载dll引发OSError:[WinError 193]%1不是有效的Win32应用程序(@CristiFati的回答)
[SO]:在库中使用fstream时,我在可执行文件中收到链接器错误(@CristiFati的回答)
因为您在导出的函数DLLDIR中返回了void void callByPtr(int * i);您应该对C和C程序__cdecl使用默认的调用约定。
更改后:在您的pch.h文件中:
#定义DLLDIR __declspec(dllexport)__stdcall
至
#定义DLLDIR __declspec(dllexport)在您的客户档案中:
typedef void(__ stdcall * callByPtr)(int *);
至
typedef void(__ cdecl * callByPtr)(int *);重建没有错误和警告,并且输出是这样的:
我10
ptri 0113FCA4
----通话后----
我11
ptri 0113FCA4
您的导出定义也不正确。应该是这样的:
1
2
3
4
5 #ifdef MYDLL_EXPORT
#define MYDLLDIR __declspec(dllexport)
#else
#define MYDLLDIR __declspec(dllimport)
#endif,并对导出(dll,定义了#MYDLL_EXPORT)和导入(客户端,未定义#MYDLL_EXPORT)使用相同的宏(MYDLLDIR)
在所有情况下,您必须在所有地方为callByPtr使用相同的调用约定
__stdcall(默认值是__cstdcall)。然后在您的pch.h中:
1 MYDLLDIR void __stdcall callByPtr(int *i);