从 Delphi 调用 C++ DLL 时发生访问冲突
Access violation when invoking a C++ DLL from Delphi
我在 Visual C++ 6.0 中编写了一个 Unicode DLL。然后尝试从 Delphi XE3.
调用 DLL 函数
当我在Delphi中调试时,当跨行调用DLL函数时,我总是会得到一个访问冲突异常。
然而,当我在 Visual C++ 中进行调试时,我可以看到从 Delphi 传递的所有参数都是正确的,并且我可以毫无例外地跨过所有代码行。
如果 运行 在调试器之外,那么我将看不到任何“访问冲突异常。
试了很多方法还是没搞清楚在Delphi中debuggin时的异常如何消除。
下面是Visual C++ 6.0部分的代码:
TestDLL.cpp:
extern "C" VOID WINAPI Test(CONST MESSAGEPROC lpMessageProc, LPVOID lParam)
{
if (lpMessageProc != NULL)
(*lpMessageProc)(1500, (const LPVOID)(LPCTSTR)CString((LPCSTR)IDS_MYTEST), lParam);
/*
if (lpMessageProc != NULL)
(*lpMessageProc)(1500, (const LPVOID)(LPCTSTR)CString(_T("Test")), lParam);*/
}
TestDLL.h:
// TestDLL.h : main header file for the TESTDLL DLL
//
#if !defined(AFX_TESTDLL_H__38054A53_5CEE_4ABF_9BA8_BCE427FCB8E1__INCLUDED_)
#define AFX_TESTDLL_H__38054A53_5CEE_4ABF_9BA8_BCE427FCB8E1__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif
#include "resource.h" // main symbols
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef BOOL (CALLBACK* MESSAGEPROC)(CONST DWORD dwMessageId, CONST LPVOID lp, LPVOID lParam);
VOID WINAPI Test(CONST MESSAGEPROC lpMessageProc, LPVOID lParam);
#ifdef __cplusplus
}
#endif
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_TESTDLL_H__38054A53_5CEE_4ABF_9BA8_BCE427FCB8E1__INCLUDED_)
下面是Delphi XE3部分的代码:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
public
{ Public declarations }
end;
PForm1 = ^TForm1;
TMessageProc = function (const dwMessageId: DWORD; const lp: Pointer; lParam: Pointer): BOOL; stdcall;
{$EXTERNALSYM TMessageProc}
var
Form1: TForm1;
procedure Test(const lpMessageProc: TMessageProc; lParam: Pointer); stdcall;
implementation
{$R *.dfm}
procedure Test; external 'TestDLL.dll' index 2;
function MessageProc(const dwMessageId: DWORD; const lp: Pointer; lParam: Pointer): BOOL; stdcall;
begin
Result := True;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Test(MessageProc, @Self); // <---- This code line will cause "access violation
end;
我相信问题出现在 DLL 测试函数中,当它尝试使用 CString((LPCSTR)IDS_MYTEST) 从资源中加载字符串时。如果我将代码更改为 CString(_T("Test")),那么问题就会消失。
谢谢
如您所料,此语句无效:
CString((LPCSTR)IDS_MYTEST)
尽管 CString
的这个构造函数确实允许您将资源 ID 传递给它,但它会尝试在调用进程(即 Delphi EXE)的资源中查找资源,而不是在DLL 的资源。从 DLL 的资源加载字符串时,您需要使用 DLL 的 HINSTANCE
,如 DLL 的 DllMain()
所提供的。您可以为此使用 CString::LoadString()
方法,例如:
HINSTANCE hInst;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
hInst = hinstDLL;
return TRUE;
}
extern "C" VOID WINAPI Test(CONST MESSAGEPROC lpMessageProc, LPVOID lParam)
{
if (lpMessageProc != NULL)
{
CString str;
str.LoadString(hInst, IDS_MYTEST);
(*lpMessageProc)(1500, (LPCTSTR)str, lParam);
}
}
终于弄明白这是MFC代码的bug(VC6.0版本)
不知道能不能post MFC 源代码所以我就只贴函数头和相关部分了。
在Microsoft Visual Studio\VC98\MFC\SRC\STRCORE.CPP中,我们可以看到以下3个函数:
//////////////////////////////////////////////////////////////////////////////
// More sophisticated construction
CString::CString(LPCTSTR lpsz) // Function 1
{
Init();
if (lpsz != NULL && HIWORD(lpsz) == NULL)
{
UINT nID = LOWORD((DWORD)lpsz);
if (!LoadString(nID))
TRACE1("Warning: implicit LoadString(%u) failed\n", nID);
}
else
{
// Construct string normally
}
}
/////////////////////////////////////////////////////////////////////////////
// Special conversion constructors
#ifdef _UNICODE
CString::CString(LPCSTR lpsz) // Function 2
{
// Construct string normally
}
#else //_UNICODE
CString::CString(LPCWSTR lpsz) // Function 3
{
// Construct string normally
}
#endif //!_UNICODE
在上面的代码片段中我们可以看到,只有函数1包含对lpsz进行特殊处理并检查它是否是字符串资源ID的代码,如果是,则从资源中加载字符串。功能2和3都没有这种特殊的过程。
我们在VS6中创建工程时,工程默认设置为_MBCS,这样的话,函数1就会变成
CString::CString(LPCSTR lpsz)
所以 CString((LPCSTR)nResID) 将实际调用函数 1 并正确加载字符串资源。
函数 2 将被禁用,因为 _UNICODE 未定义。函数 3 适用于宽字符字符串。
因此,对于 _MBCS 项目,一切都与 MSDN 文档完美一致。
但是,当我将_MBCS 更改为_UNICODE 时,函数1 将变为
CString::CString(LPCWSTR lpsz)
将启用功能 2,将禁用功能 3。
所以 CString((LPCSTR)nResID) 实际上会调用函数 2,它没有特殊的进程来加载字符串资源,这就是问题所在。
这个问题有两种解决方法:
始终使用 CString((LPCTSTR)nResID) 而不是 CString((LPCSTR)nResID) 从资源中加载字符串。然而,这种用法与MSDN文档不一致,所以我们不得不称其为未记录的用法。
始终使用 LoadString 加载字符串资源。
虽然方案一稍微简单一点,但它是一个未记录的用法,所以我最终选择了方案二来解决我的问题。
非常感谢您为解决此问题提供的所有帮助。
我在 Visual C++ 6.0 中编写了一个 Unicode DLL。然后尝试从 Delphi XE3.
调用 DLL 函数当我在Delphi中调试时,当跨行调用DLL函数时,我总是会得到一个访问冲突异常。
然而,当我在 Visual C++ 中进行调试时,我可以看到从 Delphi 传递的所有参数都是正确的,并且我可以毫无例外地跨过所有代码行。
如果 运行 在调试器之外,那么我将看不到任何“访问冲突异常。
试了很多方法还是没搞清楚在Delphi中debuggin时的异常如何消除。
下面是Visual C++ 6.0部分的代码:
TestDLL.cpp:
extern "C" VOID WINAPI Test(CONST MESSAGEPROC lpMessageProc, LPVOID lParam)
{
if (lpMessageProc != NULL)
(*lpMessageProc)(1500, (const LPVOID)(LPCTSTR)CString((LPCSTR)IDS_MYTEST), lParam);
/*
if (lpMessageProc != NULL)
(*lpMessageProc)(1500, (const LPVOID)(LPCTSTR)CString(_T("Test")), lParam);*/
}
TestDLL.h:
// TestDLL.h : main header file for the TESTDLL DLL
//
#if !defined(AFX_TESTDLL_H__38054A53_5CEE_4ABF_9BA8_BCE427FCB8E1__INCLUDED_)
#define AFX_TESTDLL_H__38054A53_5CEE_4ABF_9BA8_BCE427FCB8E1__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif
#include "resource.h" // main symbols
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef BOOL (CALLBACK* MESSAGEPROC)(CONST DWORD dwMessageId, CONST LPVOID lp, LPVOID lParam);
VOID WINAPI Test(CONST MESSAGEPROC lpMessageProc, LPVOID lParam);
#ifdef __cplusplus
}
#endif
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_TESTDLL_H__38054A53_5CEE_4ABF_9BA8_BCE427FCB8E1__INCLUDED_)
下面是Delphi XE3部分的代码:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
public
{ Public declarations }
end;
PForm1 = ^TForm1;
TMessageProc = function (const dwMessageId: DWORD; const lp: Pointer; lParam: Pointer): BOOL; stdcall;
{$EXTERNALSYM TMessageProc}
var
Form1: TForm1;
procedure Test(const lpMessageProc: TMessageProc; lParam: Pointer); stdcall;
implementation
{$R *.dfm}
procedure Test; external 'TestDLL.dll' index 2;
function MessageProc(const dwMessageId: DWORD; const lp: Pointer; lParam: Pointer): BOOL; stdcall;
begin
Result := True;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Test(MessageProc, @Self); // <---- This code line will cause "access violation
end;
我相信问题出现在 DLL 测试函数中,当它尝试使用 CString((LPCSTR)IDS_MYTEST) 从资源中加载字符串时。如果我将代码更改为 CString(_T("Test")),那么问题就会消失。
谢谢
如您所料,此语句无效:
CString((LPCSTR)IDS_MYTEST)
尽管 CString
的这个构造函数确实允许您将资源 ID 传递给它,但它会尝试在调用进程(即 Delphi EXE)的资源中查找资源,而不是在DLL 的资源。从 DLL 的资源加载字符串时,您需要使用 DLL 的 HINSTANCE
,如 DLL 的 DllMain()
所提供的。您可以为此使用 CString::LoadString()
方法,例如:
HINSTANCE hInst;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
hInst = hinstDLL;
return TRUE;
}
extern "C" VOID WINAPI Test(CONST MESSAGEPROC lpMessageProc, LPVOID lParam)
{
if (lpMessageProc != NULL)
{
CString str;
str.LoadString(hInst, IDS_MYTEST);
(*lpMessageProc)(1500, (LPCTSTR)str, lParam);
}
}
终于弄明白这是MFC代码的bug(VC6.0版本)
不知道能不能post MFC 源代码所以我就只贴函数头和相关部分了。
在Microsoft Visual Studio\VC98\MFC\SRC\STRCORE.CPP中,我们可以看到以下3个函数:
//////////////////////////////////////////////////////////////////////////////
// More sophisticated construction
CString::CString(LPCTSTR lpsz) // Function 1
{
Init();
if (lpsz != NULL && HIWORD(lpsz) == NULL)
{
UINT nID = LOWORD((DWORD)lpsz);
if (!LoadString(nID))
TRACE1("Warning: implicit LoadString(%u) failed\n", nID);
}
else
{
// Construct string normally
}
}
/////////////////////////////////////////////////////////////////////////////
// Special conversion constructors
#ifdef _UNICODE
CString::CString(LPCSTR lpsz) // Function 2
{
// Construct string normally
}
#else //_UNICODE
CString::CString(LPCWSTR lpsz) // Function 3
{
// Construct string normally
}
#endif //!_UNICODE
在上面的代码片段中我们可以看到,只有函数1包含对lpsz进行特殊处理并检查它是否是字符串资源ID的代码,如果是,则从资源中加载字符串。功能2和3都没有这种特殊的过程。
我们在VS6中创建工程时,工程默认设置为_MBCS,这样的话,函数1就会变成
CString::CString(LPCSTR lpsz)
所以 CString((LPCSTR)nResID) 将实际调用函数 1 并正确加载字符串资源。
函数 2 将被禁用,因为 _UNICODE 未定义。函数 3 适用于宽字符字符串。
因此,对于 _MBCS 项目,一切都与 MSDN 文档完美一致。
但是,当我将_MBCS 更改为_UNICODE 时,函数1 将变为
CString::CString(LPCWSTR lpsz)
将启用功能 2,将禁用功能 3。
所以 CString((LPCSTR)nResID) 实际上会调用函数 2,它没有特殊的进程来加载字符串资源,这就是问题所在。
这个问题有两种解决方法:
始终使用 CString((LPCTSTR)nResID) 而不是 CString((LPCSTR)nResID) 从资源中加载字符串。然而,这种用法与MSDN文档不一致,所以我们不得不称其为未记录的用法。
始终使用 LoadString 加载字符串资源。
虽然方案一稍微简单一点,但它是一个未记录的用法,所以我最终选择了方案二来解决我的问题。
非常感谢您为解决此问题提供的所有帮助。