Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to address other problem scenarios previously solved by dllmap #37213

Closed
kg opened this issue May 31, 2020 · 14 comments
Closed

How to address other problem scenarios previously solved by dllmap #37213

kg opened this issue May 31, 2020 · 14 comments
Labels
area-System.Runtime.InteropServices question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@kg
Copy link
Member

kg commented May 31, 2020

With the transition to .NET 5, many problems people previously solved with mono+dllmap will need to be solved using pure .NET 5 (and potentially user-authored code).

We have NativeLibrary to handle some basic problems like "how do I find the appropriate .so file for this p/invoke" with effort from the end-user, but for other things dllmap was able to do it's not clear how to solve them in .NET 5. There are also some unfortunate limitations/downsides to NativeLibrary that may need to be fixed. I'll try to enumerate everything here, ideally we can eventually have solutions for all of this or have documentation for users who need to transition off dllmap so they can understand the path forward for their software. I worked with some end-users over the last day or two to puzzle out some solutions for their use case, and hit some roadblocks that motivated filing this issue.

Some functionality described in the dllmap documentation:

The <dllentry> directive can be used to map a specific dll/function pair to a different library and also a different function name
This is a very common scenario, on win32 you will often find standard library functions that are redirected from foo.dll to bar.dll, and there are scenarios where managed code needs to do it too. We need a solution for this for P/Invoke because a given API will not be in the same place across platforms, and the number of unique assemblies you'd have to compile (and dynamically load somehow based on target) is unsustainable.
The DLL remap part can be solved by NativeLibrary but only for 1:1 mappings where everything in A.dll is implemented in B.so, which isn't always true.
Also note that the 'different function name' scenario is a real-world problem: For one example, the ICU unicode library by default mangles its own function names based on version in order to support scenarios where two versions of ICU have been statically linked into the same executable. As a result, if you probe at runtime to find ICU and grab ICU v67, all of its entry point names have to be mangled dynamically to include '67'. This is not something you can fix as-is with NativeLibrary.

You use the <dllmap> directive to map shared libraries referenced by P/Invoke in your assemblies to a different shared library.
This appears to be fully solved by NativeLibrary, with the caveat that this occurs entirely at runtime, which means it is not AOT-friendly. For runtime environments like wasm the lack of AOT compatibility could be a serious issue, but I don't know if anyone other than me cares about that.

Both the <dllmap> and <dllentry> elements allow the following attributes which make it easy to use a single configuration file and support multiple operating systems and architectures with different mapping requirements:
os: This is the name of the operating system for which the mapping should be applied. Allowed values are: linux, osx, solaris, freebsd, openbsd, netbsd, windows, aix, hpux.
cpu This is the name of the architecture for which the mapping should be applied. Allowed values are: x86, x86-64, sparc, ppc, s390, s390x, arm, armv8 (AArch64), mips, alpha, hppa, ia64.
wordsize you can use this to differentiate between 32 and 64 bit systems. The possible values are : 32 and 64.

Selecting the appropriate .so/.dll file based on cpu and word size is something you can do with NativeLibrary, but if entry point names don't match you're out of luck. I don't think the entry point names varying based on platform is that common of a scenario, however, so this is probably fine, aside from the above-mentioned AOT issue.

Additional scenarios and issues worth considering that could be solved declaratively or programmatically:

Based on the characteristics of the current environment, you may want to dispatch a p/invoke to a different function in a DLL that uses a different instruction set. For example, an image processing library may have AVX, AVX2 and SSE versions of a given function.

You could have entire separate .dll files compiled with different /arch flags, but this quickly becomes unreasonable because you could end up with 8 different libraries that bloat your download when in practice only a few specific functions might have support for AVX2. In production software it is common to see _avx versions of specific functions like this when you profile them. Doing this branching on every single invoke via a wrapper is not only complicated, it has potential performance overhead. Being able to resolve this once automatically for each p/invoke (with a config file or user-authored resolve handler) would be ideal.

An end-user or developer consuming a third-party library may need to adjust its resolution behavior to support a new platform or compiled library.

