Delphi通过JCL向C#传接口抛异常,捕获后显示0x8000FFFF求助
Delphi通过JCL传递接口给C#时异常返回0x8000FFFF的问题排查
我希望捕获Delphi端通过JCL以接口形式传递给C#库的异常。目前异常虽能被捕获,但返回错误码0x8000FFFF(E_UNEXPECTED),请问哪里操作有误?
Delphi代码
unit TestClass; interface uses SysUtils, JcLDotNet; type ITestService = interface['{7B07D16A-72AE-4DD3-8959-B1EC6C6241B7}'] procedure Call(); safecall; end; TTestService = class(TInterfacedObject, ITestService) procedure Call(); safecall; end; TSimpleClass = class FHost : TJclClrHost; FCSharp: OleVariant; constructor Create(); procedure Call(); end; implementation procedure TSimpleClass.Call; begin FCSharp.Call; end; constructor TSimpleClass.Create(); var testService : TTestService; begin FHost := TJclClrHost.Create('v4.0.30319'); FHost.Start; FCSharp := FHost.DefaultAppDomain.CreateInstance('CSharpLibrary','CSharpLibrary.TestClass').Unwrap; // testService := TTestService.Create(); FCSharp.SetService(ITestService(IUnknown(testService))); end; procedure TTestService.Call(); begin raise Exception.Create('throw exception'); end; end.
C#代码
using System; using System.Runtime.InteropServices; namespace CSharpLibrary { [ComVisible(true)] [Guid("7B07D16A-72AE-4DD3-8959-B1EC6C6241B7"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface ITestService { void Call(); } public class TestClass { private ITestService _testService; public void SetService(ITestService testService) { _testService = testService; } void Call() { try { _testService.Call(); } catch(Exception e) { // error: 0x8000FFFF E_UNEXPECTED Console.WriteLine(e.Message); throw; } } } }
问题原因及修复方案
核心问题点
- 调用约定不匹配:Delphi接口方法用
safecall(对应COM的HRESULT返回机制),但C#接口未配置对应的异常映射规则,导致.NET无法解析Delphi抛出的异常,返回通用的E_UNEXPECTED。 - 方法访问权限错误:C#中
Call方法为私有,Delphi通过OleVariant无法调用私有方法,触发隐式错误干扰异常传递。
具体修改步骤
Delphi端:实现COM错误信息支持接口
引入ActiveX单元,让TTestService实现ISupportErrorInfo接口,确保异常详情能正确传递给C#:
uses SysUtils, JcLDotNet, ActiveX; // 添加ActiveX引用 type ITestService = interface['{7B07D16A-72AE-4DD3-8959-B1EC6C6241B7}'] procedure Call(); safecall; end; TTestService = class(TInterfacedObject, ITestService, ISupportErrorInfo) procedure Call(); safecall; function InterfaceSupportsErrorInfo(const riid: TGUID): HResult; stdcall; end; // 实现ISupportErrorInfo方法 function TTestService.InterfaceSupportsErrorInfo(const riid: TGUID): HResult; begin if IsEqualGUID(riid, ITestService) then Result := S_OK else Result := S_FALSE; end; procedure TTestService.Call(); var E: Exception; begin E := Exception.Create('throw exception'); E.ErrorCode := $80004005; // 指定标准错误码E_FAIL,可选但推荐 raise E; end;
C#端:匹配调用约定并修正权限
- 给接口方法加
[PreserveSig(false)],让.NET自动将COM HRESULT转换为.NET异常 - 将
Call方法改为public,确保Delphi能正常调用
using System; using System.Runtime.InteropServices; namespace CSharpLibrary { [ComVisible(true)] [Guid("7B07D16A-72AE-4DD3-8959-B1EC6C6241B7"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface ITestService { [PreserveSig(false)] // 关键:启用HRESULT到.NET异常的自动转换 void Call(); } public class TestClass { private ITestService _testService; public void SetService(ITestService testService) { _testService = testService; } public void Call() // 修改为public { try { _testService.Call(); } catch(Exception e) { Console.WriteLine(e.Message); throw; } } } }
总结
safecall与PreserveSig(false)是跨COM传递异常的关键配对,确保Delphi异常能被.NET正确解析- 实现
ISupportErrorInfo是COM跨语言传递异常详情的标准做法,避免返回通用错误码 - 跨语言调用的方法必须是公开权限,否则会触发未预期的访问错误
内容的提问来源于stack exchange,提问作者Paweł Kępiński
相关产品推荐
相关产品推荐

