尝试读取或写入受保护的内存。通常这意味着其他内存已经损坏。来自C#的Delphi7 DLL。

5
我遇到了一个问题,需要从C#调用Delphi 7 DLL文件。我对C#不太熟悉,也不需要太了解Delphi。我只需要尽快解决这个问题。
我试图从C#调用dll,但是我收到了这个错误:“尝试读取或写入受保护的内存。这通常表明其他内存已经损坏。”
我不知道为什么会出现这种情况。正如您在我的Delphi代码中看到的,我并没有尝试返回任何值。我只需要在COM端口上发送一些命令。如果有人能帮我解决这个问题,那就太好了:( Delphi DLL代码:
library Project2;


uses
  SysUtils,
  ComPort,
  Classes;

var com1:TComport ;

{$R *.res}
procedure moveforward; export;
begin
  com1.WriteAnsiString('#20 P1528 CR'+sLineBreak);
  com1.WriteAnsiString('#7 P1465 CR'+sLineBreak);
end;

procedure movebackward; export;
begin
  comport1.WriteAnsiString('#7 P1528 CR'+sLineBreak);
  comport1.WriteAnsiString('#20 P1465 CR'+sLineBreak);
end;

procedure stopmove;export;
begin
  comport1.WriteAnsiString('#20 P1500 CR'+sLineBreak);
  comport1.WriteAnsiString('#7 P1500 CR'+sLineBreak);
end;

procedure catch; export;
begin
  comport1.WriteAnsiString('#2 P2120 T2000 CR'+sLineBreak); //arm
  comport1.WriteAnsiString('#30 P2260 T500 CR'+sLineBreak); //gripper
end;

procedure initialize; export;
begin
  comport1.WriteAnsiString('#2 P2184 T1000 CR'+sLineBreak); //arm
  comport1.WriteAnsiString('#30 P1980 T2000 CR'+sLineBreak); //gripper
end;

exports  
  moveforward, movebackward, stopmove, catch, initialize;

begin
end.

C# 代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication3
{
    public partial class Form1 : Form
    {
        [System.Runtime.InteropServices.DllImport("Project2.dll")]
        public static extern void moveforward();
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            moveforward();
        }
    }
}

非常感谢你的帮助


2
你肯定可以使用纯C#来完成这个任务,从而避免使用P/Invoke和额外的本地模块。 - David Heffernan
2个回答

7

编辑:

请注意,经过进一步思考,我相当确定这不是正确的答案。虽然使用不匹配的调用约定仍然是一个坏主意(因此我最初的直觉认为它是问题所在),但在这种情况下,它可能不是GPF的原因。

原文:

99%的时间,此错误意味着您错误地使用了调用约定。我已经很久没有使用Delphi了,但我认为这将解决您的问题:

procedure moveforward; export; stdcall;

当您仅使用单个编译器时,调用约定通常不是问题,因为所有内容都使用相同的编译器。但是,如果混合使用不同的语言,甚至混合使用不同供应商的编译器,这可能会成为问题。调用约定确定调用的哪一端必须清除参数;如果两侧都尝试这样做,可能会导致GPF。

默认情况下,DllImport属性使用CallingConvention.StdCall的调用约定,尽管您可以在属性本身中覆盖它。不幸的是,Delphi的默认调用约定是一种不受支持的类型(也称为fastcall或register),因此您唯一的选择是更改Delphi方面以使用stdcall。


2
+1。您可能需要编辑以解释所有导出过程声明都应添加stdcall(因为提问者明确表示对Delphi不熟悉)。 - Ken White
4
非常好的编辑。 :) 如果可以的话,我会额外加上 +1。 (我只会更改“不幸”和“不支持”的词语,因为在编写本机Win32应用程序时,fastcallregister是大多数情况下最佳选择 - 当没有以某种方式使用时,可以说Microsoft选择stdcall作为默认选项是“不幸的”,因为它不如fastcall高效。)Delphi的主要用途是开发独立的、自包含的Win32可执行文件,并且使用fastcallregister调用约定可以实现最高效的目标函数。 :) - Ken White
1
虽然正确使用调用规范很重要,但在这种情况下不应该成为问题的原因。当没有函数参数时,stdcall 和 register 调用约定是相同的。 - Rob Kennedy
不幸的是,.NET不支持fastcall/register,因此您无法选择更明显的选项并修复导入以匹配Delphi导出。 - Michael Edenfield
1
所有这些关于调用约定的讨论都非常有趣,但对于涉及没有参数的过程的问题来说完全无关紧要。 - David Heffernan
每当我看到P/Invoke抛出GPF时,我总是认为调用约定是错误的,在这种情况下确实如此,但你说得对,我没有足够注意被调用的实际签名。更新我的答案。 - Michael Edenfield

5

Delphi代码从未给全局变量com1赋值。您需要使其引用TComport实例。导出另一个函数以设置DLL供进一步使用:

procedure set_up_dll; stdcall; export;
begin
  com1 := TComport.Create(nil);
end;

还有一种方法可以使其失效:

procedure clean_up_dll; stdcall; export;
begin
  com1.Free;
  com1 := nil;
end;

在使用DLL的其他函数之前和之后调用这些函数。

非常感谢。我今晚会尝试一下,看看会发生什么 :) - user1184061

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接