在C#中调用Delphi COM对象会引发AccessViolationException
我试图向Delphi COM对象发送一个字符串,并期望该对象给出一个答案,但由于某种原因,它抛出了一个AccessViolationException。 这是它抛出的异常,翻译成英语的异常描述是:试图读取或写入受保护内存。这通常表示其他内存已损坏。程序输出(带堆栈顶部跟踪): QManservice已启动。在C#中调用Delphi COM对象会引发AccessViolationException,c#,delphi,com,C#,Delphi,Com,我试图向Delphi COM对象发送一个字符串,并期望该对象给出一个答案,但由于某种原因,它抛出了一个AccessViolationException。 这是它抛出的异常,翻译成英语的异常描述是:试图读取或写入受保护内存。这通常表示其他内存已损坏。程序输出(带堆栈顶部跟踪): QManservice已启动。 按任意键停止。 请求接收订单。 收到字符串:S$GET ORDERS OnInverwerkte UITzonerding:System.AccessViolationException:与
按任意键停止。
请求接收订单。
收到字符串:S$GET ORDERS OnInverwerkte UITzonerding:System.AccessViolationException:与schrijven van Beviligd geheugen的het lezen进行交易。这是一个很好的例子 bij Microsoft.Win32.Win32 Native.SysStringByteLen(IntPtr bstr)
bij系统.StubHelpers.BSTRMarshaler.ConvertToManaged(IntPtr-bstr)
bij QMan_SafanDarley.IWLM_uuz.Send(字符串消息、字符串和应答)
bij WorkLoadManagerServiceDefinitions.QManService.SendStringtoCON(字符串codToSend)位于D:\Michael\C#Projects\QManServiceConsoleApp\OrderEditor\u WCF\QManService.cs:regel 210
D:\Michael\CR Projects\QManServiceConsoleApp\OrderEditor\U WCF\QManService.cs:regel 67中的bij WorkLoadManagerServiceDefinitions.QManService.RequestGetOrders() 这是调用COM的代码
private string SendStringToCOM(string cmdToSend)
{
try
{
Console.WriteLine($"String received: {cmdToSend}");
if (WLM == null)
{
WLM = new WLM_();
}
string answer = string.Empty;
WLM.Send(cmdToSend, out answer);
Console.WriteLine("Answer received");
return answer;
} catch(Exception e)
{
Console.WriteLine(e.Message);
Console.ReadKey();
return string.Empty;
}
}
这是Delphi中接收调用的代码,它将调用发送给另一个单元,该单元根据接收到的命令执行数据库操作
function TWLM_.Send(const Msg: WideString; out Answer: WideString) : Integer;
begin
Result := fmProduction.AnalyzeData(Msg, 0);
end;
我应该补充一点,这可以在我的电脑和同事的电脑上使用,但不能在第三台电脑上使用。
关于如何解决这个问题,有什么建议吗?@J..关于未分配答案参数的评论,在我的情况下,就是这个问题。我让一位同事更改了dll,为参数指定了一个值,这似乎修复了错误,现在它可以正常工作了
function TWLM_.Send(const Msg: WideString; out Answer: WideString) : Integer;
begin
Result := fmProduction.AnalyzeData(Msg, 0);
end;
这里没有为答案参数分配任何内容。这是作为out
参数传递的,这意味着需要该方法为其分配某些内容。这与函数返回值的行为完全相同
如果不为此变量赋值,则在方法为其分配堆栈空间时,堆栈上将存在任何(未赋值)值。这将不是指向WideString
的有效指针,但消费代码将尝试封送它,就像封送一样。有时会立即崩溃,有时不会,有时可能会损坏其他数据。无论哪种情况,这都是一个错误
在本机Delphi代码中,可以使用out
和var
参数-在这两种情况下,对于引用类型,调用代码的指针都可以通过接受参数的方法进行读写。如果方法选择不修改该值,则无需修改。然而,对于托管互操作,期望的是out
参数将始终由该方法分配。C#强制执行,但德尔福没有
在这种情况下,您在C#端传入的空字符串根本不会传入到该方法中-您可能希望它仍然是一个空字符串,不会被Delphi代码修改,但是,通过将参数设置为out
参数,调用代码期望在该参数中接收一个返回值,并立即用返回的内容覆盖传递的变量(在本例中,是指向无意义的指针)。扩展而言,C端变量在传递给此方法之前可能具有的任何值在Delphi/COM端都不可访问。Delphi的WideString
是COMBSTR的包装。PInvoking toSend()
时,您的C#code封送string
s是否为BSTR
s?你没有在C#侧显示该声明。另外,您的Delphi函数是否使用与C兼容的调用约定?看起来不是。它必须仅使用cdecl
或stdcall
,并在C#端相应地声明。@RemyLebeau代码不封送字符串,但由于它在某些系统上工作,我认为这是不必要的。调用约定也是一样,目前还没有定义,但似乎没有它也能工作。@MichaelD“有时似乎能工作”本身并不是正确代码的证据(通常情况下恰恰相反)。做出假设同样是危险的。你是怎么导入的?@J。。。我通过visualstudio的addreference->COM添加了引用。dll是由我的公司制作的。应答参数将与下一版本的dll一起分配。out
参数会在输入函数时自动将托管类型(如WideString
)初始化为有效的默认值。因此,在本例中,WideString
将被初始化为nil/空字符串。看。@RemyLebeau是的,但这是德尔菲的事,不是吗?我想,初始化编译器管理的out
参数和返回值是由调用方执行的。在从Delphi代码调用此方法的情况下,是的,编译器将在调用端插入初始化,但方法本身不这样做。。。至少这是我的理解。因为这是由C#wrapper调用的,所以将由该包装器决定如何处理传入的变量。还是我完全误解了什么?@RemyLebeau事实上,我刚刚编译了一个测试,似乎是这样的,即:Project1.dpr.18:Send(ws);00417B9B8D45FC lea eax,[ebp-$04]00417B9E E85DECFFF call@WStrClr 00417BA3 E8A4FFFFFF call Send
,因此编译器在调用Send
之前插入@WStrClr
。但是,如果Send
被导出到一个COM DLL中,我认为应该由调用方决定如何处理该参数。