For example, I pulled down a NuGet recently that wrapped an open-source native library. I later discovered that the NuGet only included native binaries for Windows, not Linux. Because pointing it to a .so file would normally require that the nuget's developer make the relevant changes, I had no way to fix this even if I compiled the relevant native binaries from source. If I could drop a .dllmap equivalent next to the nuget assembly perhaps I could have fixed this. Setting a custom resolve handler program/appdomain-wide would also work. (Perhaps there is a hack to do this with NativeLibrary I'm not aware of?)

Automatically applying dllmap-style remapping to an assembly at load or use time is difficult and potentially expensive due to C# limitations

Module initializers are not exposed to C#, so the closest thing you have is putting a .cctor on every class that contains pinvokes. This means that in theory, every method call or pinvoke on that class will have a .cctor check-and-initialize generated for it. While the branch for that check will predict well, it's still an indirect load and branch on every call. JITs could potentially patch all those branches out but in AOT they're there whether you like it or not.

If you try to avoid the .cctor via manual initialization, now library consumers all need to know about this problem and you can end up with bugs from forgetting to initialize it in the right places.

This gets worse when consuming multiple libraries that all need manual initialization or dependency trees that all need it. Developers will have to manually test for every scenario (or run tests for it on CI) because this behavior is all implemented at runtime instead of declaratively.

How do you implement __Internal

This isn't specifically dllmap related but it comes back around to what I mentioned about AOT above - some platforms like iOS or wasm will effectively require AOT compilation and reward static-linking. In that scenario, you can end up with an application where half of its pinvokes have been statically linked into the executable. My understanding is that previously, you would have solved this by setting the source library to __Internal. How do you solve this now, and how do you programmatically sense it to wire things up with NativeLibrary? The AOT'd code doesn't live inside the assembly so Assembly.Location isn't immediately going to be of use for you.

ccing @TheSpydog and @flibitijibibo because they've been struggling with these issues lately and can provide more detail.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Security untriaged New issue has not been triaged by the area owner labels May 31, 2020
@ghost
Copy link

ghost commented May 31, 2020

Tagging subscribers to this area: @bartonjs, @vcsjones, @krwq
Notify danmosemsft if you want to be subscribed.

@TheSpydog
Copy link
Contributor

Let me provide a concrete example of some of the problems described here.

The past few days I've been attempting to add proper .NET Core support to the FNA game framework, a C# project that makes heavy use of a small handful of native libraries for window creation/input, audio handling, and graphics API calls (OpenGL/DirectX/Metal/etc). Most of the DllImport externs for these libraries are not included in FNA itself, but rather in separate C# binding projects, such as SDL2-CS.

FNA predates .NET Core and has historically relied on Mono's DllMap feature for native library discovery on all its supported platforms. As you can see from FNA's dllmap config file, the native library names on macOS/Linux/BSD are substantially different from their Windows counterparts, so the automatic "try adding lib as a prefix" DLL name checking doesn't work here. This means that for CoreCLR we need to use the NativeLibrary API to load the appropriate library.

Thankfully basic DllMap-like renaming functionality is fairly easy to implement with NativeLibrary. The trick is figuring out where to initialize it, i.e. where to call NativeLibrary.SetDllImportResolver. My first instinct was to put the initialization code at the top of FNA's Game.cs class, which is the typical entry point for FNA applications. But it turns out that some people only use the graphics and/or audio components of the library, which don't require a Game instance to be created.

My next attempt was to call the NativeLibrary initialization code in all possible places within FNA that might serve as the entry point. Thankfully there are relatively few potential entry points, but this was already getting awkward since it meant that DllMap-loading code was spreading throughout the codebase. Instead of classes containing only logic relevant to their internal operation, they were starting to contain runtime bookkeeping.

However, this strategy was also fatally flawed, because it was making the assumption that the end user would call into FNA before they made any calls into FNA's dependencies. That quickly fell apart when I encountered some games that called into SDL2-CS first, before NativeLibrary had a chance to initialize. This is totally valid behavior on the game's part, but for us it's very problematic, because it leaves us with only two realistic options for NativeLibrary initialization:

  1. Use static constructors to make the NativeLibrary initialization calls. Not only for FNA, but for all of its dependencies. As mentioned in the OP, this costs us performance. Just how much performance is reduced is dependent on the runtime/platform, but regardless, having any additional overhead for extern calls that are made many, many times per frame is never a good thing. Especially since FNA supports .NET Framework, Mono, and (hopefully soon) .NET Core all from a single assembly. This means that with static constructors, we would be sacrificing performance on the other two runtimes for no reason at all. (It also means that every single static constructor that gets called after the first one is completely pointless, since the initialization has already been performed for the module.)
  2. Put the responsibility on the developer to initialize DllMap themselves, probably near the top of their Main function. I think this is a terrible solution, because it's effectively forcing the library consumer to work out a library-level implementation detail for themselves. It would be a better experience for everyone involved to just have the library take care of its business internally.

