65.9K
CodeProject 正在变化。 阅读更多。
Home

原生(Delphi)回调在 .NET (C#) COM 程序集中

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2012 年 4 月 26 日

CPOL

5分钟阅读

viewsIcon

50497

downloadIcon

1091

如何将原生(Delphi)回调指针传递给 .NET COM 程序集

引言

我们假设以下情况

  • 您想编写一个 .NET 方法,并使其可供原生编程语言使用。
  • 此 .NET 方法将回调作为参数。
  • 您想从 Delphi 应用程序调用此 .NET 方法。

背景

首先,什么是回调?根据维基百科,回调是对一段可执行代码的引用,它作为参数传递给另一段代码。这允许较低级别的软件层调用定义在高层软件层中的子程序(或函数)。在原生环境中处理回调相当直接。当然,在 .NET 中,我们有代表(delegates),它们最接近这个概念,但我们如何在原生和 .NET 之间使用/传递回调呢?这正是本文主要探讨的内容。上述场景中的另一个问题是使 .NET 方法对原生世界可见。这可以通过使 .NET 程序集 COM 可见来实现,以下是使 .NET 程序集 COM 可见的几个重要步骤。

  • 将程序集注册为 COM 对象。
  • 将程序集放入全局程序集缓存 (GAC) 中,如果您想从不同的路径/位置调用它。
  • 为程序集提供强名称,如果您想将其添加到 GAC 中。

.NET 部分

我将使用 Visual Studio 2010 和 C# 来构建这部分。在 VS2010 中,添加一个新的类库项目并命名为 CSDemoLibrary。在项目属性和程序集信息对话框中,勾选“使此库 COM 可见”。

如果我们想将此程序集添加到全局程序集缓存 (GAC) 中,我们需要为它提供一个强名称。要提供此名称:转到“签名”选项卡,勾选“签名程序集”,然后从下拉列表中选择“<新建…>”,并将文件命名为 CSDemoLibrary。

因为我们需要将指针传递到此程序集,所以还需要一个额外的步骤,即在此程序集中启用不安全代码,方法是在“生成”选项卡中勾选“允许不安全代码”。

现在我们可以编写代码了。删除项目中默认的类,并添加新类,将其命名为 MyClass

为了简单起见,在此演示中,我们将使用一个简单的回调,它只接受两个参数(整数和字符串)。在 Delphi 中,此回调函数将声明如下:

procedure callback(intParam: Integer; strParam: pChar);

因此,要在 .NET 中使用此回调的等价物,请声明一个代表(delegate)如下:

public delegate void NativeCallback(Int32 intParam, [MarshalAs(UnmanagedType.LPWStr)] string strParam);

我在这里使用了 [MarshalAs(UnmanagedType.LPWStr)] 属性,以便将字符串转换为 Unicode/宽字符串。如果您只想传递 ANSI 字符串,可以省略此属性(在这种情况下,Delphi 方法中的 strParam 应该为 pAnsiChar)。现在,让我们编写 .NET 方法。首先,我们需要使用 ComVisible(true) 属性和一个唯一的 GUID 字符串使 MyClass COM 可见,并且因为此 .NET 方法将处理原生指针(回调指针),所以我们必须使用 (unsafe) 关键字标记它。以下是 MyClass.cs 的完整代码:

using System;
using System.Runtime.InteropServices;
using System.Threading;namespace CSDemoLibrary
{
public delegate void NativeCallback(Int32 intParam, 
        [MarshalAs(UnmanagedType.LPWStr)] string strParam);

[ComVisible(true),
GuidAttribute("3A65D04A-3F2F-4CB3-B65A-8D402B8C64CE")]
public class MyClass
{

public unsafe int Process(
int intValue,
Int32 callbackPointer,
int intParam,
string strParam)
{
IntPtr ptr = new IntPtr(callbackPointer);
NativeCallback callbackMethod = 
      (NativeCallback)Marshal.GetDelegateForFunctionPointer(ptr, typeof(NativeCallback));

callbackMethod(intParam, strParam);
Thread.Sleep(1000);

callbackMethod(25, "first step");
Thread.Sleep(1000);
callbackMethod(50, "second step");
Thread.Sleep(1000);
callbackMethod(75, "third step");
Thread.Sleep(1000);
callbackMethod(100, "fourth step");
return intValue * 10;
}
}
}

在前面的代码中,Process 方法模拟了一个耗时的进程(例如从互联网获取大量数据),并通过从原生主机应用程序进行回调来发送状态通知。在我们的演示中,为了简单起见,我们使用 Thread.Sleep 将执行暂停一秒钟,以模拟一个返回整数值的耗时进程。在此方法中,callbackPointer 是原生回调指针,(intParam, strParam) 是回调参数。这两个参数将用作回调的初始化参数。在许多情况下,不需要这些初始化参数,但我在这里保留它们是为了展示如何将它们从原生应用程序传递给 .NET 方法。GetDelegateForFunctionPointer 方法是此解决方案的关键;它将非托管函数指针转换为代表。有关此方法的更多信息,请 在此 处查看。

注册 .NET 程序集

为避免路径问题,我将使用(Visual Studio 命令提示符)。在(开始菜单 -> 所有程序 -> Microsoft Visual Studio 2010 -> Visual Studio Tools)中对此工具有快捷方式。使用此工具,您可以按照以下步骤注册 COM 程序集:

  • 以管理员身份运行 Visual Studio 命令提示符,然后使用常规的 DOS 命令导航到 CSDemoLibrary.dll 文件夹。
  • 如果您打算将 .NET 程序集 CSDemoLibrary.dll 放在原生应用程序(.exe)的同一目录下,您可以忽略将程序集添加到全局程序集缓存 (GAC) 的步骤,否则您应该将其添加到 GAC。要做到这一点,请使用带 –I 参数的 gacutil,如下所示:gacutil -i CSDemoLibrary.dll
  • 使用 regasm 注册 COM 程序集,如下所示:regasm CSDemoLibrary.dll

注释

  • 要从 GAC 中移除 .NET 程序集:gacutil -u CSDemoLibrary
  • 要取消注册 .NET 程序集:regasm -u CSDemoLibrary.dll

原生(Delphi)部分

我将为此部分使用 Delphi 2010。在 Delphi 2010 IDE 中,创建一个新的 VCL 应用程序。将(TLabelTButtonTGauge)添加到主窗体。

在主窗体的代码后端,将 ComObj 添加到 uses 部分。创建一个简单的回调过程来更新 Gauge1 的进度和 lbMessage 的标题,如下所示:

procedure callback(intParam: Integer; strParam: pChar); stdcall;
Begin
 Form2.Gauge1.Progress := intParam;
 Form2.lbMessage.Caption := strParam;
 Application.ProcessMessages;
End;

我将在本演示中使用后期绑定,因此请更新 btnProcess 的点击事件处理程序,如下所示:

procedure TForm2.btnProcessClick(Sender: TObject);
var
oleObject: OleVariant;
begin
try
oleObject := CreateOleObject('CSDemoLibrary.MyClass');
ShowMessage('result= ' + IntToStr(oleObject.Process(10, LongInt(@callback), 0, 'initialization')));
except on E: Exception do
ShowMessage('COM Error: ' + #13 + #10 + e.Message);
end;
end;

此应用程序调用 .NET 耗时进程,该进程通过回调过程将进程状态通知原生应用程序,最后显示 .NET 方法/进程的结果。

参考文献

关注点

我希望本文展示了在 Delphi 中使用 C# COM 程序集是多么容易。

© . All rights reserved.