从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,该函数没有特殊的过程来加载字符串资源,这会造成问题。

此问题有两种解决方案:

  1. 始终使用CString((LPCTSTR)nResID)而不是CString((LPCSTR)nResID)从资源加载字符串。但是,此用法与MSDN文档不一致,因此我们必须将其称为未记录的用法。

  2. 始终使用LoadString加载字符串资源。

  3. 尽管解决方案1稍微简单一些,但它是未记录的用法,所以我最终选择了解决方案2来解决我的问题。

非常感谢您为解决此问题所提供的所有帮助。

以上是关于从Delphi调用C ++ DLL时发生访问冲突的主要内容,如果未能解决你的问题,请参考以下文章

尝试从 VS2013 C# 程序调用 DELPHI XE2 DLL 时出错

编写从 C++ 应用程序链接的 Delphi DLL:访问 C++ 接口成员函数会导致访问冲突

从 C++ 调用 C DLL 会导致访问冲突,但 C# 项目与 DllImport 工作

调用 C++ dll 的访问冲突

MFC dll 中的访问冲突(用 C++/CLI 包装)从 C# 程序开始

在 delphi 中使用 COM DLL - MSVCR80D.dll 错误中的访问冲突