There is no clear solution to these problems. The only viable way forward that I can see is setting these mappings up before runtime, not during. My ideal scenario would be a straight-up implementation of Mono's DllMap feature into CoreCLR (maybe even implemented on top of NativeLibrary, but handled by the runtime and called at module init time), but even adding some of these configuration options to runtimeconfig.json might be sufficient.

@jkotas jkotas added area-System.Runtime.InteropServices question Answer questions and provide assistance, not an issue with source code or documentation. and removed area-System.Security labels May 31, 2020
@PathogenDavid
Copy link
Contributor

How do you implement __Internal

It's not the most ideal, but the suggested solution in the previous discussion of __Internal was to manually P/Invoke your OS-specific function to get a handle to the current module: #7267 (comment)

called at module init time

@TheSpydog Have you explored using module initializers for FNA?

Official support is planned for C# 9.0, but you can use them today through post-build tools like Fody or InjectModuleInitializer.

@TheSpydog
Copy link
Contributor

Have you explored using module initializers for FNA?

Yes, and thanks for the links. While this sounds generally useful, I don't think this is a feasible solution in our case, for the following reasons:

  1. For the sake of compatibility with older build systems and .NET Framework versions, we can't use C# 9, even once it's released. (We still have users building projects with VS2010, if you can believe it!)
  2. FNA's build process is extremely simple. The entire library can be (and often is) compiled with a simple Makefile, with no NuGet dependencies. Integrating post-build tools would add significant, unwanted complexity.
  3. Implementing it ourselves with direct IL manipulation is unfortunately off the table as well. For console ports, FNA uses a CIL->C++ transpiler that doesn't play nice with IL modification.

@jkotas
Copy link
Member

jkotas commented May 31, 2020

The directive can be used to map a specific dll/function pair to a different library and also a different function name
This is a very common scenario, on win32 you will often find standard library functions that are redirected from foo.dll to bar.dll,

It would be useful to have a concrete examples to discuss this on.

For one example, the ICU unicode library by default mangles its own function names based on version in order to support scenarios where two versions of ICU have been statically linked into the same executable.

ICU is used by the .NET runtime. We are very well aware of this ICU quirk. Another problem with ICU library is that it has very chatty APIs and paying the PInvoke overhead for each call is prohibitive. We solved both these problems by using a shim native library. dllmap would not be sufficient to solve this problem.

I pulled down a NuGet recently that wrapped an open-source native library. I later discovered that the NuGet only included native binaries for Windows, not Linux.

I agree that the current NuGet standard for distributing native libraries with NuGet packages is less than ideal. I would be nice to have a standard for distributing the sources for native libraries as part of the NuGet package and use these sources to recompile the native library as fallback when the right native asset is not available as precompiled. I believe other ecosystem (e.g. Python) have capability like this. This is independent problem from dllmap.

Because pointing it to a .so file would normally require that the nuget's developer make the relevant changes

If the library has the same APIs accross operating systems, this is not normally required. The default native library probing logic appends platform specific suffix as appropriate.

Based on the characteristics of the current environment, you may want to dispatch a p/invoke to a different function in a DLL that uses a different instruction set

You should be able to do this, without any additional overhead, using function pointers that are coming with C# 9.

Automatically applying dllmap-style remapping to an assembly at load
the closest thing you have is putting a .cctor on every class that contains pinvokes

It is a common best practice to have all PInvokes for the given library defined in one type. My recommendation would be to refactor the project to follow this best practice, and then it is pretty straightforward to install the custom DllImportResolver in the one type that contains the PInvokes.

some platforms like iOS or wasm will effectively require AOT compilation and reward static-linking

