从Delphi调用C ++ DLL时发生访问冲突
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Delphi调用C ++ DLL时发生访问冲突相关的知识,希望对你有一定的参考价值。
我在Visual C ++ 6.0中编写了Unicode DLL。然后尝试从Delphi XE3调用DLL函数。
当我在Delphi中调试时,当跨过线路调用DLL函数时,我总是会遇到访问冲突异常。
但是,当我在Visual C ++中调试时,我可以看到从Delphi传递的所有参数都是正确的,并且我可以毫无例外地跳过所有代码行。
如果在调试器之外运行,那么我将看不到任何“访问冲突异常。
我尝试了许多方法,但是仍然无法弄清楚在Delphi中调试时如何消除异常。
下面是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;
我相信,当它尝试使用CString((LPCSTR)IDS_MYTEST)从资源加载字符串时,DLL测试功能中会出现问题。如果我将代码更改为CString(_T(“ Test”)),则问题消失。
谢谢
您猜想,此声明将无效:
CString((LPCSTR)IDS_MYTEST)
尽管CString
的此构造函数确实允许您向其传递资源ID,但它将尝试在调用进程的资源(即Delphi EXE的资源)而不是DLL的资源中查找资源。从DLL的资源加载字符串时,需要使用DLL的HINSTANCE
(由DLL的DllMain()
提供)。您可以为此使用CString::LoadString()
方法,例如:
CString::LoadString()
我终于弄清楚这是MFC代码(VC6.0版本)的错误。
我不知道是否可以发布MFC源代码,因此仅粘贴函数头和相关部分。
在Microsoft Visual Studio VC98 MFC SRC STRCORE.CPP中,我们可以看到以下三个功能:
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);
}
}
正如我们在上面的代码片段中看到的那样,只有函数1包含将在lpsz上进行特殊处理并检查它是否是字符串资源ID的代码,如果是,则从资源中加载字符串。函数2和3都没有这样的特殊过程。
当我们在VS6中创建项目时,项目的默认设置为_MBCS,在这种情况下,功能1将变为[]。
////////////////////////////////////////////////////////////////////////////// // 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 ", 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
所以CString(((LPCSTR)nResID)将实际上调用函数1并正确加载字符串资源。
由于未定义_UNICODE,因此功能2将被禁用。函数3适用于宽字符字符串。
因此,对于_MBCS项目,一切都与MSDN文档完美且一致地工作。
但是,当我将_MBCS更改为_UNICODE时,功能1将变为
CString::CString(LPCSTR lpsz)
将启用功能2,并且将禁用功能3。
所以CString((LPCSTR)nResID)实际上会调用函数2,该函数没有特殊的过程来加载字符串资源,这会造成问题。
此问题有两种解决方案:
始终使用CString((LPCTSTR)nResID)而不是CString((LPCSTR)nResID)从资源加载字符串。但是,此用法与MSDN文档不一致,因此我们必须将其称为未记录的用法。
始终使用LoadString加载字符串资源。
尽管解决方案1稍微简单一些,但它是未记录的用法,所以我最终选择了解决方案2来解决我的问题。
非常感谢您为解决此问题所提供的所有帮助。
以上是关于从Delphi调用C ++ DLL时发生访问冲突的主要内容,如果未能解决你的问题,请参考以下文章
尝试从 VS2013 C# 程序调用 DELPHI XE2 DLL 时出错
编写从 C++ 应用程序链接的 Delphi DLL:访问 C++ 接口成员函数会导致访问冲突
从 C++ 调用 C DLL 会导致访问冲突,但 C# 项目与 DllImport 工作