乐趣下载_Windows乐趣与DartFFI

乐趣下载

As a product manager for a developer framework and programming language, it’s not always easy to find time during the workday to write code. But I consider it a vital task in order to empathize with my customers’ needs. So I dabble here and there with various projects that pique my interest; and over the last couple of months, I’ve been exploring a project that combines my many years of working on Windows with my current focus on Flutter and Dart, culminating in a package that wraps a good portion of the Windows API for consumption from Dart and Flutter apps. But the journey itself is also quite a fun story.

作为开发人员框架和编程语言的产品经理,在工作日内找时间写代码并不总是那么容易。 但是我认为这是至关重要的任务,目的是要满足客户的需求。 因此,我到处闲逛着各种各样的项目,引起了我的兴趣。 在过去的几个月中,我一直在探索一个项目,该项目将我多年在Windows的工作与我目前对FlutterDart的关注结合在一起,最终形成了一个包装了很多Windows API的软件包,供Dart使用和Flutter应用。 但是旅途本身也是一个很有趣的故事。

小步骤:控制台API (Small steps: Console APIs)

This all started with a small text editor. Via HackerNews, I came across Kilo, a UNIX-style terminal text editor written in less than 1,000 lines of C, and a very well-written tutorial that you can follow along to build it from scratch. I decided to give it a go, but porting the code to Dart as I went. This was a ton of fun.

这一切都始于一个小的文本编辑器。 通过HackerNews ,我遇到了Kilo (一种用不到1000行C语言编写的UNIX风格的终端文本编辑器),以及一个编写得很好的教程,您可以从头开始进行构建。 我决定尝试一下,但是在我进行时将代码移植到Dart。 这是一的乐趣。

What was not so fun? VT escape sequences, which is how Kilo manipulates the console for things like cursor movement, clearing the screen and hiding the cursor during screen updates. This archaic part of the modern Mac and Linux terminal has somehow survived for over forty years without significant modernization. In a remarkable piece of retro-refitting, it has even been introduced in recent years to the Windows terminal. At some point in the process, I forget when, I started factoring out some of the VT console commands into a separate package, so I could write something coherent like console.hideCursor() instead of stdout.write('\x1b[?25l').

有什么好玩的? VT转义序列,这是Kilo在屏幕更新期间如何操纵控制台以进行光标移动,清除屏幕和隐藏光标的操作。 现代Mac和Linux终端的这一古老部分在某种程度上得以生存40多年,而没有进行重大的现代化。 在最近的一次翻新中,它甚至在最近几年引入了Windows终端。 在此过程中的某个时刻,我忘记了何时开始将一些VT控制台命令分解为一个单独的程序包,因此我可以编写类似console.hideCursor()而不是stdout.write('\x1b[?25l')

After completing the tutorial, I had dart_kilo, a simple text editor that ran on macOS or Linux, just like the original, but in only ~500 lines of code, thanks to my separate lightweight console library.

完成本教程后,我有了dart_kilo ,这是一个简单的文本编辑器,可以在macOS或Linux上运行,就像原始版本一样,但要感谢我单独的轻量级控制台库,它只需要约500行代码。

But then I started wondering — could I port my Dart version of kilo to Windows? Most of the code worked fine, at least on a modern Windows terminal, with the exception of a couple of POSIX system calls to get the console window dimensions and set the terminal into raw mode, both of which would need conversion.

但是后来我开始怀疑-我可以将Dart版本的kg移植到Windows吗? 大多数代码至少在现代Windows终端上都能正常工作,除了几个POSIX系统调用以获取控制台窗口尺寸并将终端设置原始模式外,这两个都需要转换。

FFI简介 (Introducing FFI)

Dart includes dart:ffi, a library for making Foreign Function Interface calls to C-style APIs. Using FFI, you can declare a prototype for a C-based API and call it from your Dart code. As an example, the Win32 API function SetConsoleCursorPosition can be called with the following lines of Dart code:

Dart包含dart:ffi,一个用于对C风格的API进行外函数接口调用的库。 使用FFI,您可以声明基于C的API的原型,然后从Dart代码中调用它。 例如,可以使用以下Dart代码行来调用Win32 API函数SetConsoleCursorPosition

Now all I had to do was to create Win32 equivalents of those POSIX system calls, and the kilo editor ran on my Windows laptop without a single change to the editor itself:

现在,我要做的就是创建与这些POSIX系统调用等效的Win32,并且kg编辑器在Windows笔记本电脑上运行,而编辑器本身没有任何更改:

Image for post
kilo.dart: A console text editor in ~500 lines of code that runs on Windows, macOS and Linux.
kg.dart:包含约500行代码的控制台文本编辑器,可在Windows,macOS和Linux上运行。

