├── .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
--------------------------------------------------------------------------------