问题描述
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;
}
}
结果:
----通话前----
i = 10
ptri = 0025FB40
----通话后----
i = 11286192
ptri = 0025FB3C
ptri的地址已更改,值不是11。
谢谢!
解决方法
您的导出定义也不正确。应该是这样的:
#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);
,
因为您在导出的函数DLLDIR中返回了void 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
语法
返回类型
__stdcall
函数名称 [(参数列表)]
调用约定说明符在函数返回类型之后之后。定义的方式是之前,因此(可能)编译器会忽略它 ??? ,最终出现在 .dll中将函数导出为 __ cdecl (默认),然后当 .exe 将其命名为 __ stdcall 时,砰! -> 堆栈损坏 ,您认为指针实际上是完全不同的,因此输出很奇怪。
有趣的是,最后,当我尝试构建 .dll 时,编译器( VS2017 )吐出error C2062: type 'void' unexpected
。 >使用您的表单(#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\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 代码中缺少 #include 和 使用
- 它不能编译:
-
“次要”代码问题:
-
取消引用之前
- NULL 指针测试
- CloseHandle
- DLLDIR 定义之间的差异(也由@Petr_Dokoupil提到):
__declspec(dllexport) __stdcall
vs。__declspec(dllimport)
,但由于您正在动态加载 .dll 而不是链接到它,它与错误无关
-
为什么需要使用 __ stdcall ? (评论中提供的答案:“ 要与其他语言兼容”):
-
仅在 32bit 上起作用(在 64bit 上被忽略)
-
它引入了许多其他问题(其中一些您甚至没有经历过),例如函数名称修改(检查 dumpbin 输出),只能使用 .def 文件
-
总而言之,这似乎是一个 XY问题。 您应该使用默认设置(完全摆脱 __ stdcall ):
- 使用此版本的代码,只需在构建 .dll 和 / DCALL_CONV_STDCALL 参数传递给编译器> .exe
- 您不太可能遇到(细微)问题(至少直到您在该领域获得更多经验之前)
- 删除所有与调用约定相关的代码,将使其更短,更简洁
-
所有列出的语言( Delphi , Python , C#,...)都支持 __ cdecl (毕竟,我认为在那里运行的大多数机器代码仍然是用 C 编写的)
-
有关整个区域的更多详细信息,您可以检查(包括(递归地)引用的 URL s):