You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Framework如何调用CLR原生方法以支持ConditionalWeakTable枚举

问题背景

我尝试解决.NET Framework中ConditionalWeakTable<TKey, TValue>不支持枚举的问题,多方寻找替代方案后发现其内部依赖直接调用GC的DependentHandle,若自行实现很容易产生意外内存泄漏。

查看DependentHandle源代码发现其使用了多个标记[MethodImplAttribute(MethodImplOptions.InternalCall)]的方法,这些方法是直接调用CLR内部逻辑。

[System.Security.SecurityCritical]
[ResourceExposure(ResourceScope.AppDomain)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void nInitialize(Object primary, Object secondary, out IntPtr dependentHandle);

DependentHandle完整代码

struct DependentHandle
{
    #region Constructors
#if FEATURE_CORECLR
    [System.Security.SecuritySafeCritical] // auto-generated
#else
    [System.Security.SecurityCritical]
#endif
    public DependentHandle(Object primary, Object secondary)
    {
        IntPtr handle = (IntPtr)0;
        nInitialize(primary, secondary, out handle);
        // no need to check for null result: nInitialize expected to throw OOM.
        _handle = handle;
    }
    #endregion

    #region Public Members
    public bool IsAllocated
    {
        get
        {
            return _handle != (IntPtr)0;
        }
    }

    // Getting the secondary object is more expensive than getting the first so
    // we provide a separate primary-only accessor for those times we only want the
    // primary.
#if FEATURE_CORECLR
    [System.Security.SecuritySafeCritical] // auto-generated
#else
    [System.Security.SecurityCritical]
#endif
    public Object GetPrimary()
    {
        Object primary;
        nGetPrimary(_handle, out primary);
        return primary;
    }

#if FEATURE_CORECLR
    [System.Security.SecuritySafeCritical] // auto-generated
#else
    [System.Security.SecurityCritical]
#endif
    public void GetPrimaryAndSecondary(out Object primary, out Object secondary)
    {
        nGetPrimaryAndSecondary(_handle, out primary, out secondary);
    }

    //----------------------------------------------------------------------
    // Forces dependentHandle back to non-allocated state (if not already there)
    // and frees the handle if needed.
    //----------------------------------------------------------------------
    [System.Security.SecurityCritical]
    public void Free()
    {
        if (_handle != (IntPtr)0)
        {
            IntPtr handle = _handle;
            _handle = (IntPtr)0;
            nFree(handle);
        }
    }
    #endregion

    #region Private Members
    [System.Security.SecurityCritical]
    [ResourceExposure(ResourceScope.AppDomain)]
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    private static extern void nInitialize(Object primary, Object secondary, out IntPtr dependentHandle);

    [System.Security.SecurityCritical]
    [ResourceExposure(ResourceScope.None)]
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    private static extern void nGetPrimary(IntPtr dependentHandle, out Object primary);

    [System.Security.SecurityCritical]
    [ResourceExposure(ResourceScope.None)]
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    private static extern void nGetPrimaryAndSecondary(IntPtr dependentHandle, out Object primary, out Object secondary);

    [System.Security.SecurityCritical]
    [ResourceExposure(ResourceScope.None)]
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    private static extern void nFree(IntPtr dependentHandle);
    #endregion

    #region Private Data Member
    private IntPtr _handle;
    #endregion
}

理论上应用运行在.NET Framework上时这些内部调用应当存在,可以直接调用,但实例化DependentHandle时抛出了如下异常:

System.Security.SecurityException
  HResult=0x8013150A
  Message=ECall methods must be packaged into a system module.
  Source=ConsoleApp45
  StackTrace:
   at ConsoleApp45.DependentHandle.nInitialize(Object primary, Object secondary, IntPtr& dependentHandle)
   at ConsoleApp45.DependentHandle..ctor(Object primary, Object secondary) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 98
   at ConsoleApp45.ConditionalWeakTable`2.Container.CreateEntryNoResize(TKey key, TValue value) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 671
   at ConsoleApp45.ConditionalWeakTable`2.CreateEntry(TKey key, TValue value) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 561
   at ConsoleApp45.ConditionalWeakTable`2.Add(TKey key, TValue value) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 263
   at ConsoleApp45.Program.Main(String[] args) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 23

搜索解决方案时无法精准描述问题,发现微软近期已在.NET 6中将DependentHandle公开,但这仅能参考相关C++代码逻辑,对.NET Framework场景没有太多帮助。

之后尝试修改方法特性来调用对应原生代码,分别尝试了:

  • [DllImport("clr.dll")]
  • [DllImport("mscorlib.dll")]

但两种方式都抛出了类似异常:

System.EntryPointNotFoundException
  HResult=0x80131523
  Message=Unable to find an entry point named 'nInitialize' in DLL 'mscorlib.dll'.
  Source=ConsoleApp45
  StackTrace:
   at ConsoleApp45.DependentHandle.nInitialize(Object primary, Object secondary, IntPtr& dependentHandle)
   at ConsoleApp45.DependentHandle..ctor(Object primary, Object secondary) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 108
   at ConsoleApp45.ConditionalWeakTable`2.Container.CreateEntryNoResize(TKey key, TValue value) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 683
   at ConsoleApp45.ConditionalWeakTable`2.CreateEntry(TKey key, TValue value) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 573
   at ConsoleApp45.ConditionalWeakTable`2.Add(TKey key, TValue value) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 275
   at ConsoleApp45.Program.Main(String[] args) in C:\Users\shad\source\repos\ConsoleApp45\ConsoleApp45\Program.cs:line 33

无论是否在nInitialize方法上添加[MethodImplAttribute(MethodImplOptions.InternalCall)]特性,都会触发上述异常。

目前不确定是找错了DLL、实现方式有误,还是这种操作本身就不可行。能找到的P/Invoke相关资料都是调用开发者自行提供的C/C++库,而非CLR自带的原生方法。请问是否可以从C#中调用CLR内置的原生方法?如果可以,具体要如何操作?

注: 仅需要适配.NET Framework 4.5及以上版本。

2022-09-20更新(替代方案): 最终没有选择将ConditionalWeakTable<TKey, TValue>向后移植到.NET Framework和.NET Standard 2.0,而是为集合的每个元素使用弱事件返回自身引用,以此实现引用的"遍历"。不要求遍历顺序,因此该方案可以满足需求。

内容的提问来源于stack exchange,提问作者NightOwl888

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 00:06:05