하나의 C # 코어, 세 개의 플랫폼: 백그라운드 네트워킹 SDK를 통해 배운 것

작성자

카테고리:

← 피드로
DEV Community · Nabeel Hassan · 2026-07-22 개발(SW)

Most “cross-platform” war stories are really UI stories: one framework draws the same button on two phones and everyone calls it a day. The one I want to tell is not that. At Geonode I built the Repocket C# SDK, a single networking core that had to run on Windows, Android and iOS and carry all the logic for internet sharing on every one of them. One codebase, three very different runtimes, and a piece of software that lives in the background of someone else’s device and must never misbehave.

There is no screen to hide behind here. Repocket is an internet-sharing product, so the SDK is not a view, it is a long-lived networking client that sits quietly on a user’s machine and routes traffic. If the shared core is wrong, it is wrong on Windows, Android and iOS at once, and it is wrong in the background where nobody is watching. That raises the bar for every decision, and it is why the lessons stuck. Here is what shipping one C# SDK to three platforms actually taught me.

Why C# for a job like this

The instinct for cross-platform native code is usually C or Rust with thin bindings. We went with C#, and for this product it was the right call. A single managed core meant the networking logic, the part that had to be identical everywhere, lived in one place with one set of tests. On Windows it ran natively. On Android and iOS it ran through the .NET mobile runtime, so the same compiled logic drove all three platforms instead of three hand-ported copies drifting apart over time.

The rule I kept coming back to was simple: share the logic, never the platform. Anything that was pure behavior, how a connection is established, retried, throttled, torn down and reported, belonged in the shared C# core. Anything that touched the operating system, background execution, permissions, network-state callbacks, power management, belonged in a thin native layer per platform. The moment you let OS-specific assumptions leak into the shared core, you have three subtly broken platforms instead of one clean one.

The shared core, kept ruthlessly pure

The discipline that paid off most was keeping the core free of platform knowledge. The core does not know what a Windows service is or what an Android foreground notification is. It exposes a small surface: start, stop, current state, and events. Each platform’s native shim is responsible for translating the messy reality of that OS into those few clean calls.

That boundary is worth obsessing over, because it is exactly where cross-platform projects rot. Every time you are tempted to write if (Android) { ... } inside shared code, you are trading a day now for a month later. I would rather add one more method to the native interface than one more platform branch to the core. Years on, building AI agents instead of SDKs, I keep the same instinct: the business logic lives in a separate automation layer, not baked into whatever happens to be in front of the user.

The background is the hard part

A foreground app has it easy. It is on screen, the user expects it to use resources, and the OS mostly leaves it alone. A background networking client is the opposite: every mobile OS is actively trying to suspend it, throttle it, or kill it to save battery, and it has to keep working anyway, invisibly, for hours.

This is where the three platforms stopped rhyming and each demanded its own answer.

  • Windows was the friendliest. A long-running process with real background freedom, so the native layer there was mostly about lifecycle, clean startup and clean shutdown.
  • Android wanted the work justified. Background networking has to survive Doze, app standby and aggressive OEM battery managers, which means foreground-service semantics and being an honest citizen about what you are doing and why.
  • iOS was the strictest by a wide margin. The system reclaims background time hard, so the native layer had to work within iOS’s rules for background networking rather than fight them, and assume it could be suspended at any moment.

The lesson that generalized far beyond this SDK: design for being paused, not just for running. The core could not assume it would execute uninterrupted. It had to hold enough state to be suspended and resumed cleanly, reconcile what changed while it was asleep, and never leave a half-open connection or corrupt state behind when the OS pulled the rug out. That is the same muscle I later needed in AR work, where you assume the overlay will be wrong at the worst possible moment and make it a non-event.

Networking that has to be a good guest

Because the SDK routes traffic on someone else’s device, being well-behaved was not a nice-to-have, it was the product. Bad networking behavior on a user’s machine is not a bug ticket, it is an uninstall. So the core treated the host device’s resources as borrowed, not owned.

That shaped the connection logic directly. Retries had to back off instead of hammering. Failures had to be contained so one bad connection never cascaded. Resource use had to stay predictable so the SDK never became the reason a user’s device felt slow or hot. The networking layer’s real job was less “move packets fast” and more “move packets without ever being noticed,” which is a harder and more interesting constraint than raw throughput.

Getting that right once, in the shared core, meant every platform inherited the good behavior for free. That is the compounding payoff of the share-the-logic rule: solve the strict version of a problem in one place and all three runtimes get the fix at the same time.

CI that builds on every platform, every time

A cross-platform SDK is only as trustworthy as its worst-supported target, and the only way to keep three targets honest is to build all three on every change. So I set up Unity and Windows CI/CD pipelines so the whole thing compiled and packaged automatically rather than depending on whatever happened to be installed on my machine that week.

The value is not glamorous but it is enormous: it makes “it works on my machine” impossible to hide behind. If a change compiled on Windows but broke the mobile build, CI caught it the same hour, not three weeks later when someone tried to cut a release. For a background SDK that ships inside other apps, a broken build is not a local annoyance, it is a broken client on real users’ devices, so the pipeline was as much a part of the product as the code.

I built a React Native VPN alongside the SDK work too, another exercise in the same theme: a thin cross-platform surface over deeply platform-specific system networking. VPN entitlements, tunnel lifecycle and OS network extensions are about as platform-specific as software gets, and the same discipline applied. Keep the shared logic shared, wrap the OS-specific parts in the thinnest possible native layer, and let the platform be platform-specific where it insists on being.

What a cross-platform SDK taught me

Years later I have moved from .NET SDKs and Unity into AI agents and my own products. But the Repocket work left instincts I use in almost everything I ship now:

  • Share the logic, never the platform. One clean core plus thin native shims beats three drifting copies. The boundary is the whole game.
  • Design for being paused. Code that can be suspended, killed and resumed without corruption is more robust everywhere, not just on iOS.
  • Be a good guest. Software that runs where a user is not watching has to treat their resources as borrowed. Restraint is a feature.
  • Build every target, every time. CI across all platforms turns “works on my machine” from a hope into a guarantee.

The tools change. I have been paid for C#, Unity, mobile SDKs, and now LLM agents, and I expect that list to keep growing. What carries across all of it is knowing exactly what a piece of software cannot afford to get wrong and building the boundary around that first. A background SDK on three platforms taught me that lesson in a place where there was no interface to hide the mistakes behind, and I have built cleaner ever since.

If you want more of how I think about this kind of work, I write it all up at nabeelbaghoor.com.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다