├── .editorconfig ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Directory.Build.props ├── LICENSE.md ├── README.md ├── RockLib.EmbeddedNativeLibrary.sln ├── RockLib.EmbeddedNativeLibrary ├── CHANGELOG.md ├── DllInfo.cs ├── EmbeddedNativeLibrary.cs ├── GetFunctionPointerException.cs ├── ILibraryLoader.cs ├── LoadLibraryException.cs ├── MaybeIntPtr.cs ├── RockLib.EmbeddedNativeLibrary.csproj ├── RuntimeOS.cs ├── TargetRuntime.cs ├── UnixLibraryLoader.cs ├── WindowsLibraryLoader.cs └── appveyor.yml └── icon.png /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | csharp_indent_case_contents = true 10 | csharp_indent_switch_labels = true 11 | csharp_new_line_before_catch = true 12 | csharp_new_line_before_else = true 13 | csharp_new_line_before_finally = true 14 | csharp_new_line_before_members_in_anonymous_types = false 15 | csharp_new_line_before_members_in_object_initializers = false 16 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 17 | csharp_new_line_between_query_expression_clauses = true 18 | csharp_prefer_braces = false:suggestion 19 | csharp_prefer_simple_default_expression = true:suggestion 20 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 21 | csharp_preserve_single_line_blocks = true 22 | csharp_preserve_single_line_statements = true 23 | csharp_space_after_cast = false 24 | csharp_space_after_colon_in_inheritance_clause = true 25 | csharp_space_after_keywords_in_control_flow_statements = true 26 | csharp_space_before_colon_in_inheritance_clause = true 27 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 28 | csharp_space_between_method_call_name_and_opening_parenthesis = false 29 | csharp_space_between_method_call_parameter_list_parentheses = false 30 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_parameter_list_parentheses = false 32 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 33 | csharp_style_expression_bodied_accessors = true:suggestion 34 | csharp_style_expression_bodied_constructors = false:suggestion 35 | csharp_style_expression_bodied_methods = false:suggestion 36 | csharp_style_expression_bodied_properties = true:suggestion 37 | csharp_style_inlined_variable_declaration = true:suggestion 38 | csharp_style_var_elsewhere = true:suggestion 39 | csharp_style_var_for_built_in_types = true:suggestion 40 | csharp_style_var_when_type_is_apparent = true:suggestion 41 | dotnet_sort_system_directives_first = false 42 | dotnet_style_explicit_tuple_names = true:suggestion 43 | dotnet_style_object_initializer = true:suggestion 44 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 45 | dotnet_style_predefined_type_for_member_access = true:suggestion 46 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 47 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 48 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 49 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 50 | dotnet_style_qualification_for_field = false:suggestion 51 | dotnet_style_qualification_for_method = false:suggestion 52 | dotnet_style_qualification_for_property = false:suggestion 53 | 54 | 55 | # Analyzer Configuration 56 | # These are rules we want to either ignore or have set as suggestion or info 57 | 58 | # CA1014: Mark assemblies with CLSCompliant 59 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 60 | dotnet_diagnostic.CA1014.severity = none 61 | 62 | # CA1725: Parameter names should match base declaration 63 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 64 | dotnet_diagnostic.CA1725.severity = suggestion 65 | 66 | # CA2227: Collection properties should be read only 67 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 68 | dotnet_diagnostic.CA2227.severity = suggestion 69 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 12 | 13 | ## Type of change: 14 | 15 | 1. Non-functional change (e.g. documentation changes, removing unused `using` directives, renaming local variables, etc) 16 | 2. Bug fix (non-breaking change that fixes an issue) 17 | 3. New feature (non-breaking change that adds functionality) 18 | 4. Breaking change (fix or feature that could cause existing functionality to not work as expected) 19 | 20 | ## Checklist: 21 | 22 | - Have you reviewed your own code? Do you understand every change? 23 | - Are you following the [contributing guidelines](../blob/main/CONTRIBUTING.md)? 24 | - Have you added tests that prove your fix is effective or that this feature works? 25 | - New and existing unit tests pass locally with these changes? 26 | - Have you made corresponding changes to the documentation? 27 | - Will this change require an update to an example project? (if so, create an issue and link to it) 28 | 29 | --- 30 | 31 | _[Reviewer guidelines](../blob/main/CONTRIBUTING.md#reviewing-changes)_ 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | .nuget/ 4 | _ReSharper.* 5 | packages/ 6 | artifacts/ 7 | *.user 8 | *.suo 9 | *.userprefs 10 | *DS_Store 11 | *.sln.ide 12 | .vs/ 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at RockLibSupport@quickenloans.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to RockLib 2 | 3 | Please take a moment to review this document in order to make the contribution 4 | process easy and effective for everyone involved. 5 | 6 | Following these guidelines helps to communicate that you respect the time of 7 | the developers managing and developing this open source project. In return, 8 | they should reciprocate that respect in addressing your issue, assessing 9 | changes, and helping you finalize your pull requests. 10 | 11 | As for everything else in the project, the contributions to RockLib are governed by our [Code of Conduct](CODE_OF_CONDUCT.md). 12 | 13 | 14 | ## Using the issue tracker 15 | 16 | First things first: **Do NOT report security vulnerabilities in public issues!** Please disclose responsibly by letting [the RockLib team](mailto:RockLibSupport@quickenloans.com?subject=Security) know upfront. We will assess the issue as soon as possible on a best-effort basis and will give you an estimate for when we have a fix and release available for an eventual public disclosure. 17 | 18 | The GitHub issue tracker is the preferred channel for [bug reports](#bugs), 19 | [features requests](#features) and [submitting pull 20 | requests](#pull-requests). 21 | 22 | 23 | ## Bug reports 24 | 25 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 26 | Good bug reports are extremely helpful - thank you! 27 | 28 | Guidelines for bug reports: 29 | 30 | 1. **Use the GitHub issue search** — check if the issue has already been 31 | reported. 32 | 33 | 2. **Check if the issue has been fixed** — try to reproduce it using the 34 | latest `master` branch in the repository. 35 | 36 | 3. **Isolate the problem** — ideally create a reduced test case. 37 | 38 | A good bug report shouldn't leave others needing to chase you up for more 39 | information. Please try to be as detailed as possible in your report. What is 40 | your environment? What steps will reproduce the issue? What OS experiences the 41 | problem? What would you expect to be the outcome? All these details will help 42 | people to fix any potential bugs. 43 | 44 | Example: 45 | 46 | > Short and descriptive example bug report title 47 | > 48 | > A summary of the issue and the browser/OS environment in which it occurs. If 49 | > suitable, include the steps required to reproduce the bug. 50 | > 51 | > 1. This is the first step 52 | > 2. This is the second step 53 | > 3. Further steps, etc. 54 | > 55 | > `` - a link to the reduced test case 56 | > 57 | > Any other information you want to share that is relevant to the issue being 58 | > reported. This might include the lines of code that you have identified as 59 | > causing the bug, and potential solutions (and your opinions on their 60 | > merits). 61 | 62 | 63 | ## Feature requests 64 | 65 | Feature requests are welcome. But take a moment to find out whether your idea 66 | fits with the scope and aims of the project. It's up to *you* to make a strong 67 | case to convince the project's developers of the merits of this feature. Please 68 | provide as much detail and context as possible. 69 | 70 | 71 | ## Pull requests 72 | 73 | Good pull requests - patches, improvements, new features - are a fantastic 74 | help. They should remain focused in scope and avoid containing unrelated 75 | commits. 76 | 77 | **Please ask first** before embarking on any significant pull request (e.g. 78 | implementing features, refactoring code), otherwise you risk spending a lot of 79 | time working on something that the project's developers might not want to merge 80 | into the project. 81 | 82 | 83 | ### For Contributors 84 | 85 | If you have never created a pull request before, welcome :tada: :smile: [Here is a great tutorial](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) 86 | on how to create a pull request.. 87 | 88 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 89 | and configure the remotes: 90 | 91 | ```bash 92 | # Clone your fork of the repo into the current directory 93 | git clone https://github.com// 94 | # Navigate to the newly cloned directory 95 | cd 96 | # Assign the original repo to a remote called "upstream" 97 | git remote add upstream https://github.com/RockLib/ 98 | ``` 99 | 100 | 2. If you cloned a while ago, get the latest changes from upstream: 101 | 102 | ```bash 103 | git checkout master 104 | git pull upstream master 105 | ``` 106 | 107 | 3. Create a new topic branch (off the main project development branch) to 108 | contain your feature, change, or fix: 109 | 110 | ```bash 111 | git checkout -b 112 | ``` 113 | 114 | 4. Please follow Chris Beams' [seven rules of a great Git commit message](https://chris.beams.io/posts/git-commit/#seven-rules): 115 | 116 | 1. [Separate subject from body with a blank line](https://chris.beams.io/posts/git-commit/#separate) 117 | 2. [Limit the subject line to 50 characters](https://chris.beams.io/posts/git-commit/#limit-50) 118 | 3. [Capitalize the subject line](https://chris.beams.io/posts/git-commit/#capitalize) 119 | 4. [Do not end the subject line with a period](https://chris.beams.io/posts/git-commit/#end) 120 | 5. [Use the imperative mood in the subject line](https://chris.beams.io/posts/git-commit/#imperative) 121 | 6. [Wrap the body at 72 characters](https://chris.beams.io/posts/git-commit/#wrap-72) 122 | 7. [Use the body to explain what and why vs. how](https://chris.beams.io/posts/git-commit/#why-not-how) 123 | 124 | 5. Make sure to update, or add to the tests when appropriate. 125 | 126 | 6. If you added or changed a feature, make sure to document it accordingly in 127 | the `README.md` file. 128 | 129 | 7. Push your topic branch up to your fork: 130 | 131 | ```bash 132 | git push origin 133 | ``` 134 | 135 | 8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 136 | with a clear title and description. 137 | 138 | 139 | **IMPORTANT**: By submitting a patch, you agree to license your work under the 140 | same license as that used by the project. 141 | 142 | ## Maintainers 143 | 144 | If you have commit access, please follow this process for merging patches and cutting new releases. 145 | 146 | ### Reviewing changes 147 | 148 | 1. Check that a change is within the scope and philosophy of the component. 149 | 2. Check that a change has any necessary tests. 150 | 3. Check that a change has any necessary documentation. 151 | 4. If there is anything you don’t like, leave a comment below the respective 152 | lines and submit a "Request changes" review. Repeat until everything has 153 | been addressed. 154 | 5. If you are not sure about something, mention specific people for help in a 155 | comment. 156 | 6. If there is only a tiny change left before you can merge it and you think 157 | it’s best to fix it yourself, do so and leave a comment about it so the 158 | author and others will know. 159 | 7. Once everything looks good, add an "Approve" review. Don’t forget to say 160 | something nice 👏🐶💖✨ 161 | 8. If the commit messages follow Chris Beams' [seven rules of a great Git commit 162 | message](https://chris.beams.io/posts/git-commit/#seven-rules): 163 | 164 | 1. Use the "Merge pull request" button to merge the pull request. 165 | 2. Done! You are awesome! Thanks so much for your help 🤗 166 | 167 | 9. If the commit messages _do not_ follow our conventions: 168 | 169 | 1. Use the "Squash and merge" button to clean up the commits and merge at 170 | the same time: ✨🎩 171 | 2. Add a new commit subject and body. 172 | 173 | --- 174 | 175 | *This document is based on the [contributing](https://github.com/hoodiehq/hoodie/blob/master/CONTRIBUTING.md) document from the [Hoodie](https://github.com/hoodiehq/hoodie) project.* 176 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | AllEnabledByDefault 7 | latest 8 | enable 9 | net6.0;netcoreapp3.1;net48 10 | NU1603,NU1701 11 | true 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2021 Rocket Mortgage 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :warning: Deprecation Warning :warning: 2 | 3 | This library has been deprecated and will no longer receive updates. 4 | 5 | --- 6 | 7 | RockLib has been a cornerstone of our open source efforts here at Rocket Mortgage, and it's played a significant role in our journey to drive innovation and collaboration within our organization and the open source community. It's been amazing to witness the collective creativity and hard work that you all have poured into this project. 8 | 9 | However, as technology relentlessly evolves, so must we. The decision to deprecate this library is rooted in our commitment to staying at the cutting edge of technological advancements. While this chapter is ending, it opens the door to exciting new opportunities on the horizon. 10 | 11 | We want to express our heartfelt thanks to all the contributors and users who have been a part of this incredible journey. Your contributions, feedback, and enthusiasm have been invaluable, and we are genuinely grateful for your dedication. 🚀 12 | 13 | --- 14 | 15 | ## RockLib.EmbeddedNativeLibrary 16 | 17 | Consuming third-party native DLLs (usually C libraries) can be tricky in .NET - especially when your project is deployed as a NuGet package. The RockLib.EmbeddedNativeLibrary NuGet package makes this sort of native interop easy. 18 | 19 | ```powershell 20 | PM> Install-Package RockLib.EmbeddedNativeLibrary 21 | ``` 22 | 23 | ## Problem 24 | 25 | The "normal" way to use a native DLL is to declare an `extern` function, also known as P/Invoke, like this: 26 | 27 | ```csharp 28 | [DllImport("libsodium.dll", EntryPoint = "sodium_init", CallingConvention = CallingConvention.Cdecl)] 29 | private static extern void SodiumInit(); 30 | ``` 31 | 32 | Since `libsodium.dll` is not part of the operating system (like `kernel32.dll` or `user32.dll`), the DLL file will need to be in the same directory as the assembly where the extern function is defined - just like a .NET DLL. However, unlike a .NET DLL, a native DLL cannot be referenced by a .NET project. The implications of this difference are significant. 33 | 34 | Since the native DLL cannot be referenced by a .NET project, it isn't recognized by MSBuild or other build tools. That means that the native DLL won't be copied to a build's output directory. This means that the application will fail when it tries to invoke the extern function. 35 | 36 | ## Solution 37 | 38 | 1. Add the `RockLib.EmbeddedNativeLibrary` nuget package to your project. 39 | 1. Add the native DLL to the project as an [embedded resource](https://support.microsoft.com/en-us/kb/319292). 40 | 1. Create an instance of `EmbeddedNativeLibrary`, and call its `GetDelegate` method to obtain a delegate that invokes that native function. 41 | 42 | 1 and 2 are pretty self-explanatory. But 3... not so much. An example should help. The following class exposes libsodium's `crypto_secretbox` method (the numbers refer to the descriptions below): 43 | 44 | ```csharp 45 | public static class Sodium 46 | { 47 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] // 2 48 | private delegate int SecretBoxDelegate( 49 | byte[] buffer, byte[] payload, long payloadLength, byte[] nonce, byte[] key); // 1 50 | 51 | private static readonly SecretBoxDelegate _cryptoSecretbox; 52 | 53 | static Sodium() 54 | { 55 | var sodiumLibrary = new EmbeddedNativeLibrary( 56 | "sodium", 57 | new DllInfo("MyLibrary.Native64.libsodium.dll", "MyLibrary.Native64.msvcr120.dll"), 58 | new DllInfo("MyLibrary.Native32.libsodium.dll", "MyLibrary.Native32.msvcr120.dll")); // 3 59 | _cryptoSecretbox = sodiumLibrary.GetDelegate("crypto_secretbox"); // 4 60 | } 61 | 62 | public static int crypto_secretbox(byte[] buffer, byte[] message, long messageLength, byte[] nonce, byte[] key) 63 | { 64 | return _cryptoSecretbox(buffer, message, messageLength, nonce, key); // 5 65 | } 66 | } 67 | ``` 68 | 69 | There are several things going on here. 70 | 71 | 1. Declare a non-generic delegate that match the native function's signature. 72 | - You'll get a run-time error if you try to use a generic delegate. (Why? See the Exceptions section in [this MSDN article](https://msdn.microsoft.com/en-us/library/vstudio/zdx6dyyh.aspx) for details.) 73 | 2. Decorate that delegate with an `[UnmanagedFunctionPointer]` attribute. 74 | - You may get a run-time error if you don't decorate the delegate with this attribute. 75 | - You'll need to know the calling convention of the native function (libsodium in the example uses the CDECL calling convention). 76 | 3. Create an instance of `EmbeddedNativeLibrary`, passing it the name of the library, and one or more `DllInfo` objects. 77 | - A `DllInfo` object allows you to specify resource name of the DLL 78 | - Additional DLLs may also be specified when the primary DLL has dependencies on another DLLs. 79 | - All DLLs should all target the same architecture (x86 or x64). 80 | - `EmbeddedNativeLibrary` is able to handle multiple architectures by passing multiple `DllInfo` objects into its constructor. However, it doesn't actually track or check the architecture of the embedded DLLs. During loading, this is what `EmbeddedNativeLibrary` does: 81 | - Attempt to load the DLL specified by the first `DllInfo`. 82 | - If that DLL cannot be loaded, try the DLL specified by the second `DllInfo`. 83 | - Keep going until a DLL is successfully loaded. 84 | - If no DLL is successfully loaded, throw an exception. 85 | 4. Call the `GetDelegate` method, caching the resulting delegate in a private field. 86 | 5. Invoke the cached delegate. 87 | - _This_ is what you've wanted all along - a delegate that, when invoked, calls the native function. 88 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.EmbeddedNativeLibrary", "RockLib.EmbeddedNativeLibrary\RockLib.EmbeddedNativeLibrary.csproj", "{F41993CE-A758-4B5C-A1CF-98553D02E697}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{13CBFA37-FC07-4283-8604-E6AA223C710B}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md 12 | CONTRIBUTING.md = CONTRIBUTING.md 13 | Directory.Build.props = Directory.Build.props 14 | LICENSE.md = LICENSE.md 15 | README.md = README.md 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {F41993CE-A758-4B5C-A1CF-98553D02E697}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {F41993CE-A758-4B5C-A1CF-98553D02E697}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {F41993CE-A758-4B5C-A1CF-98553D02E697}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {F41993CE-A758-4B5C-A1CF-98553D02E697}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | GlobalSection(ExtensibilityGlobals) = postSolution 33 | SolutionGuid = {E81D2A07-F032-4B50-BAEE-0E6D8C3D141D} 34 | EndGlobalSection 35 | EndGlobal 36 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # RockLib.EmbeddedNativeLibrary Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 3.0.0 - 2022-02-07 9 | 10 | #### Added 11 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency. 12 | 13 | #### Changed 14 | - Supported targets: net6.0, netcoreapp3.1, and net48. 15 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values. 16 | 17 | ## 2.0.4 - 2021-08-12 18 | 19 | #### Changed 20 | 21 | - Changes "Quicken Loans" to "Rocket Mortgage". 22 | 23 | ## 2.0.3 - 2021-05-10 24 | 25 | #### Added 26 | 27 | - Adds SourceLink to nuget package. 28 | 29 | ---- 30 | 31 | **Note:** Release notes in the above format are not available for earlier versions of 32 | RockLib.EmbeddedNativeLibrary. What follows below are the original release notes. 33 | 34 | ---- 35 | 36 | ## 2.0.2 37 | 38 | ## v2.0.1 39 | 40 | ## v2.0.0 41 | 42 | ## v1.2.2 43 | 44 | ## v1.2.1 45 | 46 | ## v1.2.0 47 | 48 | ## v1.1.2 49 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/DllInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | 5 | namespace RockLib.Interop 6 | { 7 | /// 8 | /// Contains resource names for the DLLs that are embedded in this assembly. The DLLs 9 | /// must all be of the same architecture (x86 or x64). 10 | /// 11 | public sealed class DllInfo 12 | { 13 | private static readonly ReadOnlyCollection _assemblyManifestResourceNames = typeof(DllInfo).Assembly.GetManifestResourceNames().ToList().AsReadOnly(); 14 | 15 | private readonly TargetRuntime _targetRuntime; 16 | private readonly string _resourceName; 17 | private readonly string[] _additionalResourceNames; 18 | 19 | /// 20 | /// Initializes a new instance of the class, assuming the target runtime to be 21 | /// . 22 | /// 23 | /// The resource name of the main DLL to be loaded. 24 | /// 25 | /// The resource names of any additional DLLs that neede to be loaded. 26 | /// 27 | /// 28 | /// is null. 29 | /// 30 | /// 31 | /// is empty. 32 | /// or 33 | /// is not found in this assembly's manifest resource names. 34 | /// or 35 | /// has any null elements. 36 | /// or 37 | /// has any empty elements. 38 | /// or 39 | /// has any elements that are not found in this assembly's manifest resource names. 40 | /// 41 | public DllInfo(string resourceName, params string[] additionalResourceNames) 42 | : this(TargetRuntime.Windows, resourceName, additionalResourceNames) 43 | { 44 | } 45 | 46 | /// 47 | /// Initializes a new instance of the class. 48 | /// 49 | /// The runtime that this targets. 50 | /// The resource name of the main DLL to be loaded. 51 | /// 52 | /// The resource names of any additional DLLs that neede to be loaded. 53 | /// 54 | /// 55 | /// is null. 56 | /// 57 | /// 58 | /// is empty. 59 | /// or 60 | /// is not found in this assembly's manifest resource names. 61 | /// or 62 | /// has any null elements. 63 | /// or 64 | /// has any empty elements. 65 | /// or 66 | /// has any elements that are not found in this assembly's manifest resource names. 67 | /// 68 | public DllInfo(TargetRuntime targetRuntime, string resourceName, params string[] additionalResourceNames) 69 | { 70 | if (resourceName == null) throw new ArgumentNullException(nameof(resourceName)); 71 | if (resourceName.Length == 0) throw new ArgumentException("'resourceName' must not be empty.", nameof(resourceName)); 72 | if (!_assemblyManifestResourceNames.Contains(resourceName)) 73 | { 74 | var resourceNames = string.Join(", ", _assemblyManifestResourceNames.Select(n => "'" + n + "'")); 75 | throw new ArgumentException($"Resource '{resourceName}' was not found in the assembly manifest resource names: {resourceNames}", nameof(resourceName)); 76 | } 77 | 78 | if (additionalResourceNames != null) 79 | { 80 | foreach (var additionalResourceName in additionalResourceNames) 81 | { 82 | if (additionalResourceName == null) throw new ArgumentException("Elements of 'additionalResourceNames' must not be null.", nameof(additionalResourceNames)); 83 | if (additionalResourceName.Length == 0) throw new ArgumentException("Elements of 'additionalResourceNames' must not be empty.", nameof(additionalResourceNames)); 84 | if (!_assemblyManifestResourceNames.Contains(additionalResourceName)) 85 | { 86 | var resourceNames = string.Join(", ", _assemblyManifestResourceNames.Select(n => "'" + n + "'")); 87 | throw new ArgumentException($"Additional resource '{additionalResourceName}' was not found in the assembly manifest resource names: {resourceNames}", 88 | nameof(additionalResourceNames)); 89 | } 90 | } 91 | } 92 | 93 | _targetRuntime = targetRuntime; 94 | _resourceName = resourceName; 95 | _additionalResourceNames = additionalResourceNames ?? Array.Empty(); 96 | } 97 | 98 | /// 99 | /// Gets the runtime that this targets. 100 | /// 101 | public TargetRuntime TargetRuntime 102 | { 103 | get { return _targetRuntime; } 104 | } 105 | 106 | /// 107 | /// Gets the resource name of the main DLL to be loaded. 108 | /// 109 | public string ResourceName 110 | { 111 | get { return _resourceName; } 112 | } 113 | 114 | /// 115 | /// Gets the resource names of any additional DLLs that need to be loaded. 116 | /// 117 | #pragma warning disable CA1819 // Properties should not return arrays 118 | public string[] AdditionalResourceNames 119 | #pragma warning restore CA1819 // Properties should not return arrays 120 | { 121 | get { return _additionalResourceNames; } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/EmbeddedNativeLibrary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Security.Cryptography; 9 | using System.Text; 10 | using System.Text.RegularExpressions; 11 | 12 | namespace RockLib.Interop 13 | { 14 | /// 15 | /// A class that provides access to the functions of a native DLL when the DLL is 16 | /// embedded as a resource in the same assembly that 17 | /// is defined. 18 | /// 19 | public sealed partial class EmbeddedNativeLibrary : IDisposable 20 | { 21 | private const bool _defaultPreferEmbeddedOverInstalled = true; 22 | 23 | private static readonly RuntimeOS _runtimeOS = GetRuntimeOS(); 24 | private static readonly ILibraryLoader _libraryLoader = GetLibraryLoader(_runtimeOS); 25 | 26 | private readonly Lazy _libraryPointer; 27 | 28 | /// 29 | /// Loads the native library defined by a list of objects. 30 | /// 31 | /// The name of the library. 32 | /// A collection of objects. 33 | /// True, if the native library was loaded, or false if the library failed to load. 34 | public static bool Load(string libraryName, params DllInfo[] dllInfos) 35 | { 36 | return Load(libraryName, _defaultPreferEmbeddedOverInstalled, dllInfos); 37 | } 38 | 39 | /// 40 | /// Loads the native library defined by a list of objects. 41 | /// 42 | /// The name of the library. 43 | /// 44 | /// If true, loading the embedded native library is attempted first and if it fails, then loading 45 | /// the native library from the operating system's default load paths is attempted. If false, 46 | /// the installed library is attempted first and the embedded library is attempted second. 47 | /// 48 | /// A collection of objects. 49 | /// True, if the native library was loaded, or false if the library failed to load. 50 | public static bool Load(string libraryName, bool preferEmbeddedOverInstalled, params DllInfo[] dllInfos) 51 | { 52 | if (dllInfos != null && dllInfos.Any(info => info.TargetRuntime == TargetRuntime.Linux || info.TargetRuntime == TargetRuntime.Mac)) 53 | { 54 | throw new ArgumentException("Embedding a Mac or Linux native library is not supported with the Load method: one or more DllInfo object had a TargetRuntime with a non-windows value.", nameof(dllInfos)); 55 | } 56 | 57 | if (_runtimeOS != RuntimeOS.Windows) 58 | { 59 | return false; 60 | } 61 | 62 | using var library = new EmbeddedNativeLibrary(libraryName, preferEmbeddedOverInstalled, dllInfos!); 63 | var libraryPointer = library._libraryPointer.Value; 64 | return libraryPointer != IntPtr.Zero; 65 | } 66 | 67 | /// 68 | /// Initializes a new instance of the class. 69 | /// 70 | /// The name of the library. 71 | /// 72 | /// A collection of objects that describe the native library. 73 | /// 74 | /// 75 | /// is null. 76 | /// or 77 | /// is null. 78 | /// 79 | /// 80 | /// is empty. 81 | /// or 82 | /// is empty. 83 | /// 84 | public EmbeddedNativeLibrary(string libraryName, params DllInfo[] dllInfos) 85 | : this(libraryName, _defaultPreferEmbeddedOverInstalled, dllInfos) 86 | { 87 | } 88 | 89 | /// 90 | /// Initializes a new instance of the class. 91 | /// 92 | /// The name of the library. 93 | /// 94 | /// If true, loading the embedded native library is attempted first and if it fails, then loading 95 | /// the native library from the operating system's default load paths is attempted. If false, 96 | /// the installed library is attempted first and the embedded library is attempted second. 97 | /// 98 | /// 99 | /// A collection of objects that describe the native library. 100 | /// 101 | /// 102 | /// is null. 103 | /// or 104 | /// is null. 105 | /// 106 | /// 107 | /// is empty. 108 | /// or 109 | /// is empty. 110 | /// 111 | public EmbeddedNativeLibrary(string libraryName, bool preferEmbeddedOverInstalled, params DllInfo[] dllInfos) 112 | { 113 | if (libraryName == null) throw new ArgumentNullException(nameof(libraryName)); 114 | if (dllInfos == null) throw new ArgumentNullException(nameof(dllInfos)); 115 | if (libraryName.Length == 0) throw new ArgumentException("'libraryName' must not be empty.", nameof(libraryName)); 116 | if (dllInfos.Length == 0) throw new ArgumentException("'dllInfos' must not be empty.", nameof(dllInfos)); 117 | 118 | _libraryPointer = new Lazy(() => 119 | { 120 | var exceptions = new List(); 121 | 122 | IntPtr libraryPointer; 123 | 124 | if (preferEmbeddedOverInstalled) 125 | { 126 | libraryPointer = LoadFromDllInfos(libraryName, dllInfos, exceptions); 127 | if (libraryPointer != IntPtr.Zero) 128 | { 129 | return libraryPointer; 130 | } 131 | } 132 | 133 | libraryPointer = LoadFromInstall(libraryName); 134 | if (libraryPointer != IntPtr.Zero) 135 | { 136 | return libraryPointer; 137 | } 138 | 139 | if (!preferEmbeddedOverInstalled) 140 | { 141 | libraryPointer = LoadFromDllInfos(libraryName, dllInfos, exceptions); 142 | if (libraryPointer != IntPtr.Zero) 143 | { 144 | return libraryPointer; 145 | } 146 | } 147 | 148 | throw new AggregateException( 149 | $"Unable to load library from resources: {string.Join(", ", dllInfos.Select(dll => dll.ResourceName))}", 150 | exceptions.ToArray()); 151 | }); 152 | } 153 | 154 | private static IntPtr LoadFromDllInfos(string libraryName, DllInfo[] dllInfos, List exceptions) 155 | { 156 | foreach (var dllInfo in dllInfos.Where(info => RuntimeMatchesTarget(info.TargetRuntime))) 157 | { 158 | var libraryPath = GetLibraryPath(libraryName, dllInfo); 159 | var maybePointer = _libraryLoader.LoadLibrary(libraryPath); 160 | 161 | if (maybePointer.HasValue) 162 | { 163 | return maybePointer.Value; 164 | } 165 | 166 | exceptions.Add(new AggregateException( 167 | $"The load library operation for '{dllInfo.ResourceName}' failed and reported {maybePointer.Exceptions.Length} exception{(maybePointer.Exceptions.Length > 1 ? "s" : "")}.", 168 | maybePointer.Exceptions)); 169 | } 170 | 171 | return IntPtr.Zero; 172 | } 173 | 174 | private static IntPtr LoadFromInstall(string libraryName) 175 | { 176 | foreach (var installPath in _libraryLoader.GetInstallPathCandidates(libraryName)) 177 | { 178 | var maybePointer = _libraryLoader.LoadLibrary(installPath); 179 | 180 | if (maybePointer.HasValue) 181 | { 182 | return maybePointer.Value; 183 | } 184 | } 185 | 186 | return IntPtr.Zero; 187 | } 188 | 189 | private static bool RuntimeMatchesTarget(TargetRuntime targetRuntime) 190 | { 191 | return targetRuntime switch 192 | { 193 | TargetRuntime.Windows => _runtimeOS == RuntimeOS.Windows, 194 | TargetRuntime.Win32 => _runtimeOS == RuntimeOS.Windows && IntPtr.Size == 4, 195 | TargetRuntime.Win64 => _runtimeOS == RuntimeOS.Windows && IntPtr.Size == 8, 196 | TargetRuntime.Mac => _runtimeOS == RuntimeOS.Mac, 197 | TargetRuntime.Linux => _runtimeOS == RuntimeOS.Linux, 198 | _ => false, 199 | }; 200 | } 201 | 202 | /// 203 | /// Finalizes an instance of the class. 204 | /// 205 | ~EmbeddedNativeLibrary() 206 | { 207 | FreeLibrary(); 208 | } 209 | 210 | /// 211 | /// Unloads the native libarary, rendering any functions created by the 212 | /// method unusable. 213 | /// 214 | public void Dispose() 215 | { 216 | FreeLibrary(); 217 | GC.SuppressFinalize(this); 218 | } 219 | 220 | /// 221 | /// Gets a delegate that executes the native function identified by 222 | /// . 223 | /// 224 | /// The type of the delegate. 225 | /// The name of the native function. 226 | /// A delegate that executes the native function. 227 | /// 228 | /// is null. 229 | /// 230 | /// 231 | /// is empty. 232 | /// 233 | /// 234 | /// TDelegate is not delegate. 235 | /// 236 | /// 237 | /// Unable to load the native library. 238 | /// or 239 | /// Unable to get a pointer to the function. 240 | /// 241 | public TDelegate GetDelegate(string functionName) 242 | { 243 | ValidateGetDelegate(functionName); 244 | 245 | var maybePointer = _libraryLoader.GetFunctionPointer(_libraryPointer.Value, functionName); 246 | 247 | if (!maybePointer.HasValue) 248 | { 249 | throw new AggregateException( 250 | "Unable to load function: " + functionName, 251 | maybePointer.Exceptions); 252 | } 253 | 254 | #if BEFORE_NET451 255 | return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer(maybePointer.Value, typeof(TDelegate)); 256 | #else 257 | return Marshal.GetDelegateForFunctionPointer(maybePointer.Value); 258 | #endif 259 | } 260 | 261 | /// 262 | /// Gets a lazy object that, when unwrapped, returns a delegate that executes 263 | /// the native function identified by . 264 | /// 265 | /// The type of the delegate. 266 | /// The name of the native function. 267 | /// A delegate that executes the native function. 268 | /// 269 | /// is null. 270 | /// 271 | /// 272 | /// is empty. 273 | /// 274 | /// 275 | /// TDelegate is not delegate. 276 | /// 277 | /// 278 | /// When the lazy object unwrapped: 279 | /// Unable to load the native library. 280 | /// or 281 | /// Unable to get a pointer to the function. 282 | /// 283 | public Lazy GetLazyDelegate(string functionName) 284 | { 285 | ValidateGetDelegate(functionName); 286 | 287 | return new Lazy(() => GetDelegate(functionName)); 288 | } 289 | 290 | private static void ValidateGetDelegate(string functionName) 291 | { 292 | if (functionName == null) throw new ArgumentNullException(nameof(functionName)); 293 | if (functionName.Length == 0) throw new ArgumentException("'functionName' must not be empty.", nameof(functionName)); 294 | 295 | if (!typeof(Delegate).IsAssignableFrom(typeof(TDelegate))) 296 | { 297 | throw new InvalidOperationException("TDelegate must be a delegate."); 298 | } 299 | } 300 | 301 | private static ILibraryLoader GetLibraryLoader(RuntimeOS os) 302 | { 303 | return os switch 304 | { 305 | RuntimeOS.Windows => new WindowsLibraryLoader(), 306 | RuntimeOS.Mac => new UnixLibraryLoader(true), 307 | RuntimeOS.Linux => new UnixLibraryLoader(false), 308 | _ => throw new ArgumentOutOfRangeException(nameof(os)), 309 | }; 310 | } 311 | 312 | private static RuntimeOS GetRuntimeOS() 313 | { 314 | var windir = Environment.GetEnvironmentVariable("windir"); 315 | 316 | #if NET48 317 | if (!string.IsNullOrEmpty(windir) && windir.Contains('\\') && Directory.Exists(windir)) 318 | #else 319 | if (!string.IsNullOrEmpty(windir) && windir.Contains('\\', StringComparison.OrdinalIgnoreCase) && Directory.Exists(windir)) 320 | #endif 321 | { 322 | return RuntimeOS.Windows; 323 | } 324 | else if (File.Exists(@"/proc/sys/kernel/ostype")) 325 | { 326 | var osType = File.ReadAllText(@"/proc/sys/kernel/ostype"); 327 | if (osType.StartsWith("Linux", StringComparison.OrdinalIgnoreCase)) 328 | { 329 | // Note: Android gets here too 330 | return RuntimeOS.Linux; 331 | } 332 | else 333 | { 334 | throw new PlatformNotSupportedException(osType); 335 | } 336 | } 337 | else if (File.Exists(@"/System/Library/CoreServices/SystemVersion.plist")) 338 | { 339 | // Note: iOS gets here too 340 | return RuntimeOS.Mac; 341 | } 342 | else 343 | { 344 | throw new PlatformNotSupportedException(); 345 | } 346 | } 347 | 348 | private static string GetLibraryPath(string libraryName, DllInfo dllInfo) 349 | { 350 | var dllData = LoadResource(dllInfo.ResourceName); 351 | var hash = GetHash(dllData); 352 | 353 | string? directory = null; 354 | 355 | var exceptions = new List(); 356 | foreach (var candidateLocation in _libraryLoader.CandidateWritableLocations) 357 | { 358 | if (TryGetWritableDirectory( 359 | candidateLocation, libraryName, hash, out directory, out var exception)) 360 | { 361 | Debug.Assert(directory != null, "'directory' must not be null if TryGetWritableDirectory returns true."); 362 | break; 363 | } 364 | exceptions.Add(exception!); 365 | } 366 | 367 | if (directory == null) 368 | { 369 | throw new AggregateException( 370 | $"Unable to obtain writable file path in candidate locations: {string.Join(", ", _libraryLoader.CandidateWritableLocations.Select(x => "'" + x + "'"))}.", 371 | exceptions.ToArray()); 372 | } 373 | 374 | var path = WriteDll(dllData, dllInfo.ResourceName, directory); 375 | 376 | foreach (var resourceName in dllInfo.AdditionalResourceNames) 377 | { 378 | dllData = LoadResource(resourceName); 379 | WriteDll(dllData, resourceName, directory); 380 | } 381 | 382 | return path; 383 | } 384 | 385 | private static bool TryGetWritableDirectory( 386 | string root, string libraryName, string hash, out string? directory, out Exception? exception) 387 | { 388 | var dir = Path.Combine(root, libraryName, hash); 389 | if (!Directory.Exists(dir)) 390 | { 391 | try 392 | { 393 | Directory.CreateDirectory(dir); 394 | } 395 | catch (UnauthorizedAccessException ex) 396 | { 397 | exception = ex; 398 | directory = null; 399 | return false; 400 | } 401 | } 402 | 403 | try 404 | { 405 | var filePath = Path.Combine(dir, Path.GetRandomFileName()); 406 | using (var stream = File.Create(filePath)) stream.WriteByte(1); 407 | File.Delete(filePath); 408 | } 409 | catch (UnauthorizedAccessException ex) 410 | { 411 | exception = ex; 412 | directory = null; 413 | return false; 414 | } 415 | 416 | directory = dir; 417 | exception = null; 418 | return true; 419 | } 420 | 421 | private static string WriteDll(byte[] dllData, string resourceName, string directory) 422 | { 423 | var fileName = Regex.Match(resourceName, @"[^.]+\.(?:dll|exe|so|dylib)").Value; 424 | var path = Path.Combine(directory, fileName); 425 | 426 | if (!File.Exists(path)) 427 | { 428 | File.WriteAllBytes(path, dllData); 429 | } 430 | else 431 | { 432 | var fileDllData = File.ReadAllBytes(path); 433 | if (dllData.Length != fileDllData.Length 434 | || GetHash(dllData) != GetHash(fileDllData)) 435 | { 436 | File.Delete(path); 437 | File.WriteAllBytes(path, dllData); 438 | } 439 | } 440 | 441 | return path; 442 | } 443 | 444 | private static byte[] LoadResource(string resourceName) 445 | { 446 | var stream = typeof(EmbeddedNativeLibrary).Assembly.GetManifestResourceStream(resourceName); 447 | 448 | if (stream == null) 449 | { 450 | throw new DllNotFoundException("Unable to locate resource: " + resourceName); 451 | } 452 | 453 | var buffer = new byte[stream.Length]; 454 | stream.Read(buffer, 0, buffer.Length); 455 | return buffer; 456 | } 457 | 458 | private static string GetHash(byte[] dllData) 459 | { 460 | using var algorithm = SHA256.Create(); 461 | var hash = algorithm.ComputeHash(dllData); 462 | var builder = new StringBuilder(hash.Length * 2); 463 | 464 | foreach (var value in hash) 465 | { 466 | builder.Append(value.ToString("x2", CultureInfo.InvariantCulture)); 467 | } 468 | 469 | return builder.ToString(); 470 | } 471 | 472 | private void FreeLibrary() 473 | { 474 | if (_libraryPointer.IsValueCreated) 475 | { 476 | _libraryLoader.FreeLibrary(_libraryPointer.Value); 477 | } 478 | } 479 | } 480 | } -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/GetFunctionPointerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace RockLib.Interop 5 | { 6 | /// 7 | /// Exception that is created when a library cannot be loaded. 8 | /// 9 | [Serializable] 10 | public sealed class GetFunctionPointerException 11 | : Exception 12 | { 13 | /// 14 | /// Creates a new 15 | /// 16 | public GetFunctionPointerException() { } 17 | 18 | /// 19 | /// Creates a new 20 | /// 21 | /// 22 | /// The message that describes the error. 23 | /// 24 | public GetFunctionPointerException(string message) : base(message) { } 25 | 26 | /// 27 | /// Creates a new 28 | /// 29 | /// 30 | /// The message that describes the error. 31 | /// 32 | /// 33 | /// The exception that is the cause of the current exception, or null 34 | /// if no inner exception is specified. 35 | /// 36 | public GetFunctionPointerException(string message, Exception? inner) : base(message, inner) { } 37 | 38 | private GetFunctionPointerException(SerializationInfo serializationInfo, StreamingContext streamingContext) 39 | : base(serializationInfo, streamingContext) { } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/ILibraryLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RockLib.Interop 5 | { 6 | partial class EmbeddedNativeLibrary 7 | { 8 | private interface ILibraryLoader 9 | { 10 | string[] CandidateWritableLocations { get; } 11 | IEnumerable GetInstallPathCandidates(string libraryName); 12 | MaybeIntPtr LoadLibrary(string libraryPath); 13 | void FreeLibrary(IntPtr libraryPointer); 14 | MaybeIntPtr GetFunctionPointer(IntPtr libraryPointer, string functionName); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/LoadLibraryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace RockLib.Interop 5 | { 6 | /// 7 | /// Exception that is created when a library cannot be loaded. 8 | /// 9 | [Serializable] 10 | public sealed class LoadLibraryException 11 | : Exception 12 | { 13 | /// 14 | /// Creates a new 15 | /// 16 | public LoadLibraryException() { } 17 | 18 | /// 19 | /// Creates a new 20 | /// 21 | /// 22 | /// The message that describes the error. 23 | /// 24 | public LoadLibraryException(string message) : base(message) { } 25 | 26 | /// 27 | /// Creates a new 28 | /// 29 | /// 30 | /// The message that describes the error. 31 | /// 32 | /// 33 | /// The exception that is the cause of the current exception, or null 34 | /// if no inner exception is specified. 35 | /// 36 | public LoadLibraryException(string message, Exception? inner) : base(message, inner) { } 37 | 38 | private LoadLibraryException(SerializationInfo serializationInfo, StreamingContext streamingContext) 39 | : base(serializationInfo, streamingContext) { } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/MaybeIntPtr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Interop 4 | { 5 | partial class EmbeddedNativeLibrary 6 | { 7 | private class MaybeIntPtr 8 | { 9 | public MaybeIntPtr(IntPtr value) 10 | { 11 | if (value == IntPtr.Zero) 12 | { 13 | throw new ArgumentException("value must be non-zero.", nameof(value)); 14 | } 15 | 16 | Exceptions = Array.Empty(); 17 | Value = value; 18 | } 19 | 20 | public MaybeIntPtr(Exception[] exceptions) 21 | { 22 | if(exceptions is null) 23 | { 24 | throw new ArgumentNullException(nameof(exceptions)); 25 | } 26 | 27 | if(exceptions.Length == 0) 28 | { 29 | throw new ArgumentException("exceptions must contain at least one element.", nameof(exceptions)); 30 | } 31 | 32 | Exceptions = exceptions; 33 | } 34 | 35 | public IntPtr Value { get; private set; } 36 | public Exception[] Exceptions { get; private set; } 37 | public bool HasValue { get { return Value != IntPtr.Zero; } } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/RockLib.EmbeddedNativeLibrary.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | RockLib.EmbeddedNativeLibrary 4 | 3.0.0 5 | RockLib 6 | Consume native libraries from .NET by adding as embedded resources. 7 | false 8 | A changelog is available at https://github.com/RockLib/RockLib.EmbeddedNativeLibrary/blob/main/RockLib.EmbeddedNativeLibrary/CHANGELOG.md. 9 | https://github.com/RockLib/RockLib.EmbeddedNativeLibrary 10 | LICENSE.md 11 | icon.png 12 | Copyright 2015-2022 (c) Rocket Mortgage. All rights reserved. 13 | rocklib embedded native library 14 | 3.0.0 15 | True 16 | True 17 | True 18 | Embedded 19 | 20 | 21 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 22 | 23 | 24 | true 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | EmbeddedNativeLibrary.cs 36 | 37 | 38 | EmbeddedNativeLibrary.cs 39 | 40 | 41 | EmbeddedNativeLibrary.cs 42 | 43 | 44 | EmbeddedNativeLibrary.cs 45 | 46 | 47 | EmbeddedNativeLibrary.cs 48 | 49 | 50 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/RuntimeOS.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Interop 2 | { 3 | partial class EmbeddedNativeLibrary 4 | { 5 | private enum RuntimeOS 6 | { 7 | Windows, 8 | Mac, 9 | Linux 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/TargetRuntime.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Interop 2 | { 3 | /// 4 | /// Defines the target runtimes that are supported by the class. 5 | /// 6 | public enum TargetRuntime 7 | { 8 | /// 9 | /// A windows environment. Whether it is 32-bit or 64-bit is unspecified. 10 | /// 11 | Windows, 12 | 13 | /// 14 | /// A windows 32-bit environment. 15 | /// 16 | Win32, 17 | 18 | /// 19 | /// A Windows 64-bit environment. 20 | /// 21 | Win64, 22 | 23 | /// 24 | /// A Mac environment. 25 | /// 26 | Mac, 27 | 28 | /// 29 | /// A Linux environment. 30 | /// 31 | Linux, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/UnixLibraryLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace RockLib.Interop 8 | { 9 | partial class EmbeddedNativeLibrary 10 | { 11 | private class UnixLibraryLoader : ILibraryLoader 12 | { 13 | private static readonly string[] _candidateWritableLocations = new[] { "/tmp", "/var/tmp" }; 14 | 15 | private readonly bool _isMac; 16 | 17 | public UnixLibraryLoader(bool isMac) 18 | { 19 | _isMac = isMac; 20 | } 21 | 22 | public string[] CandidateWritableLocations { get { return _candidateWritableLocations; } } 23 | 24 | public IEnumerable GetInstallPathCandidates(string libraryName) 25 | { 26 | var fullName = libraryName + (_isMac ? ".dylib" : ".so"); 27 | 28 | var assembly = Assembly.GetEntryAssembly() ?? typeof(UnixLibraryLoader).Assembly; 29 | var potentialInstallPath = Path.Combine(Path.GetDirectoryName(assembly.Location)!, fullName); 30 | if (File.Exists(potentialInstallPath)) 31 | { 32 | yield return potentialInstallPath; 33 | } 34 | 35 | yield return fullName; 36 | } 37 | 38 | public MaybeIntPtr LoadLibrary(string libraryPath) 39 | { 40 | var libraryPointer = dlopen(libraryPath, dlopenFlags.RTLD_LAZY | dlopenFlags.RTLD_GLOBAL); 41 | 42 | if (libraryPointer != IntPtr.Zero) 43 | { 44 | return new MaybeIntPtr(libraryPointer); 45 | } 46 | 47 | var error = dlerror(); 48 | if (string.IsNullOrEmpty(error)) 49 | { 50 | error = "Null pointer was returned from dlopen."; 51 | } 52 | 53 | return new MaybeIntPtr(new[] { new LoadLibraryException(error) }); 54 | } 55 | 56 | public void FreeLibrary(IntPtr libraryPointer) 57 | { 58 | dlclose(libraryPointer); 59 | } 60 | 61 | public MaybeIntPtr GetFunctionPointer(IntPtr libraryPointer, string functionName) 62 | { 63 | var functionPointer = dlsym(libraryPointer, functionName); 64 | 65 | if (functionPointer != IntPtr.Zero) 66 | { 67 | return new MaybeIntPtr(functionPointer); 68 | } 69 | 70 | return new MaybeIntPtr(new Exception[] { new GetFunctionPointerException(dlerror()) }); 71 | } 72 | 73 | private IntPtr dlopen(string filename, dlopenFlags flags) 74 | { 75 | return _isMac ? Mac.NativeMethods.dlopen(filename, flags) : Linux.NativeMethods.dlopen(filename, flags); 76 | } 77 | 78 | private string dlerror() 79 | { 80 | return _isMac ? Mac.NativeMethods.dlerror() : Linux.NativeMethods.dlerror(); 81 | } 82 | 83 | private IntPtr dlsym(IntPtr handle, string symbol) 84 | { 85 | return _isMac ? Mac.NativeMethods.dlsym(handle, symbol) : Linux.NativeMethods.dlsym(handle, symbol); 86 | } 87 | 88 | private IntPtr dlclose(IntPtr handle) 89 | { 90 | return _isMac ? Mac.NativeMethods.dlclose(handle) : Linux.NativeMethods.dlclose(handle); 91 | } 92 | 93 | private static class Mac 94 | { 95 | internal static class NativeMethods 96 | { 97 | #pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments 98 | #pragma warning disable CA5392 // Use DefaultDllImportSearchPaths attribute for P/Invokes 99 | [DllImport("libSystem.dylib")] 100 | public static extern IntPtr dlopen(string filename, dlopenFlags flags); 101 | 102 | [DllImport("libSystem.dylib")] 103 | public static extern string dlerror(); 104 | 105 | [DllImport("libSystem.dylib")] 106 | public static extern IntPtr dlsym(IntPtr handle, string symbol); 107 | 108 | [DllImport("libSystem.dylib")] 109 | public static extern IntPtr dlclose(IntPtr handle); 110 | #pragma warning restore CA5392 // Use DefaultDllImportSearchPaths attribute for P/Invokes 111 | #pragma warning restore CA2101 // Specify marshaling for P/Invoke string arguments 112 | } 113 | } 114 | 115 | private static class Linux 116 | { 117 | internal static class NativeMethods 118 | {// libdl.so libcoreclr.so 119 | #pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments 120 | #pragma warning disable CA5392 // Use DefaultDllImportSearchPaths attribute for P/Invokes 121 | [DllImport("libdl.so")] 122 | public static extern IntPtr dlopen(string filename, dlopenFlags flag); 123 | 124 | [DllImport("libdl.so")] 125 | public static extern string dlerror(); 126 | 127 | [DllImport("libdl.so")] 128 | public static extern IntPtr dlsym(IntPtr handle, string name); 129 | 130 | [DllImport("libdl.so")] 131 | public static extern IntPtr dlclose(IntPtr handle); 132 | #pragma warning restore CA5392 // Use DefaultDllImportSearchPaths attribute for P/Invokes 133 | #pragma warning restore CA2101 // Specify marshaling for P/Invoke string arguments 134 | } 135 | } 136 | 137 | [Flags] 138 | private enum dlopenFlags 139 | { 140 | RTLD_LAZY = 0x1, 141 | RTLD_NOW = 0x2, 142 | RTLD_LOCAL = 0x4, 143 | RTLD_GLOBAL = 0x8, 144 | RTLD_NOLOAD = 0x10, 145 | RTLD_NODELETE = 0x80, 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/WindowsLibraryLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace RockLib.Interop 9 | { 10 | partial class EmbeddedNativeLibrary 11 | { 12 | private class WindowsLibraryLoader : ILibraryLoader 13 | { 14 | private static readonly string[] _candidateWritableLocations; 15 | 16 | #pragma warning disable CA1810 // Initialize reference type static fields inline 17 | static WindowsLibraryLoader() 18 | #pragma warning restore CA1810 // Initialize reference type static fields inline 19 | { 20 | var candidateLocations = new List(); 21 | var localAppData = Environment.GetEnvironmentVariable("LocalAppData"); 22 | if (!string.IsNullOrEmpty(localAppData)) 23 | { 24 | candidateLocations.Add(localAppData); 25 | } 26 | 27 | var tmpDirectory = Environment.GetEnvironmentVariable("TMP"); 28 | if (!string.IsNullOrEmpty(tmpDirectory)) 29 | { 30 | candidateLocations.Add(tmpDirectory); 31 | } 32 | 33 | var tempDirectory = Environment.GetEnvironmentVariable("TEMP"); 34 | if (!string.IsNullOrEmpty(tempDirectory)) 35 | { 36 | candidateLocations.Add(tempDirectory); 37 | } 38 | 39 | _candidateWritableLocations = candidateLocations.ToArray(); 40 | } 41 | 42 | public string[] CandidateWritableLocations { get { return _candidateWritableLocations; } } 43 | 44 | public IEnumerable GetInstallPathCandidates(string libraryName) 45 | { 46 | var fullName = libraryName + ".dll"; 47 | 48 | var assembly = Assembly.GetEntryAssembly() ?? typeof(WindowsLibraryLoader).Assembly; 49 | var potentialInstallPath = Path.Combine(Path.GetDirectoryName(assembly.Location)!, fullName); 50 | if (File.Exists(potentialInstallPath)) 51 | { 52 | yield return potentialInstallPath; 53 | } 54 | 55 | yield return fullName; 56 | } 57 | 58 | public MaybeIntPtr LoadLibrary(string libraryPath) 59 | { 60 | var libraryPointer = NativeMethods.LoadLibrary(libraryPath); 61 | 62 | if (libraryPointer != IntPtr.Zero) 63 | { 64 | return new MaybeIntPtr(libraryPointer); 65 | } 66 | 67 | var exceptions = new List(); 68 | 69 | exceptions.Add(new Win32Exception()); 70 | 71 | libraryPointer = NativeMethods.LoadLibraryEx(libraryPath, IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); 72 | 73 | if (libraryPointer != IntPtr.Zero) 74 | { 75 | return new MaybeIntPtr(libraryPointer); 76 | } 77 | 78 | exceptions.Add(new Win32Exception()); 79 | 80 | var originalPathVariable = Environment.GetEnvironmentVariable("PATH"); 81 | var pathVariable = originalPathVariable + ";" + Path.GetDirectoryName(libraryPath); 82 | Environment.SetEnvironmentVariable("PATH", pathVariable); 83 | 84 | libraryPointer = NativeMethods.LoadLibrary(libraryPath); 85 | 86 | Environment.SetEnvironmentVariable("PATH", originalPathVariable); 87 | 88 | if (libraryPointer != IntPtr.Zero) 89 | { 90 | return new MaybeIntPtr(libraryPointer); 91 | } 92 | 93 | exceptions.Add(new Win32Exception()); 94 | 95 | return new MaybeIntPtr(exceptions.ToArray()); 96 | } 97 | 98 | public void FreeLibrary(IntPtr libraryPointer) 99 | { 100 | NativeMethods.FreeLibrary(libraryPointer); 101 | } 102 | 103 | public MaybeIntPtr GetFunctionPointer(IntPtr libraryPointer, string functionName) 104 | { 105 | var functionPointer = NativeMethods.GetProcAddress(libraryPointer, functionName); 106 | 107 | if (functionPointer != IntPtr.Zero) 108 | { 109 | return new MaybeIntPtr(functionPointer); 110 | } 111 | 112 | return new MaybeIntPtr(new Exception[] { new Win32Exception() }); 113 | } 114 | 115 | private static class NativeMethods 116 | { 117 | #pragma warning disable CA5392 // Use DefaultDllImportSearchPaths attribute for P/Invokes 118 | #pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments 119 | [DllImport("kernel32.dll", EntryPoint = "LoadLibraryEx", BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)] 120 | public static extern IntPtr LoadLibraryEx([MarshalAs(UnmanagedType.LPStr)] string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags); 121 | 122 | [DllImport("kernel32.dll", EntryPoint = "LoadLibrary", BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)] 123 | public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpLibFileName); 124 | 125 | [DllImport("kernel32.dll", EntryPoint = "GetProcAddress", BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)] 126 | public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName); 127 | 128 | [DllImport("kernel32.dll", EntryPoint = "FreeLibrary", SetLastError = true)] 129 | public static extern bool FreeLibrary(IntPtr hModule); 130 | #pragma warning restore CA2101 // Specify marshaling for P/Invoke string arguments 131 | #pragma warning restore CA5392 // Use DefaultDllImportSearchPaths attribute for P/Invokes 132 | } 133 | 134 | [Flags] 135 | private enum LoadLibraryFlags : uint 136 | { 137 | DONT_RESOLVE_DLL_REFERENCES = 0x00000001, 138 | LOAD_LIBRARY_AS_DATAFILE = 0x00000002, 139 | LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008, 140 | LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, 141 | LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, 142 | LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, 143 | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100, 144 | LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200, 145 | LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400, 146 | LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800, 147 | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000, 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /RockLib.EmbeddedNativeLibrary/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 'RockLib.EmbeddedNativeLibrary.{build}.0.0-ci' 2 | image: Visual Studio 2022 3 | configuration: Release 4 | only_commits: 5 | files: 6 | - RockLib.EmbeddedNativeLibrary/ 7 | - RockLib.EmbeddedNativeLibrary.Shared/ 8 | - RockLib.EmbeddedNativeLibrary.sln 9 | before_build: 10 | - ps: | 11 | # The path to the solution to restore. 12 | $sln_path = "RockLib.EmbeddedNativeLibrary.sln" 13 | 14 | # The path to the main csproj file. It will be patched during the build. 15 | $csproj_path = "RockLib.EmbeddedNativeLibrary\RockLib.EmbeddedNativeLibrary.csproj" 16 | 17 | # The version of the build artifact's nuget package when created by CI builds. 18 | $ci_package_version = "$Env:appveyor_build_number.0.0-ci" 19 | 20 | # This before_build script does three things: 21 | # 22 | # 1) Synchronize the AppVeyor build version and the csproj's package version. 23 | # a) If the current build is a deploy build*, update the AppVeyor build version to match 24 | # the csproj's nuget package version. 25 | # b) If the current build is a CI build*, update the csproj's package version to match 26 | # the AppVeyor build version. 27 | # 2) Set an AppVeyor build variable named 'csproj_build_version' to the csproj's package version. 28 | # This value is used by deploy providers to determine whether the current commit should be deployed. 29 | # 3) Restore packages for the sln. .NET Standard libraries won't build without restoring first. 30 | # 31 | # *The current build is a deploy build if the 'appveyor_repo_tag' variable is 'true' and the 32 | # 'appveyor_repo_tag_name' variable is the the value of the 'csproj_build_version' variable, as set in 33 | # #2 above. Otherwise, the current build is a CI build. 34 | 35 | function Get-Csproj-Build-Version ($csproj) 36 | { 37 | $package_id = $csproj.SelectSingleNode("/Project/PropertyGroup/PackageId").InnerText 38 | $package_version = $csproj.SelectSingleNode("/Project/PropertyGroup/PackageVersion").InnerText 39 | Return "$package_id.$package_version" 40 | } 41 | 42 | function Synchronize-AppVeyor-And-Csproj-Versions ($csproj) 43 | { 44 | $csproj_build_version = Get-Csproj-Build-Version $csproj 45 | 46 | If ($Env:appveyor_repo_tag -eq "true" -AND $Env:appveyor_repo_tag_name -eq $csproj_build_version) { 47 | # If this is a deploy build, update the AppVeyor build version to match the csproj's package version. 48 | Update-AppVeyorBuild -Version $csproj_build_version 49 | } else { 50 | # Else, update the csproj's package version to match the AppVeyor build version. 51 | $package_version_node = $csproj.SelectSingleNode("/Project/PropertyGroup/PackageVersion") 52 | $package_version_node.InnerText = $ci_package_version 53 | } 54 | } 55 | 56 | function Set-Csproj-Build-Version-Variable ($csproj) 57 | { 58 | $csproj_build_version = Get-Csproj-Build-Version $csproj 59 | Set-AppVeyorBuildVariable -Name "csproj_build_version" -Value $csproj_build_version 60 | } 61 | 62 | # The $csproj xml object contains the contents of the csproj file. 63 | $csproj = [xml](Get-Content $csproj_path) 64 | 65 | Synchronize-AppVeyor-And-Csproj-Versions $csproj 66 | Set-Csproj-Build-Version-Variable $csproj 67 | 68 | # Patch the csproj file with the modified xml object after all changes have been made. 69 | $csproj.Save((Get-Item $csproj_path)) 70 | 71 | nuget restore $sln_path 72 | build: 73 | project: RockLib.EmbeddedNativeLibrary.sln 74 | verbosity: minimal 75 | artifacts: 76 | - path: '**/$(csproj_build_version).nupkg' 77 | deploy: 78 | - provider: GitHub 79 | tag: $(appveyor_repo_tag_name) 80 | release: $(appveyor_repo_commit_message) 81 | description: $(appveyor_repo_commit_message_extended) 82 | auth_token: 83 | secure: gglgEqQVi2MuIUl8g8rS6jb5r8sgr0PYa4qXq9XaAGeAJ+oAEFmlNFFw/jgX8hQr 84 | on: 85 | appveyor_repo_tag: true 86 | appveyor_repo_tag_name: $(csproj_build_version) 87 | - provider: NuGet 88 | api_key: 89 | secure: d662rvY3udud0hnjkQv3mXwAvidAylYGnl8biGLb7/5Fb3mD+tJIfkY7/Y74cEMR 90 | on: 91 | appveyor_repo_tag: true 92 | appveyor_repo_tag_name: $(csproj_build_version) 93 | notifications: 94 | - provider: Email 95 | to: 96 | - rocklibsupport@rocketmortgage.com 97 | on_build_success: false 98 | on_build_failure: false 99 | on_build_status_changed: true 100 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockLib/RockLib.EmbeddedNativeLibrary/5bba4f1f910d62416d99edff7496a1c44804488c/icon.png --------------------------------------------------------------------------------