我有一个 C++ 进程外 COM 服务器,它托管大量 C# 代码以支持 C++ COM 对象公开的 API。
出于各种原因,我正在考虑删除我的解决方案中的 C++ 部分。但是,由于我无法控制的限制,我必须保留进程外的 COM 服务器。 Microsoft 确实有此 here 的典型示例.
看着这个例子,有些东西我不明白。在消息循环开始之前,会创建一个计时器,每 5 秒调用一次 GC.Collect。我能找到的唯一提及表明这是为了确保在合理的时间范围内释放 COM 对象。我对此有点困惑...我的 C++ 主机当前是否自动调用 GC.Collect?我当然不会这样做。然而我正在创建托管对象(使用 COMVisible(true) 作为 C++ 代码中的 COM 对象。这是否意味着我现在应该每 5 秒调用一次 GC.Collect?如果不是,为什么我需要在这个新的 C# 中调用它在进程服务器之外。这是为了弥补在普通 C++ 应用程序中清理未引用的 COM 对象的自动进程吗?(我假设在消息循环的某个时间发生。)
每 5 秒调用一次 GC.Collect 似乎不是一个好主意。我担心错了吗?有没有其他方法可以达到同样的效果?
我正在使用 .NET 4.5 和 Visual Studio 2012。
最佳答案
IMO,在 C# 中创建 COM 进程外服务器的最简单方法是使用 DLL surrogate process .
您仍然仅限于双重接口(interface) (ComInterfaceType.InterfaceIsDual
),并且您需要注册生成的类型库(并将其作为部署的一部分):
C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe ManagedServer.dll/codebase/tlb
这将允许您使用 COM 类型库编码器,因为您没有用于 C# COM 对象的专用 COM 代理/stuf DLL。
确保使用正确的 RegAsm.exe
二进制文件,具体取决于您的 ManagedServer.dll
程序集的目标位数。以上假定 x86 代码。
这是一个完整的工作模板示例。它负责代理注册:
using Microsoft.Win32;
using System;
using System.Runtime.InteropServices;
namespace ManagedServer
{
[ComVisible(true), Guid("1891CF89-1282-4CA8-B7C5-F2608AF1E2F1")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IManagedComObject
{
string ComMethod(string data);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IManagedComObject))]
[Guid("989162CD-A6A6-4A7D-A7FB-C94086A4E90A")]
[ProgId("Noseratio.ManagedComObject")]
public class ManagedComObject : IManagedComObject
{
// public constructor
public ManagedComObject()
{
}
// IManagedComObject
public string ComMethod(string data)
{
return data;
}
// registration
[ComRegisterFunction()]
public static void Register(Type type)
{
var guid = type.GUID.ToString("B");
using (var appIdKey = Registry.ClassesRoot.CreateSubKey(@"AppID\" + guid))
{
appIdKey.SetValue("DllSurrogate", String.Empty);
}
using (var appIdKey = Registry.ClassesRoot.CreateSubKey(@"CLSID\" + guid))
{
appIdKey.SetValue("AppId", guid);
}
}
[ComUnregisterFunction()]
public static void Unregister(Type type)
{
var guid = type.GUID.ToString("B");
using (var appIdKey = Registry.ClassesRoot.OpenSubKey(@"AppID\" + guid, writable: true))
{
if (appIdKey != null)
appIdKey.DeleteValue("DllSurrogate", throwOnMissingValue: false);
}
Registry.ClassesRoot.DeleteSubKeyTree(@"CLSID\" + guid, throwOnMissingSubKey: false);
}
}
}
默认情况下,对象将在 MTA 单元中创建,因此接口(interface)方法可能会在任何线程上调用,您需要实现线程安全。
如果您需要在对象的代理进程内使用带有消息泵的 STA 线程,您可以通过实现工厂单例并使用 CoMarshalInterThreadInterfaceInStream
/CoGetInterfaceAndReleaseStream
明确地做到这一点在 STA 线程外导出对象(this 可能相关)。
还有一点,您的 COM 客户端代码在创建此 ManagedComObject
实例时应使用 CLSCTX_LOCAL_SERVER
。 Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.ManagedComObject"))
不是这种情况,它显然使用了 CLSCTX_ALL
。这很容易解决:
using System;
using System.Runtime.InteropServices;
namespace Client
{
class Program
{
static void Main(string[] args)
{
// dynamic obj = Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.ManagedComObject"));
dynamic obj = ComExt.CreateInstance(
Type.GetTypeFromProgID("Noseratio.ManagedComObject").GUID,
localServer: true);
Console.WriteLine(obj.ComMethod("hello"));
}
}
// COM interop
public static class ComExt
{
const uint CLSCTX_LOCAL_SERVER = 0x4;
const uint CLSCTX_INPROC_SERVER = 0x1;
static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
[DllImport("ole32.dll", ExactSpelling = true, PreserveSig = false)]
static extern void CoCreateInstance(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
uint dwClsContext,
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object rReturnedComObject);
public static object CreateInstance(Guid clsid, bool localServer)
{
object unk;
CoCreateInstance(clsid, null, localServer ? CLSCTX_LOCAL_SERVER : CLSCTX_INPROC_SERVER, IID_IUnknown, out unk);
return unk;
}
}
}
关于c# - 托管托管代码和垃圾回收,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22901224/