My recommendation would be to add switch for the AOT compiler that tells it which libraries are statically linked and not expected to be loaded from disk. This allows one to switch between static linking and dynamic linking per individual library without changing sources.

For the sake of compatibility with older build systems and .NET Framework versions

Is having two parallel builds or build systems an option?


To provide some context: The experience we want .NET users to have is that they add NuGet package to the project and it works. We have discussed dllmap in length and it is hard to see how to fit it into this experience. On top of that, dllmap has problematic security characteristics. It is equivalent to code and allows changing the control flow of the application in arbitrary ways, but it does not come with the same security features like ability to be signed.

@kg
Copy link
Member Author

kg commented May 31, 2020

The directive can be used to map a specific dll/function pair to a different library and also a different function name
This is a very common scenario, on win32 you will often find standard library functions that are redirected from foo.dll to bar.dll,

It would be useful to have a concrete examples to discuss this on.

It's been a while since I had to deal with it, but at at least one point the MSVC stdlib redirected some of its exports to other libraries. I know for sure that I ran into this with strdup and free because I wasted an entire day debugging it when it broke my local source builds of Firefox.

Also, since we were previously discussing ICU, the number of dynamic libraries and where the entry points live depends on platform - afaik windows is two DLLs, while I see at least 4 .so files on Linux. NativeLibrary 1:1 mapping of libraries does not handle this, you need per-entry-point mapping.

If the claim is that this is an unimportant use case that's fine, but it's a simple enough one that it shouldn't require an exhaustive list of scenarios where it happens... we move APIs like this around in the managed environment, it's not that implausible for it to happen on native.

For one example, the ICU unicode library by default mangles its own function names based on version in order to support scenarios where two versions of ICU have been statically linked into the same executable.

ICU is used by the .NET runtime. We are very well aware of this ICU quirk. Another problem with ICU library is that it has very chatty APIs and paying the PInvoke overhead for each call is prohibitive. We solved both these problems by using a shim native library. dllmap would not be sufficient to solve this problem.

If the solution is for everyone to write a native shim for every library they consume I guess that does count as a solution. It's not clear to me why dllmap is specifically not capable of addressing this.

I pulled down a NuGet recently that wrapped an open-source native library. I later discovered that the NuGet only included native binaries for Windows, not Linux.

I agree that the current NuGet standard for distributing native libraries with NuGet packages is less than ideal. I would be nice to have a standard for distributing the sources for native libraries as part of the NuGet package and use these sources to recompile the native library as fallback when the right native asset is not available as precompiled. I believe other ecosystem (e.g. Python) have capability like this. This is independent problem from dllmap.

Dllmap allows externally substituting relevant native libraries to address a missing dependency in a managed assembly you're consuming or adapt it to a new platform, as I mentioned. Doing this with NativeLibrary is far more convoluted because you can't reach in and modify the assembly to insert the appropriate setup code and/or cctors.

Because pointing it to a .so file would normally require that the nuget's developer make the relevant changes

If the library has the same APIs accross operating systems, this is not normally required. The default native library probing logic appends platform specific suffix as appropriate.

OK, it sounds like the solution here is that you come up with matching SOs for your platform and either rename them or generate appropriately named symlinks and .net 5 will magically fix it up? If that works, maybe that's good enough.

I had to use LD debug flags to observe the .net 5 behavior here but maybe I didn't read the docs properly.

Based on the characteristics of the current environment, you may want to dispatch a p/invoke to a different function in a DLL that uses a different instruction set

You should be able to do this, without any additional overhead, using function pointers that are coming with C# 9.

Is it realistic to expect everyone to migrate all their software to C# 9 when dllmap is an existing solution in the runtime they used to use? Is C#9 going to be well-baked enough on .NET 5 launch day such that not only is it easy to migrate but the tools developers use every day - resharper, etc - will understand things like fn pointers and module initializers?

Automatically applying dllmap-style remapping to an assembly at load
the closest thing you have is putting a .cctor on every class that contains pinvokes

It is a common best practice to have all PInvokes for the given library defined in one type. My recommendation would be to refactor the project to follow this best practice, and then it is pretty straightforward to install the custom DllImportResolver in the one type that contains the PInvokes.

