【问题标题】:Access violation when invoking a C++ DLL from Delphi从 Delphi 调用 C++ DLL 时访问冲突
【发布时间】:2019-10-24 08:16:44
【问题描述】:

我在 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;

我相信问题发生在 DLL 测试函数中,当它尝试使用 CString((LPCSTR)IDS_MYTEST) 从资源加载字符串时。如果我将代码更改为 CString(_T("Test")),那么问题就消失了。

谢谢

【问题讨论】:

  • Self 一个指针,所以 @Self 可能不是你想要的。你可能想要Pointer(Self)

标签: c++ delphi exception access-violation


【解决方案1】:

正如你所猜测的,这种说法是行不通的:

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);
    }
}

【讨论】:

【解决方案2】:

我终于发现这是MFC代码的一个bug(VC6.0版本)。

我不知道我是否可以发布 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

正如我们在上面的代码 sn-p 中看到的,只有函数 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,它没有特殊的过程来加载字符串资源,这就产生了问题。

这个问题有两种解决方案:

  1. 始终使用 CString((LPCTSTR)nResID) 而不是 CString((LPCSTR)nResID) 从资源加载字符串。但是,这种用法与 MSDN 文档不一致,因此我们不得不将其称为无证用法。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-19
    • 1970-01-01
    • 2012-09-12
    • 1970-01-01
    相关资源
    最近更新 更多