每个桌面应用程序都是一个超级网页浏览器
每个基于 PE 可执行文件的 64 位桌面应用程序都有一种“超级网络浏览器”启动模式,允许开发者直接为其编写任意数量的网页。
我们需要解决什么问题?
如果网页 DOM 描述技术能够直接且深入地修改Windows 操作系统原生窗口对象的 UI 结构,那么桌面应用世界将发生巨大变化。
我们将要解决的问题是,如何为现有或正在开发中的 64 位桌面应用程序,特别是那些使用 .NET、C++/MFC 和 Win32 SDK 开发的应用程序,提供一个“超级网页内容生态系统”,从而使普通桌面应用程序拥有超越当前网页浏览器的网页内容处理能力,并使原生桌面窗口对象成为一个可与“html div 元素”相媲美的扩展网页 DOM 元素,即使开发者没有这些应用程序的源代码,具体来说:
- 允许开发者直接将普通桌面应用程序视为超越标准网页浏览器的“超级网络浏览器”,开发者可以直接为普通桌面应用程序编写任意数量的网页。
- 提供一个基于网页 DOM 的动态描述驱动机制来控制“原生桌面窗口对象的 UI 结构”,开发者可以像在网页中“组织 HTML div 元素来表达丰富的网页内容”一样,组装“复合原生窗口对象”。
- 所有大语言模型技术都为网页浏览器提供了最友好的支持,一旦桌面应用程序可以被视为“超级网络浏览器”,这意味着开发者可以将大语言模型技术深度集成到普通桌面应用程序的内容生态系统中,即使他们没有相应桌面应用程序的源代码。
本文的背景与愿景
至今为止,开发者已经基于 .NET、C++/MFC 和 Win32 SDK 编译了无数的 64 位桌面应用程序,同时还有无数的桌面应用程序正在开发中。当一个桌面应用程序完成原生代码编译后,其刚性将主导其运行时行为,具体来说,原生桌面窗口的结构在运行时基本上处于一种相对固定的状态,这使得开发者很难在运行时对其进行合理的调整和扩展。当我们面对一个已编译的 MFC 桌面应用程序或一个已编译的 WinForm 桌面应用程序时:
如果我们希望在其主窗口对象中添加一个“网页浏览器标签组”,或者我们想使用像 ChatGPT 这样的大语言模型技术来修改其主窗口结构,如下图所示:
当我们面对上述问题时,我们可能会无能为力,即使我们拥有相应应用程序的源代码,我们现有的技术也很难解决这类问题。
如果我们有可能发现控制原生窗口对象结构的“动态描述驱动机制”,那么对于那些已经编译或正在开发的桌面应用程序,无论是基于网页技术还是ChatGPT 技术来调整或修改原生窗口对象的 UI 结构,“动态描述驱动机制”都将成为“动态应用内容”创新的驱动力,我们将进入一个全新的桌面应用世界。
本文的愿景是揭示每个 64 位桌面应用程序都具有超越标准网络浏览器的网页解析能力,从而拥有自己的网页内容生态系统,允许开发者直接为 64 位桌面应用程序编写任意数量的网页。所有现代大语言模型技术都为网页浏览器提供了最友好的支持,这无疑为大语言模型技术深度融入桌面应用程序奠定了客观基础。
AIGC 浏览器与动态集成 X 对象
AIGC 浏览器是一个基于 Chromium 的开源网络浏览器,它将原生窗口对象视为“描述驱动的超级 HTML 元素”,就像在自然语言处理(NLP)中用一组词元(token)组织句子一样,AIGC 浏览器允许开发者从一个“现有的子窗口”出发,基于网页 DOM 技术组装功能强大的“复合窗口”,这赋予了“网页 DOM 技术”对原生窗口对象结构的强大控制力,使得网页技术和大语言模型技术能够完全融入现代桌面软件。
如果你的应用程序的可执行文件是“exeName.exe”,并且包含该可执行文件的文件夹中存在名为“exeName.app.html”的初始化网页,那么:
- 对于已编译的桌面应用程序,在该应用程序启动后启动“AIGC 浏览器”,或在“AIGC 浏览器”启动后启动该应用程序。
- 对于正在开发中的桌面应用程序,添加或修改不超过 5 行原生代码,即可让应用程序直接激活其内置的 AIGC 浏览器。
无论你的应用程序属于以上哪种情况,AIGC 浏览器都会为你的应用程序的原生窗口对象生成相应的“Web DOM”元素,网页技术将完全融入你的应用程序的内容生态系统。你的应用程序将容纳现代网页浏览器的所有功能,并会生成越来越多的网页,如下图所示,借助“exeName.app.html”中的超链接:
你的桌面应用程序将直接打开任意数量的网页,生成各种浏览器窗口。
这意味着开发者可以直接为 64 位桌面应用程序编写任意数量的网页,就好像它们本身就是一个现代浏览器一样。
当“整个网络浏览器”直接成为桌面应用程序功能结构的一部分时,符合行业标准的 UI 组件,如 .NET UI 组件、ActiveX 控件等,将自然而然地成为某种“扩展的 Web DOM 元素”。同时,由于所有大语言模型技术都支持网络浏览器,开发者可以直接在现有桌面应用程序中使用像 ChatGPT 这样的技术。例如,可以利用 ChatGPT 的“多模态输入”技术来直接操作原生桌面窗口,并可能向其添加新的 UI 元素。
桌面应用程序中的“复合窗口”可以将其“网页浏览器窗口”视为其“动态子窗口”,并进一步使这个“复合窗口”成为一个超级网页内容门户。例如,一个如下图所示的 MFC 框架窗口:
在实际场景中,它可以被用作一个超级浏览器窗口。
AIGC 浏览器提供了一个高度通用、依赖最少且源代码独立的解决方案,用于将 X 对象动态集成到普通的 64 位桌面应用程序中,其中“X 对象”可以是以下任意一个或一组:
- 全功能基于 Chromium 的网页浏览器子系统。
- .NET Core/.NET Framework 引擎。
- 你感兴趣的任何类型的窗口对象,如网页、WinForms/User Control、MFC 窗口,或更通用的桌面窗口对象类型。
原生桌面窗口对象作为网页内容入口
如果一个原生窗口对象的运行时屏幕位置唯一地依赖于其父窗口,那么该窗口被称为其父窗口的“窗口核”,而父窗口被称为“有核窗口”。
如下图所示,我们可以看到一个普通的 MFC 框架窗口,其中心位置呈现的“FormView”对象就是一个“窗口核”。
在运行时,我们为“窗口核(FormView)”分配不同的网页 DOM 元素,“FormView”的外围呈现出完全不同的结构变化。
如果我们将以下网页 DOM 元素串联到 MFC 框架中的“FormView”对象上:
<someWindow>
<nucleus>
<xobj rows="3" cols="3"
width="150,100,100"
height="200,180,100">
<xobj></xobj>
<xobj></xobj>
<xobj></xobj>
<xobj></xobj>
<xobj objid="nucleus">
</xobj>
<xobj></xobj>
<xobj></xobj>
<xobj></xobj>
<xobj></xobj>
</xobj>
</nucleus>
</someWindow>
我们将在“FormView”的外围看到一个呈现出的“布局结构”:
如果我们将以下网页 DOM 元素串联到 MFC 框架中的“FormView”对象上:
<someWindow>
<nucleus>
<xobj rows="1" cols="2" width="350," id="xxx">
<xobj id=""></xobj>
<xobj rows="3" cols="1" heigh="150,150" id="xxx">
<xobj id=""></xobj>
<xobj style="23">
<xobj objid="nucleus"
caption="Using Tabbed Window">
</xobj>
<xobj objid="SunnyCtrl.UserControl3,SunnyCtrl"
caption="Second Page">
</xobj>
<xobj objid="SunnyCtrl.UserControl2,SunnyCtrl"
caption="Third Page">
</xobj>
</xobj>
<xobj id=""></xobj>
</xobj>
</xobj>
</nucleus>
</someWindow>
我们将在“FormView”的外围看到另一个呈现出的“布局结构”:
通过以上两个场景,我们可以看到“窗口核”串联不同的网页 DOM 元素会产生完全不同的外围布局结构。由于网页 DOM 元素的数量是无限的,这意味着在运行时,“窗口核”的外围将会有无穷无尽的结构变化,这实际上揭示了与“窗口核”相关联的一种“新型网页内容入口”的存在。
安装和构建 AIGC 浏览器
开发者可以访问 AIGCBrowser 获取 AIGC 浏览器的安装包。安装 AIGC 浏览器后,打开“AIGCBrowser.sln”即可获取 AIGC 浏览器的源代码及各种示例。
安装 AIGC 浏览器后,开发者将得到一个 Visual Studio 解决方案“AIGCBrowser.sln”,其中包含了 AIGC 浏览器的源代码和各种示例。
如果开发者熟悉 C++,可以直接编译 AIGC 浏览器的“核心组件”。如果开发者需要深入了解 X 对象的具体集成细节,可以参考“UniversePro”项目。对于“.Net 部分”,则需要参考“CosmosPro”项目。如果你是 C# 开发者,可以忽略所有相关的 C++ 项目,直接使用即可。
如果开发者熟悉 Chromium 项目并正确安装了 AIGC 浏览器安装包,他们将获得“ChromiumSrcPatch”文件夹。开发者首先需要获取与 AIGC 浏览器版本匹配的 Chromium 项目代码分支,并确保该分支能够正常编译。然后,他们需要将 ChromiumSrcPatch 文件夹中包含的所有子文件夹复制到该代码分支中,并重新编译更新后的 Chromium 代码分支,以完成 AIGC 浏览器 Chromium 子系统的编译。由于代码的复杂性以及编译基于 Chromium 的 C++ 代码通常需要 6-8 小时,大多数开发者不需要执行此步骤,可以直接专注于桌面应用程序的开发。
关于清单(manifest)配置
现代浏览器的网页渲染技术要求浏览器的可执行文件支持Win10 兼容性,因此,开发者需要在编译过程中提供符合要求的清单文件。有关清单配置的具体信息,请参考微软提供的清单配置相关文档。一个典型的清单文件“aigc.manifest”的基本结构如下:
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
在编译过程中,开发者需要按如下图所示配置编译所需的清单文件:
对于未提供清单配置的桌面应用程序,AIGC 浏览器可能会调整相应桌面应用程序的清单配置以使其兼容 Win10,开发者将看到以下消息框:
出现此消息框的原因是,相应的桌面应用程序源代码中没有关于“Win10 兼容性”的清单配置。AIGC 浏览器将在特定应用程序的启动阶段“修复”这些缺失的配置。
使用 Visual Studio向导
由 Visual Studio 向导生成的每个桌面应用程序在编译后都具有一种网页开发机制,尽管开发者完全不知道其存在或没有刻意为其存在做准备。对于由 Visual Studio 向导生成的桌面应用程序(WinForms、MFC 应用程序等),开发者可以先提供一个“不含任何内容的空白初始化页面”,在运行时 AIGC 浏览器将基于此页面生成一个与应用程序类型匹配的初始化页面,供开发者继续后续工作。
由于文章篇幅限制,我们仅简要演示 AIGC 浏览器在 .Net Framework WinForm 类型应用程序中的应用过程。我们建议本文的读者根据 Visual Studio 向导生成的各种应用程序类型,如 .Net Core 和 MFC 等,逐一体验 AIGC 浏览器的应用过程。
我们首先使用“Visual Studio 向导”创建一个最简单的“WinForm 应用程序 WindowsFormsApp1”,你需要在 WinForm 对象上添加一个 Dock 属性值为“DockFill”的 panel 控件。
选择 64 位编译,所有选项均为默认。编译后,我们将得到可执行文件“WindowsFormsApp1.exe”。在包含该可执行文件的文件夹中,提供一个名为“WindowsFormsApp1.app.html”的“空白”网页。启动该应用程序后,再启动 AIGC 浏览器,我们将看到如下的运行时 WinForm 对象和一个“网页浏览器窗口”:
我们注意到,这里的 WinForm 应用程序只是一个普通的桌面应用程序,其对应的项目不包含任何与网页浏览器相关的控件或代码。然而,在它的运行过程中,它确实创建了“网页浏览器窗口对象”。我们看到,“网页浏览器窗口对象”可以成为 WinForm 对象的一个“组件”。如果我们将“C# 项目”替换为“MFC 项目”,我们将得到完全一致的实验结论。
使用示例
“AIGCBrowser.sln”中的所有示例都可以基于原生代码直接激活 AIGC 浏览器,这意味着在编译这些示例后,一旦开发者提供了相应应用程序的初始化网页,相关的网页开发机制也会被激活,无需单独启动 AIGC 浏览器。
对于 .NET 开发者,他们需要关注“AIGCBrowser.sln”中的“AIGCBrowserSharedProject”项目项,它是一个“共享项目”。一旦其他 .Net 项目引用了该项目,开发者只需将:
Application.Run
用
替换为 AIGC.AIGCApp.InitCosmos
即可完成在运行时直接激活其网页内容生态系统。
对于 C++ 开发者,我们提供了一对 C++ 源文件:
aigc.h 和 aigc.cpp
这两个文件包含了 MFC 应用程序的 CWinApp(Ex) 类的派生类和 ATL 应用程序的 CAtlExeModuleT 类的派生类,以及 Win32 SDK 应用程序所必需的 C++ 类。
MFC 开发者只需在他们的 MFC 应用程序中替换应用程序类的基类:
从“CWinApp(Ex)”替换为“CAIGCApp(Ex)”
重新编译后,应用程序本身的网页内容生态系统就可以在运行时直接激活。对于像 ATL 和 Win32 SDK 这样的应用程序,该解决方案提供了类似 MFC 应用程序的处理机制。具体细节请参考解决方案中的具体示例。
如何使用 AIGCSDK
如果你需要在桌面应用程序内部直接激活其网页内容生态系统,你需要下载 AIGC SDK 包,解压后即可获得 SDK 包。开发者应注意,所有与 AIGC SDK 相关的桌面应用程序都必须基于 64 位进行编译。
如何在 C# WinForm 应用程序中使用 AIGCSDK
AIGC SDK 包含一个C# 共享项目 'AIGCBrowserSharedProject.shproj',其中包含一个具有以下结构的 C# 静态类:
// The static class "AIGCApp" comes from the .NET shared project "AIGCBrowserSharedProject",
// which is included in "AIGCBrowser.sln". All WinForm projects can reference this shared project at the project level
//
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace AIGC
{
static public class AIGCApp
{
static IntPtr initDll = IntPtr.Zero;
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void InitWebRT(IntPtr IUnkPtr, IntPtr IUnkWndPtr);
private static string BuildConfigDataFile(string strExeName, string strProductName, string strCompanyPathName)
{
string _strProductName = strProductName.ToLower();
string _strCompanyPathName = strCompanyPathName.ToLower();
StringBuilder sb = new StringBuilder();
sb.Append(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)).Append("\\TangramData\\").Append(strExeName).Append("\\");
string _strConfigDataFile = sb.ToString().ToLower();
if (Directory.Exists(_strConfigDataFile) == false)
Directory.CreateDirectory(_strConfigDataFile);
using (var md5 = MD5.Create())
{
StringBuilder sb2 = new StringBuilder();
_ = sb2.Append(_strConfigDataFile).Append("@").Append(_strCompanyPathName).Append("@").Append(_strProductName);
var result = md5.ComputeHash(Encoding.UTF8.GetBytes(sb2.ToString()));
_strConfigDataFile += BitConverter.ToString(result).Replace("-", "");
}
_strConfigDataFile += "\\";
if (Directory.Exists(_strConfigDataFile) == false)
Directory.CreateDirectory(_strConfigDataFile);
_strConfigDataFile += strExeName;
_strConfigDataFile += ".tangram";
return _strConfigDataFile;
}
public static bool InitCosmos(object StartObj, object MainWndObj = null)
{
initDll = LoadLibrary(@"universe.DLL");
if (initDll == IntPtr.Zero)
{
String strCfgFile = BuildConfigDataFile("aigcbrowser", "aigcbrowser", "Tangram Team");
if (File.Exists(strCfgFile))
{
string strData = File.ReadAllText(strCfgFile);
String strTemp = strData.Substring(strData.IndexOf("Universe") + 8);
if (String.IsNullOrEmpty(strTemp) == false)
{
strTemp = strTemp.Substring(0, strTemp.IndexOf(".dll") + 4);
initDll = LoadLibrary(strTemp.Substring(strTemp.IndexOf(":") - 1));
}
}
}
if (initDll == IntPtr.Zero)
{
StringBuilder sb = new StringBuilder();
sb.Append(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)).Append("\\Tangram\\AIGCBrowser\\universe.dll");
string strLib = sb.ToString();
if (File.Exists(strLib))
{
initDll = LoadLibrary(strLib);
}
}
if (initDll != IntPtr.Zero)
{
IntPtr fnInitWebRT = GetProcAddress(initDll, "InitWebRT");
if (fnInitWebRT != IntPtr.Zero)
{
InitWebRT InitWebRT = (InitWebRT)Marshal.GetDelegateForFunctionPointer(fnInitWebRT, typeof(InitWebRT));
InitWebRT((StartObj != null) ? Marshal.GetIUnknownForObject(StartObj) : IntPtr.Zero, (MainWndObj != null) ? Marshal.GetIUnknownForObject(MainWndObj) : IntPtr.Zero);
return true;
}
}
return false;
}
}
}
开发者的 C# WinForm 项目需要引用共享项目 'AIGCBrowserSharedProject.shproj'。
引用 AIGCBrowserSharedProject 后,打开该 C# 项目的 'program.cs' 文件,并按如下方式重写 main 函数:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form startForm = new Form1();
if (AIGC.AIGCApp.InitCosmos(startForm) == true) return;
Application.Run(startForm);
}
}
如何在 C++ 应用程序中使用 AIGCSDK
C++/ATL/MFC 开发者如何处理预编译头文件
将 AIGC SDK 中的“aigc.h”和“aigc.cpp”复制到 C++ 桌面软件项目中。通常,C++ 桌面应用程序项目会有一对“预编译”头文件,“stdafx.h”和“stdafx.cpp”,或者“pch.h”和“pch.cpp”,具体取决于 Visual Studio 的版本。
在 C++ 桌面应用程序项目中,打开预编译文件“stdafx.h”或“pch.h”,并添加以下代码:
#include "AIGC.h"
在“stdafx.h”或“pch.h”的底部。
//
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently,
// but are changed infrequently
#pragma once
#ifndef STRICT
#define STRICT
#endif
#include "targetver.h"
#define _ATL_APARTMENT_THREADED
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _AFX_NO_MFC_CONTROLS_IN_DIALOGS
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit
#include "framework.h"
#include "AIGC.h"
接下来,打开“stdafx.cpp”或“pch.cpp”,并添加以下代码:
#include "AIGC.cpp"
在“stdafx.cpp”或“pch.cpp”的底部。
//
// stdafx.cpp : source file that includes just the standard includes
// TangramExcelTabWnd.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
#include "AIGC.cpp"
如何在 MFC 应用程序中使用 AIGCSDK
在 MFC 项目中,打开应用程序类所在的“头文件”。
//
// Any source code blocks look like this
//
// CMFCSDIApp:
// See MFCSDIApp.cpp for the implementation of this class
//
class CMFCSDIApp : public CWinApp
{
public:
CMFCSDIApp() noexcept;
// Overrides
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
// Implementation
afx_msg void OnAppAbout();
DECLARE_MESSAGE_MAP()
};
将应用程序类的基类 'CWinApp(Ex)' 替换为 'CAIGCApp(Ex)'。
//
//
// CMFCSDIApp:
// See MFCSDIApp.cpp for the implementation of this class
//
class CMFCSDIApp : public CAIGCWinApp
{
public:
CMFCSDIApp() noexcept;
// Overrides
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
// Implementation
afx_msg void OnAppAbout();
DECLARE_MESSAGE_MAP()
};
如何在 ATL 应用程序中使用 AIGCSDK
在 ATL 项目中,打开 AtlExeModule 类所在的“头文件”。
#include "pch.h"
#include "framework.h"
#include "resource.h"
#include "ATLApp_i.h"
using namespace ATL;
class CATLAppModule : public CAtlExeModuleT< CATLAppModule >
{
public:
DECLARE_LIBID(LIBID_ATLAppLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_ATLAPP, "{5a319c27-7415-4304-80ee-4bce0f6759da}")
};
CATLAppModule _AtlModule;
//
extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
LPTSTR /*lpCmdLine*/, int nShowCmd)
{
return _AtlModule.WinMain(nShowCmd);
}
将 AtlExeModule 类的基类 'CAtlExeModuleT' 替换为 'CAIGCModuleT'。
#include "pch.h"
#include "framework.h"
#include "resource.h"
#include "ATLApp_i.h"
using namespace ATL;
class CATLAppModule : public CAIGCModuleT< CATLAppModule >
{
public:
DECLARE_LIBID(LIBID_ATLAppLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_ATLAPP, "{5a319c27-7415-4304-80ee-4bce0f6759da}")
};
结论和关注点
每个 Windows 64 位桌面应用程序都有一个“超级网页内容生态系统”,即使开发者没有该应用程序的源代码,他们仍然可以使用 AIGC 浏览器来开启这种“网页生态模式”,这对于桌面应用程序的生产力至关重要。如果开发者计划将像 ChatGPT 这样的 AI 技术集成到普通桌面应用程序中,那么在开启这个“超级网页内容生态系统”之后,将“ChatGPT”或大语言模型技术集成到桌面应用程序中将成为一个网页技术问题,这也是网页开发者全面参与桌面应用程序开发的最简单方式。
有关 AIGC 浏览器的技术细节,您可以访问我们的 GitHub 网站。