.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

