Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Add NativeLibrary class #16409

Closed
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/mscorlib/System.Private.CoreLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<PropertyGroup Condition="'$(Platform)' == 'x86'">
<PlatformTarget>x86</PlatformTarget>
<BaseAddress>0x10000000</BaseAddress>
<DefineConstants>BIT32;$(DefineConstants)</DefineConstants>
<DefineConstants>BIT32;X86;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)' == 'arm'">
<PlatformTarget>arm</PlatformTarget>
Expand Down Expand Up @@ -149,6 +149,7 @@
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ICustomMarshaler.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\InvalidOleVariantTypeException.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\Marshal.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeLibrary.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\PInvokeMap.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\PInvokeMarshal.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\RuntimeEnvironment.cs" />
Expand Down Expand Up @@ -585,6 +586,7 @@
<Compile Include="$(BclSourcesRoot)\System\Globalization\GlobalizationMode.Unix.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\Versioning\CompatibilitySwitch.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Unix.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeLibrary.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="$(BclSourcesRoot)\Interop\Windows\Kernel32\Interop.GetSystemDirectoryW.cs" />
Expand All @@ -594,6 +596,7 @@
<Compile Include="$(BclSourcesRoot)\System\Globalization\GlobalizationMode.Windows.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Windows.cs" />
<Compile Include="$(BclSourcesRoot)\System\Environment.Windows.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeLibrary.Windows.cs" />
</ItemGroup>
<ItemGroup>
<!--
Expand Down Expand Up @@ -669,4 +672,4 @@
</ItemGroup>

<Import Project="GenerateCompilerResponseFile.targets" />
</Project>
</Project>
8 changes: 8 additions & 0 deletions src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,14 @@ public static void PrelinkAll(Type c)
}
}

internal static int NumParamBytes(RuntimeMethodInfo m, bool isForStdCallDelegate)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
return InternalNumParamBytes(m, isForStdCallDelegate);
}

[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern int InternalNumParamBytes(IRuntimeMethodInfo m, bool isForStdCallDelegate);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern /* struct _EXCEPTION_POINTERS* */ IntPtr GetExceptionPointers();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Runtime.InteropServices
{
// Contains UNIX-specific logic for NativeLibrary class.
Copy link
Member Author

@GrabYourPitchforks GrabYourPitchforks Feb 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, "UNIX" also includes OSX.

public unsafe sealed partial class NativeLibrary
{
// The allowed mask values for the DllImportSearchPath.
// Non-Windows sytems only allow AssemblyDirectory and LegacyBehavior.
private const uint AllowedDllImportSearchPathsMask = (uint)(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.LegacyBehavior);

// [DllImport] (NDirectMethodDesc::FindEntryPoint) doesn't allow lookup by ordinal on non-Windows.
private const bool AllowLocatingFunctionsByOrdinal = false;

// In UNIX, the CLR's HINSTANCE is actually a pointer to _MODSTRUCT.
// We can extract the underlying OS handle from that structure.
private IntPtr OperatingSystemHandle => ((_MODSTRUCT*)_hInstance)->dl_handle;

private static bool IsValidModuleHandle(IntPtr hModule)
{
// No validation other than checking for null.

return (hModule != IntPtr.Zero);
}

// This is a *partial* copy of include/pal/module.h so that we can extract the dl_handle.
[StructLayout(LayoutKind.Sequential)]
private struct _MODSTRUCT
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really a fan of this pattern, but this seemed safer than trying to have NativeLibrary.cpp reach deep into the PAL-specific header files. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could expose it as a PAL function, e.g. PAL_GetDlHandle(HMODULE hMod) and pinvoke into it here (or expose a wrapper that calls this function as a QCALL) to get rid of this exposure of PAL internals.

It actually seems that moving the IsValidModuleHandle to the native part of the implementation and exposing it as a QCALL would be better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the IsValidModuleHandle to the native part of the implementation

There is a lot of chattiness between the managed and native parts - there are 5 new FCalls/QCalls already. I think it would be better to have bulk of this implementation in C++, and not have this chattiness.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree.. I would either all(or mostly) implements in native code or all implements in managed code. but not in middle. It will help interop team maintain these codes in long term..

Copy link
Member Author

@GrabYourPitchforks GrabYourPitchforks Feb 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we were developing this I chatted with quite a few people who have a vested interest in this type's implementation, and the recommendations eventually came down to: (a) reuse as much code as practical; and (b) if new code must be created keep as much of it managed as possible, even at the expense of performance. This would make the code easier to understand and would benefit maintenance in the long run.

In practice the only real implementation logic that exists in C# is a managed equivalent to the C++ FindEntryPoint method. Tweaking and reusing the FindEntryPoint method directly was dismissed due to how much refactoring it would need. All other C# code is parameter validation / error handling / etc., with p/invokes back into the runtime to do the real work.

If you feel strongly about moving the managed FindEntryPoint logic back into C++ let me know and I'll defer to your judgment. Be aware that it will result in larger, more complex, less readable code, with minimal visible effects for consumers of this type. It also risks slipping this type from 2.1 to vNext.

{
internal IntPtr self;
internal IntPtr dl_handle;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Runtime.InteropServices
{
// Contains Windows-specific logic for NativeLibrary class.
public sealed partial class NativeLibrary
{
// The allowed mask values for the DllImportSearchPath.
// The high three bytes are passed as-is to the OS (which will check them for validity),
// and the low byte is allowed to contain AssemblyDirectory or LegacyBehavior.
private const uint AllowedDllImportSearchPathsMask = ~0xFFU | (uint)(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.LegacyBehavior);

// [DllImport] (NDirectMethodDesc::FindEntryPoint) allows lookup by ordinal on Windows.
private const bool AllowLocatingFunctionsByOrdinal = true;

// from libloaderapi.h
private const uint GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004;
private const uint GET_MODULE_HANDLE_EX_FLAG_PIN = 0x00000001;

// On Windows, the CLR's HINSTANCE is the underlying OS handle.
private IntPtr OperatingSystemHandle => _hInstance;

private static bool IsValidModuleHandle(IntPtr hModule)
{
// This method has two purposes: (a) it ensures that the provided module handle is indeed valid,
// and (b) it pins the module so that it can never be unloaded from the current process. The CLR
// expects modules to be pinned (see the call to BaseHolder<...>::Extract in NDirect::LoadLibraryModule),
// and we should enforce this invariant regardless of which code path ended up loading the module.

if (hModule == IntPtr.Zero)
{
return false;
}

if (!GetModuleHandleEx(
dwFlags: GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
lpModuleName: hModule, // module base address, not module name
phModule: out IntPtr baseAddress))
{
return false;
}

if (hModule != baseAddress)
{
return false;
}

// all checks succeeded
return true;
}

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683200(v=vs.85).aspx
[DllImport(Interop.Libraries.Kernel32, EntryPoint = "GetModuleHandleExW", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetModuleHandleEx(
[In] uint dwFlags,
[In] IntPtr lpModuleName,
[Out] out IntPtr phModule);
}
}
Loading