如何使用函数的参数作为 C++ 中的指针从 DLL 获取 return 值?
How to return value from DLL using parameter of function as a pointer in C++?
我有一个简单的 DLL:
dllmain.cpp:
#define MYDLLDIR
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
...
}
void callByPtr(int *i) {
(*i)++;
}
pch.h
#include "framework.h"
#ifdef MYDLLDIR
#define DLLDIR __declspec(dllexport) __stdcall
#else
#define DLLDIR __declspec(dllimport)
#endif
extern "C" {
DLLDIR void callByPtr(int *i);
};
客户:
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 ----\n";
std::cout << "i " << i << std::endl;
std::cout << "ptri " << ptri << std::endl;
}
}
结果:
----调用前----
我=10
ptri = 0025FB40
---- 通话后----
我=11286192
ptri = 0025FB3C
ptri 的地址已更改,值不是 11。
如何正确实现这一点,以便我可以使用上述方法从 DLL 中获取值?
谢谢!
您的导出定义也不正确。应该是这样的:
#ifdef MYDLL_EXPORT
#define MYDLLDIR __declspec(dllexport)
#else
#define MYDLLDIR __declspec(dllimport)
#endif
并对导出(dll,#MYDLL_EXPORT 已定义)和导入(客户端,#MYDLL_EXPORT 未定义)使用相同的宏 (MYDLLDIR)
在你的情况下,你必须在所有地方对 callByPtr 使用相同的调用约定
__stdcall(默认为__cstdcall)。
在你的 pch.h 然后:
MYDLLDIR void __stdcall callByPtr(int *i);
因为你 return 在你的导出函数中无效 DLLDIR void callByPtr(int *i);你应该使用 C 和 C++ 程序的默认调用约定 __cdecl.
更改后:
- 在您的 pch.h 文件中:
#define DLLDIR __declspec(dllexport) __stdcall
至
#define DLLDIR __declspec(dllexport)
- 在您的客户端文件中:
typedef void(__stdcall* callByPtr)(int*);
至
typedef void(__cdecl* callByPtr)(int*);
重建没有错误和警告,输出如下:
我 10
ptri 0113FCA4
---- 通话后----
我 11
ptri 0113FCA4
Syntax
return-type __stdcall
function-name[( argument-list )]
调用约定说明符在函数return类型之后。你定义它的方式是before,所以(可能)编译器忽略了它???,最终在 .dll 中将函数导出为 __cdecl(默认),当 .exe 称之为 __stdcall, Bang! -> Stack Corruption,你认为你的指针实际上是完全不同的东西,因此你的输出很奇怪。
有趣的是,在我这边,编译器 (VS2017) 在我尝试构建 .dll 使用你的表单 (#define DLL00_EXPORT_API __declspec(dllexport) __stdcall
).
下面是一个有效的示例(我修改了文件名和内容)。
dll00.h:
#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)
}
#endif
dll00.cpp:
#define DLL00_EXPORTS
#include "dll00.h"
void CALL_CONV callByPtr(int *pI) {
if (pI) {
(*pI)++;
}
}
main00.cpp:
#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 ----\n";
std::cout << "i " << i << std::endl;
std::cout << "ptri " << ptri << std::endl;
CloseHandle(hDLL);
return 0;
}
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q063951075]> sopr.bat
*** Set shorter prompt to better fit when pasted in Whosebug (or other) pages ***
[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity17\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
备注:
显然,提供的代码不是生成输出的代码:
- 它不编译:
- 我在开头提到的错误(尽管使用一些较旧的编译器(或(怀疑?)编译器标志)可能可以避免)
- 缺少 #includes 和 usings 在你的 .exe 代码
“次要”代码问题:
- NULL 取消引用前的指针测试
- 关闭句柄
- DLLDIR 定义之间的差异(@Petr_Dokoupil 也提到):
__declspec(dllexport) __stdcall
vs. __declspec(dllimport)
,但由于您是动态加载 .dll 而不是链接到它, 它与错误无关
为什么要用__stdcall? (评论中提供的答案:“与其他语言兼容”):
它只对32位有影响(在64位它被忽略)
它引入了很多额外的问题(其中一些你甚至没有经历过),比如函数名重整(检查 dumpbin 输出),只能使用 .def 文件
来避免
总而言之,这似乎是一个 XY 问题。 您应该使用默认值(完全摆脱 __stdcall):
- 使用此版本的代码,在构建.dll 和 .exe
- 你不太可能 运行 陷入(微妙的)问题(至少在你获得更多这方面的经验之前)
- 删除所有与调用约定相关的代码,会使它更短更清晰
所有列出的语言 (Delphi, Python, C#, ...) 支持 __cdecl(毕竟,我认为大多数机器代码 运行 都在那里,还是写成C)
有关整个区域的更多详细信息,您可以查看(包括(递归)引用的 URLs):
[SO]: Python Ctypes - loading dll throws OSError: [WinError 193] %1 is not a valid Win32 application (@CristiFati's answer)
[SO]: When using fstream in a library I get linker errors in the executable (@CristiFati's answer)
我有一个简单的 DLL:
dllmain.cpp:
#define MYDLLDIR
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
...
}
void callByPtr(int *i) {
(*i)++;
}
pch.h
#include "framework.h"
#ifdef MYDLLDIR
#define DLLDIR __declspec(dllexport) __stdcall
#else
#define DLLDIR __declspec(dllimport)
#endif
extern "C" {
DLLDIR void callByPtr(int *i);
};
客户:
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 ----\n";
std::cout << "i " << i << std::endl;
std::cout << "ptri " << ptri << std::endl;
}
}
结果:
----调用前----
我=10
ptri = 0025FB40
---- 通话后----
我=11286192
ptri = 0025FB3C
ptri 的地址已更改,值不是 11。
如何正确实现这一点,以便我可以使用上述方法从 DLL 中获取值?
谢谢!
您的导出定义也不正确。应该是这样的:
#ifdef MYDLL_EXPORT
#define MYDLLDIR __declspec(dllexport)
#else
#define MYDLLDIR __declspec(dllimport)
#endif
并对导出(dll,#MYDLL_EXPORT 已定义)和导入(客户端,#MYDLL_EXPORT 未定义)使用相同的宏 (MYDLLDIR)
在你的情况下,你必须在所有地方对 callByPtr 使用相同的调用约定 __stdcall(默认为__cstdcall)。
在你的 pch.h 然后:
MYDLLDIR void __stdcall callByPtr(int *i);
因为你 return 在你的导出函数中无效 DLLDIR void callByPtr(int *i);你应该使用 C 和 C++ 程序的默认调用约定 __cdecl.
更改后:
- 在您的 pch.h 文件中:
#define DLLDIR __declspec(dllexport) __stdcall
至
#define DLLDIR __declspec(dllexport) - 在您的客户端文件中:
typedef void(__stdcall* callByPtr)(int*);
至
typedef void(__cdecl* callByPtr)(int*);
重建没有错误和警告,输出如下:
我 10
ptri 0113FCA4
---- 通话后----
我 11
ptri 0113FCA4
Syntax
return-type
__stdcall
function-name[( argument-list )]
调用约定说明符在函数return类型之后。你定义它的方式是before,所以(可能)编译器忽略了它???,最终在 .dll 中将函数导出为 __cdecl(默认),当 .exe 称之为 __stdcall, Bang! -> Stack Corruption,你认为你的指针实际上是完全不同的东西,因此你的输出很奇怪。
有趣的是,在我这边,编译器 (VS2017) 在我尝试构建 .dll 使用你的表单 (#define DLL00_EXPORT_API __declspec(dllexport) __stdcall
).
下面是一个有效的示例(我修改了文件名和内容)。
dll00.h:
#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)
}
#endif
dll00.cpp:
#define DLL00_EXPORTS
#include "dll00.h"
void CALL_CONV callByPtr(int *pI) {
if (pI) {
(*pI)++;
}
}
main00.cpp:
#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 ----\n";
std::cout << "i " << i << std::endl;
std::cout << "ptri " << ptri << std::endl;
CloseHandle(hDLL);
return 0;
}
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q063951075]> sopr.bat *** Set shorter prompt to better fit when pasted in Whosebug (or other) pages *** [prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity17\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
备注:
显然,提供的代码不是生成输出的代码:
- 它不编译:
- 我在开头提到的错误(尽管使用一些较旧的编译器(或(怀疑?)编译器标志)可能可以避免)
- 缺少 #includes 和 usings 在你的 .exe 代码
- 它不编译:
“次要”代码问题:
- NULL 取消引用前的指针测试
- 关闭句柄
- DLLDIR 定义之间的差异(@Petr_Dokoupil 也提到):
__declspec(dllexport) __stdcall
vs.__declspec(dllimport)
,但由于您是动态加载 .dll 而不是链接到它, 它与错误无关
为什么要用__stdcall? (评论中提供的答案:“与其他语言兼容”):
它只对32位有影响(在64位它被忽略)
它引入了很多额外的问题(其中一些你甚至没有经历过),比如函数名重整(检查 dumpbin 输出),只能使用 .def 文件
来避免总而言之,这似乎是一个 XY 问题。 您应该使用默认值(完全摆脱 __stdcall):
- 使用此版本的代码,在构建.dll 和 .exe
- 你不太可能 运行 陷入(微妙的)问题(至少在你获得更多这方面的经验之前)
- 删除所有与调用约定相关的代码,会使它更短更清晰
所有列出的语言 (Delphi, Python, C#, ...) 支持 __cdecl(毕竟,我认为大多数机器代码 运行 都在那里,还是写成C)
有关整个区域的更多详细信息,您可以查看(包括(递归)引用的 URLs):
[SO]: Python Ctypes - loading dll throws OSError: [WinError 193] %1 is not a valid Win32 application (@CristiFati's answer)
[SO]: When using fstream in a library I get linker errors in the executable (@CristiFati's answer)