This doesn't solve the cctor overhead problem and also doesn't solve the dependency graph problem. If one library consumes 3 other libraries they all need to be updated with their own custom resolver for all their pinvokes and the pinvokes all need to be relocated into a single class, ruining encapsulation and organization. If this is the cost people have to pay to adopt .net 5 so be it I guess, but it's hard to justify wasting the energy as long as netframework and mono are still functioning unless .net 5 is life-changing for end-users. I agree that this is much easier if you follow best practices, but most customers will not have the luxury of ensuring all their code and all their libraries are C#9 and conformant to current best practices.

some platforms like iOS or wasm will effectively require AOT compilation and reward static-linking

My recommendation would be to add switch for the AOT compiler that tells it which libraries are statically linked and not expected to be loaded from disk. This allows one to switch between static linking and dynamic linking per individual library without changing sources.

So we migrate configuration that used to be in dllmap to a bunch of compiler switches in a response file? I guess that's functionally not that different, it's just only works for AOT instead of for most scenarios.

For the sake of compatibility with older build systems and .NET Framework versions

Is having two parallel builds or build systems an option?

I mean, if it solves the problem we could have 8+ parallel builds and 3+ meta-build-systems. It's good enough for Chromium. I would hope .NET could do better, since in the good old days I could ship one managed .dll that worked on both mono and netframework with minimal assistance (i.e. from a dllmap), vs the current situation with .netcore which feels like "the only supported scenario is dotnet run" (I know this is no longer accurate, but every time I've tried to use netcore it's been my experience)

To provide some context: The experience we want .NET users to have is that they add NuGet package to the project and it works. We have discussed dllmap in length and it is hard to see how to fit it into this experience. On top of that, dllmap has problematic security characteristics. It is equivalent to code and allows changing the control flow of the application in arbitrary ways, but it does come with the same security features like ability to be signed.

It's not clear to me how dllmap is an obstacle to consuming nuget packages, though I can see how you would not consider it useful for that. I don't understand how being able to drop a dllmap into the folder is any less safe than the reality that you can drop a dll into the folder right now and have a more significant security impact than a dllmap file. At the point where an attacker can drop files into the application folder the application is already 100% compromised. Dllmap is less powerful than this because all it can do is redirect entry points. As you point out it can be signed (much like executables) so in both cases the solution there would be requiring that all executables and dllmaps be signed. Of course an attacker getting a signing key is game over, and I can attest that getting an authenticode certificate is not hard.

@jkotas
Copy link
Member

jkotas commented May 31, 2020

If the solution is for everyone to write a native shim for every library they consume

Yes, native shim is the appropriate solution in many cases (not everyone).

For the record, we have tried very hard to avoid the native shims during the early days of the .NET Core project. We have found that it is just not feasible and ended up on standardizing on shims for majority of the PInvokes done by .NET runtime libraries.

Dllmap allows externally substituting relevant native libraries to address a missing dependency in a managed assembly you're consuming

Instead of this, we want to strongly encourage people to make the proper fixes in the libraries so that it will just work for the next person.

@jkotas
Copy link
Member

jkotas commented Jun 1, 2020

I would be nice to have a standard for distributing the sources for native libraries as part of the NuGet package and use these sources to recompile the native library as fallback when the right native asset is not available as precompiled

I have opened NuGet/Home#9631 on this

@AaronRobinsonMSFT AaronRobinsonMSFT removed the untriaged New issue has not been triaged by the area owner label Jun 8, 2020
@AaronRobinsonMSFT AaronRobinsonMSFT added this to the Future milestone Jun 8, 2020
@CoffeeFlux
Copy link
Contributor

There's been discussion on __Internal in #7267, but I'll redirect discussion here.

From my perspective, the biggest gaps in functionality are __Internal and symbol name remapping.

First, __Internal. A user can implement this with the DllImportResolver snippet, but it is such a common use case in embedding, with a years-established pattern for dealing with it, that I don't think it's fair to ask customers to repeatedly do so. I do not relish telling users "here, copy this snippet into every project to get the functionality you want" on something so common—we are essentially punting on a common user problem that should be solved by the runtime, and instead accepting the status quo of "find the snippet in a random Github issue". __Internal continues to exist in Mono on mobile because the Xamarin products make heavy use of it, but privileging them does not seem very fair when others keep asking for this and it's been in framework Mono forever.