从控制台到图形用户界面 (From the console to a graphical UI)

Image for post
ANSI extended 256-color support in dart_console
dart_console中的ANSI扩展256色支持

For a while, I continued exploring the Win32 console APIs, and gradually wrapping more of them in my dart_console package. Dart has a relatively basic set of console functions, and so I gradually built up a reasonably feature-rich package that offered color selection, cursor manipulation, REPL-style command input and control key processing. By separating interface and implementation, I was able to add features with support on both Windows and UNIX-like terminals, even including the Windows 7 console app, which doesn’t support VT escape sequences.

一段时间以来,我继续探索Win32控制台API,并逐渐将更多API封装在dart_console程序包中。 Dart具有相对基本的控制台功能集,因此我逐渐建立了一个功能丰富的程序包,该程序包提供颜色选择,光标操作,REPL样式的命令输入和控制键处理。 通过分离接口和实现,我能够在Windows和类似UNIX的终端上添加支持的功能,甚至包括不支持VT转义序列的Windows 7控制台应用程序。

But I was curious to see whether I could go further. Any self-respecting language should be able to call the Win32 MessageBox function, after all:

但是我很好奇我能否走得更远。 毕竟,任何自重的语言都应该能够调用Win32 MessageBox函数:

And then I started thinking about the sine qua non of Windows applications, the hello.c Hello World program presented by Charles Petzold in his seminal Programming Windows title. Could this be brought to Dart without needing Visual Studio or the Windows SDK?

然后,我开始考虑Windows应用程序的基本条件,即Charles Petzold在开创性的Windows编程标题中介绍hello.c Hello World程序。 是否可以在不需要Visual Studio或Windows SDK的情况下将其引入Dart?

hello.c is on the surface a much more complex challenge: it requires allocation of structs on the heap, callback functions, a WinMain() entry, and about twenty Win32 calls. At the time, I still hadn’t really got my head around structs in dart:ffi, nor fully grokked the translation between various Dart primitives and their C equivalents. In short, I wasn’t expecting success.

hello.c面临着一个更为复杂的挑战:它需要在堆上分配结构,回调函数,一个WinMain()条目以及大约20个Win32调用。 那时,我还没有真正了解dart:ffi中的结构,也没有完全理解各种Dart原语及其C等效项之间的转换。 简而言之,我没想到会成功。

So after a fair amount of trial and error, I was somewhat stunned when Windows displayed this small but exciting window:

因此,经过大量的反复试验,当Windows显示出这个小而令人兴奋的窗口时,我有些惊讶:

Image for post
The most unimaginative app of all time.
有史以来最具想象力的应用。

But what is cool is how similar the Dart code is to the original for anyone who cut their teeth writing traditional Windows code, albeit with the advantages of a somewhat more forgiving, strongly-typed language backing it:

但是,什么酷是Dart代码是多么相似于原始的人谁削减他们的牙齿写传统的Windows代码,虽然带着几分更多的宽容,强类型语言支持它的优点:

From here, I started to get a lot more excited about the potential of this. I created a separate package for the Win32 APIs, and started to wrap more of them. I started looking for other small Windows C applications that were permissively licensed and porting them across to Dart.

从这里开始,我开始对这种潜力感到更加兴奋。 我为Win32 API创建了一个单独的程序包,并开始包装更多它们。 我开始寻找其他获许可的小型Windows C应用程序,并将其移植到Dart。

For example, here’s Tetris:

例如,这是俄罗斯方块

Image for post
The world needed another implementation of Tetris. This time, in Dart.
世界需要再次实现“俄罗斯方块”。 这次是在Dart。

And (thanks again to Charles Petzold), a Notepad implementation, including menus, shortcut keys, find/replace and font selection:

并且(再次感谢Charles Petzold),记事本实现,包括菜单,快捷键,查找/替换和字体选择:

Image for post
Most programmers have one text editor in them. Apparently I had two.
大多数程序员中都有一个文本编辑器。 显然我有两个。

COM和Dart (COM and Dart)

By now I was getting more confident. In building (or translating) these more advanced apps with Dart, I’d wrapped a hundred or more Win32 APIs, including some more painstaking work to bring across the various constants and structs needed to use them, adding as many tests as I could, and starting to build out documentation for the embryonic package that I had released for pub.dev, the Dart package manager.

到现在,我变得更加自信。 在使用Dart构建(或翻译)这些更高级的应用程序时,我包装了一百或更多的Win32 API,包括一些艰巨的工作来介绍使用它们所需的各种常量和结构,并添加了尽可能多的测试,并开始为我为Dart软件包管理器pub.dev发布的原始软件包建立文档。

