从 Delphi 调用 C++ DLL 时的访问冲突

阿兰克

我在 Visual C++ 6.0 中编写了一个 Unicode DLL。然后尝试从 Delphi XE3 调用 DLL 函数。

当我在Delphi中调试时,当跨行调用DLL函数时,我总是会得到一个访问冲突异常。

但是,当我在 Visual C++ 中调试时,我可以看到从 Delphi 传递的所有参数都是正确的,并且我可以毫无例外地跳过所有代码行。

如果在调试器之外运行,那么我将看不到任何“访问冲突异常”。

我尝试了很多方法,但仍然无法弄清楚在Delphi中调试时如何消除异常。

下面是 Visual C++ 6.0 部分的代码:

测试DLL.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);*/
}

测试DLL.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")),那么问题就会消失。

谢谢

阿兰克

我终于弄清楚这是MFC代码(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

正如我们在上面的代码片段中看到的,只有函数 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 来解决我的问题。

非常感谢您帮助解决这个问题。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章