I think the main issue with __Internal is a single ambiguity: do you mean dlopen(NULL) or the runtime library? Historically in Mono these were usually one and the same, but we're now dynamically linking the runtime library in some cases (I forget what platform) so we have logic that checks both. This seems okay to me, but I'd love to hear other opinions. Just keep in mind that if we shore it up to mean only dlopen(NULL) this will be an extremely painful change for some users including Xamarin, similarly to when we changed from RTLD_GLOBAL (which was necessary! but still painful). Maybe worth looking at usage and getting a better understanding of which of the two is more common? I suspect it'll just be both, though.

Next, symbol name remapping. As Katelyn mentioned above, this functionality existed with dllmap but is lost with NativeLibrary. In addition to any scenarios she mentioned, I want to again bring up the Xamarin products, specifically iOS. As discussed previously over email with @jkotas, they have to remap objc_msgSend pinvokes to be able to catch Objective-C exceptions, and this isn't going away. They do not any alternative to dllmap for this, and so we've enabled the dllmap embedding APIs for those products. They are apparently not the only users who need this capability however, and if we want people to migrate to the new blessed managed patterns in NativeLibrary we need to make that actually possible. If discussion here would be easier with a proposed API surface, I don't mind mapping one out.

@jkotas
Copy link
Member

jkotas commented Jul 20, 2020

I think the main issue with __Internal is a single ambiguity: do you mean dlopen(NULL) or the runtime library

I believe that there is also third meaning that is "static linking". It is used for example here: https://github.com/grpc/grpc/blob/04cdb1826666e0de67cfd11c1f5214e0fd53a018/templates/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs.template#L81

they have to remap objc_msgSend pinvokes to be able to catch Objective-C exceptions, and this isn't going away

The official Xamarin documentation tells people to P/Invoke objc_msgSend directly and depend on the runtime to do the remapping for them: https://docs.microsoft.com/en-us/xamarin/ios/internals/objective-c-selectors. You are right that the entrypoint remapping is pretty much the only way to replicate the existing behavior. I look at this one as one-off compatibility quirk and not as a pattern that we would like to encourage in other places. If we were designing this as a proper managed API today, I do not think it is the pattern we would use.

Mono has historic quirks for Xamarin and it is ok to keep some of them even in .NET 5+ world to ease migration.

Similarly, CoreCLR has historic quirks for Windows and COM that we are not removing either. Speaking of P/Invoke entry-point remapping, CoreCLR.dll has a similar one-off entrypoint remapping compatibility quirk for kernel32!GetLastError:

BOOL HeuristicDoesThisLookLikeAGetLastErrorCall(LPBYTE pTarget)
.

@akoeplinger
Copy link
Member

akoeplinger commented Jul 21, 2020

I think the main issue with __Internal is a single ambiguity: do you mean dlopen(NULL) or the runtime library

I believe that there is also third meaning that is "static linking". It is used for example here: grpc/grpc@04cdb18/templates/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs.template#L81

I don't think that's a third meaning since the usage should be covered by the dlopen(NULL) meaning that @CoffeeFlux mentioned.

I think now that we support QCalls in mono (so there's no need for __Internal to mean "the runtime library" anymore afaik) I think we could remove the ambiguity and support only the dlopen(NULL) case.

@jkotas
Copy link
Member

jkotas commented Jul 21, 2020

For CoreCLR, QCalls are internal implementation detail that only works in CoreLib. They are intentionally not recognized anywhere else.

@AaronRobinsonMSFT
Copy link
Member

AaronRobinsonMSFT commented Jul 28, 2020

If discussion here would be easier with a proposed API surface, I don't mind mapping one out.

@CoffeeFlux I think the __Internal support (#7267) is something we should have a conversation about in .NET 6 and have marked it as such. If you wanted to start another .NET 6 effort to reimagine DlIMap I think that would be appropriate. It would be helpful to bring together feedback from the various issues we have heard about DllIMap and fold them into an official proposal.

@jkotas
Copy link
Member

jkotas commented Sep 5, 2020

The follow ups on this discussion are tracked by #7267 NuGet/Home#9631 . I do not see anything actionable left here.

@jkotas jkotas closed this as completed Sep 5, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Runtime.InteropServices question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

8 participants