But I was starting to hit some limitations: in particular, more recent Win32 APIs often used the class-based COM model, which presented a different set of challenges. I figured this might be my glass ceiling, since the C-based interop libraries in Dart don’t mesh well with the C++ assumptions of COM. But then I discovered a lengthy CodeProject article dating back to 2006 that described how COM components could be called from plain old C, and so I decided to give it a go.

但是我开始遇到一些限制:特别是最近的Win32 API经常使用基于类的COM模型,这带来了一系列不同的挑战。 我认为这可能是我的玻璃天花板,因为Dart中基于C的互操作库与COM的C ++假设不太吻合。 但是后来我发现了一篇冗长的CodeProject文章,可追溯到2006年,该文章描述了如何从普通的旧C中调用COM组件,因此我决定尝试一下。

COM from C is ugly. A COM component supports one or more interfaces, such as IUnknown or IFileDialog, through which they expose methods to a calling application. The addresses of the methods themselves are stored in a virtual function table, and the details of the objects are stored in a rather painful format called MIDL (or Microsoft Interface Definition Language).

来自C的COM很难看。 一个COM组件支持一个或多个接口,例如IUnknownIFileDialog ,它们通过这些接口将方法公开给调用的应用程序。 方法本身的地址存储在虚拟函数表中,而对象的详细信息以痛苦的格式存储,称为MIDL (或Microsoft接口定义语言)。

I used to know some things about COM, when I was a younger man, but by now all the brain cells holding this knowledge had long atrophied. All the good books on COM were written twenty years ago. (They advertise as a point of pride that they come with a CD-ROM, since the Internet was still far from pervasive at the time.) To my amusement, I found myself scavenging online used bookstores to try and recover replacements for titles I’d given away at the turn of the century. If you’d told me in 2000 that I’d find these books relevant in 2020, I’d have either laughed or wept.

当我还是一个年轻人的时候,我曾经对COM有一些了解,但是到现在为止,所有拥有这种知识的脑细胞都已经萎缩了。 所有关于COM的好书都是二十年前写的。 (由于当时互联网还很普及,他们自豪地宣称它们带有CD-ROM。)令我高兴的是,我发现自己在网上寻找二手书店,以尝试替换我所用的书名。 d在世纪之交被放弃。 如果您在2000年告诉我,我会在2020年找到这些相关的书,那我要么大笑要么哭泣。

Image for post
My debugging approach, visualized.
我的调试方法已可视化。

I spent months — I mean months — trying to get the damn thing working. In theory I understood what I needed to do: initialize COM and create an instance of the class, dig into the vtable to find the method I needed, map the address of that method to the Dart prototype I’d created, and then call the result. But my commits of the time demonstrate a litany of failed attempts, with endless print statements and futile explorations in both Dart and C to try and figure out what I’d done wrong. I’d put it away for a couple of weeks and work on something else, and then come back to make the same mistakes I’d already made in the hope of a different answer. Dear reader: my coding is the equivalent of a random walk through a maze, hoping against hope that my Brownian motion will eventually lead me to the exit.

我花了几个月的时间-我的意思是几个月-试图使该死的东西正常工作。 从理论上讲,我理解了我需要做的事情:初始化COM并创建类的实例,深入vtable以查找所需的方法,将该方法的地址映射到我创建的Dart原型,然后调用结果。 但是我当时的承诺证明了一系列失败的尝试,在Dart和C中无休止的打印语句和徒劳的探索试图找出我做错了什么。 我把它放了几个星期,然后再做其他事情,然后又回来犯同样的错误,以期得到不同的答案。 亲爱的读者:我的编码相当于在迷宫中随机穿行,希望我的布朗运动最终将我引向出口。

And then, suddenly, daylight! I found a pointer dereferencing bug for the third time, but on this occasion, the other two bugs that had prevented me making headway had been overcome. My code was horrid, but functional for the single method I needed.

然后,突然间,白昼! 我第三次发现了一个指针,该指针不再引用该错误,但是在这种情况下,其他两个使我无法取得进展的错误得以克服。 我的代码很糟糕,但是可以满足我所需的单一方法的要求。

Image for post
My first COM success: the modern Windows file open dialog with IFileDialogOpen.
我的第一个COM成功:使用IFileDialogOpen的现代Windows文件打开对话框。

One problem remained: the manual approach I’d adopted for Win32 was just unfeasible for COM. The IFileDialog interface alone is 23 methods, not counting the 30+ other methods I’d need to implement to implement the other associated interfaces. Clearly I needed a different approach. So I started a brute force parser for some of the header metadata in the Windows SDK, allowing me to read in a file like this:

