{"id":13722488,"url":"https://github.com/mattleibow/CreatingPlatformPlugins","last_synced_at":"2025-05-07T15:30:43.306Z","repository":{"id":145549475,"uuid":"113507102","full_name":"mattleibow/CreatingPlatformPlugins","owner":"mattleibow","description":"A set of examples and documentation to aid in the development of cross-platform libraries and plugins.","archived":false,"fork":false,"pushed_at":"2018-01-16T23:07:57.000Z","size":265,"stargazers_count":49,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-05T00:36:34.846Z","etag":null,"topics":["cross-platform","csharp","dot-net","nuget","xamarin"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mattleibow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-12-07T23:00:41.000Z","updated_at":"2023-01-04T21:20:45.000Z","dependencies_parsed_at":"2023-05-25T18:37:18.931Z","dependency_job_id":null,"html_url":"https://github.com/mattleibow/CreatingPlatformPlugins","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattleibow%2FCreatingPlatformPlugins","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattleibow%2FCreatingPlatformPlugins/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattleibow%2FCreatingPlatformPlugins/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattleibow%2FCreatingPlatformPlugins/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mattleibow","download_url":"https://codeload.github.com/mattleibow/CreatingPlatformPlugins/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252905555,"owners_count":21822824,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cross-platform","csharp","dot-net","nuget","xamarin"],"created_at":"2024-08-03T01:01:29.436Z","updated_at":"2025-05-07T15:30:41.310Z","avatar_url":"https://github.com/mattleibow.png","language":"C#","funding_links":[],"categories":["Articles"],"sub_categories":["Plugins"],"readme":"# Creating Platform Plugins\n\nThere are a few ways to create a plugin or bait-and-switch library which\npermits the use of platform-specific code from a cross-platform project:\n\n - [**Shared Project**](UsingSharedProjects)  \n   All the code exists in a single shared project, controlled with `#if`\n   preprocessor directives. This is good for projects with a large API\n   but only a small set of platform-specific code.\n - [**Mixed Projects**](UsingMixedProjects)  \n   The code exists in both the shared and platform projects. The code still has\n   a few `#if` preprocessor directives, but the main implementation logic has\n   been moved to the platform projects. This is good for projects that have a \n   few platform differences, but those differences require more complex code.\n - [**Abstracted Project**](UsingAbstractionProjects)  \n   The code is split between a base/core project which defines the public API \n   using a set of interfaces, and then a series of platform projects which \n   independently define the implementation of those interfaces. This is good \n   for projects with larger APIs as well as for libraries that needs to \n   support third-party extensions or platforms.\n - [**Mixed Projects with Abstraction**](UsingAbstractedMixedProjects)  \n   The code exists in both the shared and platform projects. Instead of relying\n   of many API matching or numerous `#if` preprocessor directives, the public \n   API is abstracted out into an interface which can be implemented on each \n   platform separately.\n - [**Multi-Targeting**](UsingMultiTargeting)  \n   All the code lives in a single project, which is set up to produce multiple \n   platform assemblies. The actual implementation may be to use a set of `#if` \n   preprocessor directives or it may be to abstract interfaces.\n - [**Generated Projects**](UsingGeneratedProjects)  \n   All the code lives in the platform projects, and then the cross-platform code is genertaed using GenAPI. This is good for projects that have a cross-platform API, but has a large amount of platform-specific code.\n\n## Bait-And-Switch\n\nThe concept of a bait-and-switch NuGet is very simple and consists of 2 or more\nassemblies with the same name:\n\n - a dummy assembly, named `AssemblyName.dll`\n - the real assembly, also named `AssemblyName.dll`\n\nThe process of using a bait-and-switch is to compile against the dummy assembly\nand then to run against the real assembly.\n\n### The Problem\n\nThe reason this whole process is required is simply because a .NET Standard \nlibrary or PCL cannot reference a platform-specific project. \n\nAn example scenario would be to fetch the current device's screen density. \nThis is a very device-specific process, and there is no way to do this from \na .NET Standard library.\n\nA .NET Standard library may be required because the app is a Xamarin.Forms app,\nwhich is designed to be written in a .NET Standard library. Or the library may\nbe targeting multiple platforms, and needs to be consumable from any .NET\nStandard-compliant platform - such as .NET Core or Unity.\n\n### The Solution\n\nBait-and-switch is a process in which to \"trick\" the app into thinking that it\nis using a .NET Standard library, but in fact is using a platform library. This\nis typically easy to do as _assemblies are loaded by name_. So, as long as the \nfinal assembly names match, the app will never know that the assembly it is \nrunning with is not the same as the one it was compiled with.\n\nObviously, the _public_ types must also match otherwise there will be a runtime\nerror when the app attempts to access a member, but it does not exist.\n\nThe dummy assembly doesn't have to do anything (it usually just throws an \nexception), it just needs to have the same name as the platform assembly and \nhave the same _public_ types and members.\n\n### An Example Library\n\nAssume that there is some requirement for a .NET Standard library that fetches\nthe screen density. Assume this is the required/desired API:\n\n```csharp\npublic static class Screen\n{\n    public static double GetDensity();\n}\n```\n\nAs can be seen, the API is pure BCL and does not appear to require any platform\ntypes. Since nothing can be done in the dummy .NET Standard library to fetch \nthe screen density, the only thing to do is to throw an exception:\n\n```csharp\npublic static class Screen\n{\n    public static double GetDensity()\n    {\n        throw new PlatformNotSupportedException();\n    }\n}\n```\n\nThe exception _should_ never actually be thrown at runtime, because this \nassembly is never going to make it into the final app. This assembly\nis purely for the compiler during the compilation of the consumer .NET \nStandard library.\n\nBut, the actual implementation is very platform-specific - such as with\nthe iOS implementation:\n\n```csharp\npublic static class Screen\n{\n    public static double GetDensity()\n    {\n        return UIScreen.MainScreen.Scale;\n    }\n}\n```\n\nThe type `UIScreen` is only available on iOS, will only ever be available on \niOS and this type will never reach .NET Standard definition. The only way to \ncreate this library is to create an iOS library, but this means that it will \nnot be consumable from any .NET Standard library.\n\nBoth the dummy .NET Standard library and the platform library _must_ be \ncompiled with the same name. Then, the consumer .NET Standard library \nreferences the dummy .NET Standard library and the consumer app references\nthe platform library.\n\nIf a NuGet is created, then the work will be done automatically and the only \nstep that needs to be taken is to ensure that both the app and the consumer \n.NET Standard library reference that NuGet.\n\n## The NuGet Package\n\nSince most of the work is actually done by NuGet, the definition of the .nuspec\n(or the .csproj for multi-targeting) is very important. NuGet will always \nchoose the more specific platform.\n\nIf there is an assembly for `netstandard`, `MonoAndroid` and `MonoAndroid70`,\ndepending on the destination project, NuGet will select the assembly that best\nfits:\n\n - for a Android v7.0+ project, the `MonoAndroid70` assembly will be selected\n - for a Android v1.0+ project, the `MonoAndroid` assembly will be selected\n - for any other project type, the `netstandard` assembly will be selected\n\nMore information on creating NuGet packages can be found in the NuGet\ndocumentation:  \nhttps://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package\n\n### Example NuGet Package\n\nThere are many elements that can be used to customize the package, but only a\nfew are required to create a bait-and-switch package:\n\n```xml\n\u003c?xml version=\"1.0\"?\u003e\n\u003cpackage\u003e\n  \u003cmetadata\u003e\n    \u003c!-- the unique package ID --\u003e\n    \u003cid\u003ePackageId\u003c/id\u003e\n    \u003c!-- the current version of the package --\u003e\n    \u003cversion\u003e1.0.0\u003c/version\u003e\n    \u003c!-- the authors text for the gallery --\u003e\n    \u003cauthors\u003eFirstName LastName\u003c/authors\u003e\n    \u003c!-- the description for the package manager UI --\u003e\n    \u003cdescription\u003ePackage description here.\u003c/description\u003e\n  \u003c/metadata\u003e\n  \u003cfiles\u003e\n    \u003c!-- the platform-specific assembly for Android --\u003e\n    \u003cfile src=\"output/android/Library.dll\" target=\"lib/MonoAndroid\" /\u003e\n    \u003c!-- the platform-specific assembly for iOS --\u003e\n    \u003cfile src=\"output/ios/Library.dll\" target=\"lib/Xamarin.iOS\" /\u003e\n    \u003c!-- the platform-specific assembly for UWP --\u003e\n    \u003cfile src=\"output/uwp/Library.dll\" target=\"lib/uap10.0\" /\u003e\n\n    \u003c!-- the bait assembly --\u003e\n    \u003cfile src=\"output/netstandard/Library.dll\" target=\"lib/netstandard\" /\u003e\n  \u003c/files\u003e\n\u003c/package\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattleibow%2FCreatingPlatformPlugins","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmattleibow%2FCreatingPlatformPlugins","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattleibow%2FCreatingPlatformPlugins/lists"}