一个问题仍然存在:我为Win32采用的手动方法对于COM来说是不可行的。 仅IFileDialog接口是23种方法,还不包括我实现其他相关接口所需实现的30多种其他方法。 显然,我需要一种不同的方法。 因此,我为Windows SDK中的某些标头元数据启动了蛮力分析器,使我可以读取以下文件:

and convert it to something like this:

并将其转换为如下所示:

This may be some of the least attractive Dart code ever written (at least, aesthetically), but that’s OK. It’s machine-generated, faithful to the original COM interface, and is unlikely to need close inspection. With a lightweight wrapper to shield the non-idiomatic code from the light of day, you can now write something like this:

这可能是有史以来吸引力最低的Dart代码(至少从美学角度而言),但这没关系。 它是机器生成的,忠实于原始的COM接口,因此不太可能需要仔细检查。 使用轻量级包装器可以保护非惯用代码免受日后的影响,您现在可以编写如下内容:

Now I could start to build out other packages that depended on the COM APIs, such as filepicker_windows. And even write simple Windows utilities that combine Flutter UI with Win32 APIs:

现在,我可以开始构建依赖于COM API的其他软件包,例如filepicker_windows 。 甚至编写将Flutter UI与Win32 API结合在一起的简单Windows实用程序

Image for post
A basic Windows-only app written with Flutter that sets the desktop background from a user-selected file. Pretty useless, but a good end-to-end test of Flutter and Win32 APIs working in harmony.
用Flutter编写的基本的Windows专用应用程序,它可以根据用户选择的文件设置桌面背景。 Flutter和Win32 API可以很好地协同工作,但它没什么用,但却是一个很好的端到端测试。

Win32软件包 (The Win32 Package)

Over the last few months, I’ve been gradually refining and improving the package, building out samples and adding documentation. The package now supports hundreds of APIs, and a wide variety of COM APIs, with code generators doing much of the heavy lifting. More recently, I’ve been working to provide a projection for the latest Windows Runtime APIs as used in UWP apps, which opens up some other intriguing possibilities. But that’s a story for another time.

在过去的几个月中,我一直在逐步完善和改进该软件包,构建示例并添加文档。 该软件包现在支持数百个API和各种COM API,其中代码生成器承担了许多繁重的工作。 最近,我一直在为UWP应用程序中使用的最新Windows运行时API提供一个预测,这为其他有趣的可能性开辟了道路。 但这是另一个故事。

One of the most gratifying aspects of contributing a package like this back to the community is having others file issues, depend on your code, or even submit pull requests. I’ve enjoyed seeing folk like Tomek Polanski use it for his fast_flutter_driver test harness, and working with others to add new APIs for packages like biometric_storage. We’re even now using it for the Windows implementation of Flutter packages like path_provider.

向社区贡献像这样的程序包最令人高兴的方面之一就是让其他文件出现问题,取决于您的代码,甚至提交请求请求。 我很高兴看到像Tomek Polanski这样的人将其用于他的fast_flutter_driver测试工具,并与其他人一起为biometric_storage之类的软件包添加新的API。 我们甚至现在将其用于Flutter软件包Windows实现,例如path_provider

And as a product manager, this whole experience has continued to refine and improve my understanding of how our product feels to others. There are friction points that were previously merely academic and are now viscerally felt; I’ve found some new bugs (and filed issues for them); and submitted numerous documentation pull requests that hopefully ease the path for those following. At the same time, product managers need to avoid the highly seductive trap of putting too much emphasis on our own direct observations— we’re building for customers, and it’s critical to temper personal experience with data-driven insights and customer feedback.

作为产品经理,整个经验不断完善和提高了我对我们产品对他人的感觉的理解。 有一些摩擦点以前只是学术上的,而现在却内在地感觉到了。 我发现了一些新的错误(并为它们提出了问题); 并提交了许多文档请求请求,希望可以简化后续工作。 同时,产品经理需要避免过于诱人的陷阱,即过分强调我们自己的直接观察力-我们是在为客户服务,而通过数据驱动的洞察力和客户反馈来改善个人经验至关重要。

Win32 is now available at pub.dev, and I’d be honored if you use it for your own projects.

Win32现在可在pub.dev上获得,如果您将其用于自己的项目,我将感到很荣幸。

Image for post
“The road goes ever on and on… and I must follow if I can…”
“道路不断前进……如果可以的话,我必须遵循……”

翻译自: https://medium.com/@timsneath/windows-fun-with-dart-ffi-687c4619e78d

乐趣下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值