当前位置: 首页 > news >正文

购物网站页面布局百度指数疫情

购物网站页面布局,百度指数疫情,建行今日最新存款利率,网站开发运营成本这是我用不到 370 行代码,从零开始控制台创建 Win32 窗口,再挂上交换链,在窗口上使用 D2D 绘制界面内容。最后使用 AOT 方式发布的测试应用。成品文件体积不超过 10MB 且运行内存稳定在 60MB 以内,满帧率运行但 CPU 近乎不动 整个…

这是我用不到 370 行代码,从零开始控制台创建 Win32 窗口,再挂上交换链,在窗口上使用 D2D 绘制界面内容。最后使用 AOT 方式发布的测试应用。成品文件体积不超过 10MB 且运行内存稳定在 60MB 以内,满帧率运行但 CPU 近乎不动

整个测试应用采用了 .NET 8 的框架,用于更好的支持 AOT 发布

使用了 Vortice 系列库用于对 DirectX 的封装,方便让编写调用 DirectX 的代码

使用了 Microsoft.Windows.CsWin32 方便进行 Win32 方法的调用

所有的代码都写在 Program.cs 文件里面,代码长度不到 370 行,更有趣的是,可以强行算是都写在 Main 方法里面,由 Main 方法以及放在 Main 方法里面的局部方法构成。整体实现非常简单。我将会在本文末尾告诉大家本文的代码的下载方法

本文仅仅是分享我的开发经验,不包含 DirectX 的前置知识。如果不熟悉 D2D 和 DirectX 还请以看着玩的心态阅读本文

一开始采用了 DirectX 使用 Vortice 从零开始控制台创建 Direct2D1 窗口修改颜色 和 dotnet DirectX 通过 Vortice 控制台使用 ID2D1DeviceContext 绘制画面 博客提供的方法搭建了基础的应用框架

为了让界面更加的丰富,我准备在界面添加多个圆形。然后为了让界面动起来,我添加了名为 DrawingInfo 的结构体,用于存放每个圆形的坐标和大小等信息

readonly record struct DrawingInfo(System.Numerics.Vector2 Offset, Size Size, D2D.ID2D1SolidColorBrush Brush);

先在绘制的循环外对 DrawingInfo 进行随机设置值

var ellipseInfoList = new List<DrawingInfo>();for (int i = 0; i < 3000; i++){// 随意创建颜色var color = new Color4((byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255));D2D.ID2D1SolidColorBrush brush = renderTarget.CreateSolidColorBrush(color);ellipseInfoList.Add(new DrawingInfo(new System.Numerics.Vector2(Random.Shared.Next(clientSize.Width), Random.Shared.Next(clientSize.Height)), new Size(Random.Shared.Next(10, 100)), brush));}

进入循环之后,再每次修改 Offset 的值,这样就可以让每次绘制的圆形动起来

while (true){// 开始绘制逻辑renderTarget.BeginDraw();// 清空画布renderTarget.Clear(new Color4(0xFF, 0xFF, 0xFF));// 在下面绘制漂亮的界面for (var i = 0; i < ellipseInfoList.Count; i++){var drawingInfo = ellipseInfoList[i];var vector2 = drawingInfo.Offset;vector2.X += Random.Shared.Next(200) - 100;vector2.Y += Random.Shared.Next(200) - 100;while (vector2.X < 100 || vector2.X > clientSize.Width - 100){vector2.X = Random.Shared.Next(clientSize.Width);}while (vector2.Y < 100 || vector2.Y > clientSize.Height - 100){vector2.Y = Random.Shared.Next(clientSize.Height);}ellipseInfoList[i] = drawingInfo with { Offset = vector2 };// 忽略其他代码}// 忽略其他代码}

以上的修改坐标代码只是为了让圆形每次都在其附近移动

附带就在里层循环将每个圆形绘制,代码如下

// 在下面绘制漂亮的界面for (var i = 0; i < ellipseInfoList.Count; i++){// 忽略其他代码renderTarget.FillEllipse(new D2D.Ellipse(vector2, drawingInfo.Size.Width, drawingInfo.Size.Height), drawingInfo.Brush);}

大概的改动如此,接下来咱需要改造一下 csproj 项目文件,让此项目可以构建出 AOT 版本的应用

先修改 TargetFramework 为 net8.0 使用 .NET 8 可以更好构建 AOT 应用

<PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>

接着为了减少不断提示的平台警告,添加以下代码忽略 CA1416 警告

<PropertyGroup><TargetFramework>net8.0</TargetFramework><NoWarn>CA1416</NoWarn></PropertyGroup>

接着再添加 PublishAot 属性,这样调用发布命令之后,就可以自动创建 AOT 应用的文件

<PropertyGroup><TargetFramework>net8.0</TargetFramework><NoWarn>CA1416</NoWarn><PublishAot>true</PublishAot></PropertyGroup>

此时运行起来将不会成功,将会提示大概如下的错误

Unhandled Exception: System.MissingMethodException: No parameterless constructor defined for type 'Vortice.DXGI.IDXGIFactory2'.at System.ActivatorImplementation.CreateInstance(Type, BindingFlags, Binder, Object[], CultureInfo, Object[]) + 0x348at SharpGen.Runtime.MarshallingHelpers.FromPointer[T](IntPtr) + 0x8cat Vortice.DXGI.DXGI.CreateDXGIFactory1[T]() + 0x55at Program.<<Main>$>g__CreateD2D|0_2(Program.<>c__DisplayClass0_0&) + 0x90at Program.<Main>$(String[] args) + 0x23eat CedageawhakairnerewhalNaibiferenagifee!<BaseAddress>+0x17a3c0

或者是如下的错误

Unhandled Exception: System.MissingMethodException: No parameterless constructor defined for type 'Vortice.Direct3D11.ID3D11Device1'.at System.ActivatorImplementation.CreateInstance(Type, BindingFlags, Binder, Object[], CultureInfo, Object[]) + 0x348at SharpGen.Runtime.MarshallingHelpers.FromPointer[T](IntPtr) + 0x8cat SharpGen.Runtime.ComObject.QueryInterface[T]() + 0x64at Program.<<Main>$>g__CreateD2D|0_2(Program.<>c__DisplayClass0_0&) + 0x1c7at Program.<Main>$(String[] args) + 0x23eat CedageawhakairnerewhalNaibiferenagifee!<BaseAddress>+0x335cf0

这是因为这些引用的库里面的类型在 AOT 的裁剪过程被丢掉

修复的方法很简单,那就是将 Vortice 添加到 TrimmerRootAssembly 里面,防止在 AOT 过程被裁剪

<ItemGroup><TrimmerRootAssembly Include="Vortice.Win32"/><TrimmerRootAssembly Include="Vortice.DXGI"/><TrimmerRootAssembly Include="Vortice.Direct3D11"/><TrimmerRootAssembly Include="Vortice.Direct2D1"/><TrimmerRootAssembly Include="Vortice.D3DCompiler"/><TrimmerRootAssembly Include="Vortice.DirectX"/><TrimmerRootAssembly Include="Vortice.Mathematics"/></ItemGroup>

修改之后的 csproj 代码如下

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable><PublishAot>true</PublishAot><NoWarn>CA1416</NoWarn></PropertyGroup><ItemGroup><PackageReference Include="Vortice.Direct2D1" Version="2.1.32" /><PackageReference Include="Vortice.Direct3D11" Version="2.1.32" /><PackageReference Include="Vortice.DirectX" Version="2.1.32" /><PackageReference Include="Vortice.D3DCompiler" Version="2.1.32" /><PackageReference Include="Vortice.Win32" Version="1.6.2" /><PackageReference Include="Microsoft.Windows.CsWin32" PrivateAssets="all" Version="0.2.63-beta" /></ItemGroup><ItemGroup><TrimmerRootAssembly Include="Vortice.Win32"/><TrimmerRootAssembly Include="Vortice.DXGI"/><TrimmerRootAssembly Include="Vortice.Direct3D11"/><TrimmerRootAssembly Include="Vortice.Direct2D1"/><TrimmerRootAssembly Include="Vortice.D3DCompiler"/><TrimmerRootAssembly Include="Vortice.DirectX"/><TrimmerRootAssembly Include="Vortice.Mathematics"/></ItemGroup>
</Project>

完成以上配置之后,即可使用命令行 dotnet publish 将项目进行发布,如果在发布的控制台可以看到 Generating native code 输出,那就证明配置正确,正在构建 AOT 文件

完成构建之后,即可在 bin\Release\net8.0\win-x64\publish 文件夹找到构建输出的文件,在我这里看到的输出文件大小大概在 10MB 以下,大家可以尝试使用本文末尾的方法拉取我的代码自己构建一下,试试效果

运行起来的任务管理器所见内存大小大约是 30MB 左右,通过 VMMap 工具查看 WorkingSet 和 Private Bytes 都在 60MB 以内。虽然 Committed 的内存高达 300MB 但是绝大部分都是 Image 共享部分占用内存,如显卡驱动等部分的占用,这部分占用大约在 250MB 以上,实际的 Image 的 private 的占用不到 10MB 大小

我认为这个技术可以用来制作一些小而美的工具,甚至是不用考虑 x86 的,只需考虑 x64 的机器上运行的应用的安装包制作程序。要是拿着 D2D 绘制的界面去当安装包的界面,那估计安装包行业会卷起来

以下是所有的代码

using D3D = Vortice.Direct3D;
using D3D11 = Vortice.Direct3D11;
using DXGI = Vortice.DXGI;
using D2D = Vortice.Direct2D1;using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using static Windows.Win32.PInvoke;
using static Windows.Win32.UI.WindowsAndMessaging.PEEK_MESSAGE_REMOVE_TYPE;
using static Windows.Win32.UI.WindowsAndMessaging.WNDCLASS_STYLES;
using static Windows.Win32.UI.WindowsAndMessaging.WINDOW_STYLE;
using static Windows.Win32.UI.WindowsAndMessaging.WINDOW_EX_STYLE;
using static Windows.Win32.UI.WindowsAndMessaging.SYSTEM_METRICS_INDEX;
using static Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD;
using Vortice.DCommon;
using Vortice.Mathematics;
using AlphaMode = Vortice.DXGI.AlphaMode;
using System.Diagnostics;unsafe
{SizeI clientSize = new SizeI(1000, 1000);// 窗口标题var title = "lindexi D2D AOT";var windowClassName = title;WINDOW_STYLE style = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_CLIPSIBLINGS | WS_BORDER | WS_DLGFRAME | WS_THICKFRAME | WS_GROUP | WS_TABSTOP | WS_SIZEBOX;var rect = new RECT{right = clientSize.Width,bottom = clientSize.Height};AdjustWindowRectEx(&rect, style, false, WS_EX_APPWINDOW);int x = 0;int y = 0;int windowWidth = rect.right - rect.left;int windowHeight = rect.bottom - rect.top;// 随便,放在屏幕中间好了。多个显示器?忽略int screenWidth = GetSystemMetrics(SM_CXSCREEN);int screenHeight = GetSystemMetrics(SM_CYSCREEN);x = (screenWidth - windowWidth) / 2;y = (screenHeight - windowHeight) / 2;var hInstance = GetModuleHandle((string) null);fixed (char* lpszClassName = windowClassName){PCWSTR szCursorName = new((char*) IDC_ARROW);var wndClassEx = new WNDCLASSEXW{cbSize = (uint) Unsafe.SizeOf<WNDCLASSEXW>(),style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,// 核心逻辑,设置消息循环lpfnWndProc = new WNDPROC(WndProc),hInstance = (HINSTANCE) hInstance.DangerousGetHandle(),hCursor = LoadCursor((HINSTANCE) IntPtr.Zero, szCursorName),hbrBackground = (Windows.Win32.Graphics.Gdi.HBRUSH) IntPtr.Zero,hIcon = (HICON) IntPtr.Zero,lpszClassName = lpszClassName};ushort atom = RegisterClassEx(wndClassEx);if (atom == 0){throw new InvalidOperationException($"Failed to register window class. Error: {Marshal.GetLastWin32Error()}");}}// 创建窗口var hWnd = CreateWindowEx(WS_EX_APPWINDOW,windowClassName,title,style,x,y,windowWidth,windowHeight,hWndParent: default,hMenu: default,hInstance: default,lpParam: null);// 创建完成,那就显示ShowWindow(hWnd, SW_NORMAL);CreateD2D();// 开个消息循环等待Windows.Win32.UI.WindowsAndMessaging.MSG msg;while (true){if (GetMessage(out msg, hWnd, 0, 0) != false){_ = TranslateMessage(&msg);_ = DispatchMessage(&msg);if (msg.message is WM_QUIT or WM_CLOSE or 0){return;}}}void CreateD2D(){RECT windowRect;GetClientRect(hWnd, &windowRect);clientSize = new SizeI(windowRect.right - windowRect.left, windowRect.bottom - windowRect.top);// 开始创建工厂创建 D3D 的逻辑var dxgiFactory2 = DXGI.DXGI.CreateDXGIFactory1<DXGI.IDXGIFactory2>();var hardwareAdapter = GetHardwareAdapter(dxgiFactory2)// 这里 ToList 只是想列出所有的 IDXGIAdapter1 方便调试而已。在实际代码里,大部分都是获取第一个.ToList().FirstOrDefault();if (hardwareAdapter == null){throw new InvalidOperationException("Cannot detect D3D11 adapter");}// 功能等级// [C# 从零开始写 SharpDx 应用 聊聊功能等级](https://blog.lindexi.com/post/C-%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99-SharpDx-%E5%BA%94%E7%94%A8-%E8%81%8A%E8%81%8A%E5%8A%9F%E8%83%BD%E7%AD%89%E7%BA%A7.html)D3D.FeatureLevel[] featureLevels = new[]{D3D.FeatureLevel.Level_11_1,D3D.FeatureLevel.Level_11_0,D3D.FeatureLevel.Level_10_1,D3D.FeatureLevel.Level_10_0,D3D.FeatureLevel.Level_9_3,D3D.FeatureLevel.Level_9_2,D3D.FeatureLevel.Level_9_1,};DXGI.IDXGIAdapter1 adapter = hardwareAdapter;D3D11.DeviceCreationFlags creationFlags = D3D11.DeviceCreationFlags.BgraSupport;var result = D3D11.D3D11.D3D11CreateDevice(adapter,D3D.DriverType.Unknown,creationFlags,featureLevels,out D3D11.ID3D11Device d3D11Device, out D3D.FeatureLevel featureLevel,out D3D11.ID3D11DeviceContext d3D11DeviceContext);if (result.Failure){// 如果失败了,那就不指定显卡,走 WARP 的方式// http://go.microsoft.com/fwlink/?LinkId=286690result = D3D11.D3D11.D3D11CreateDevice(IntPtr.Zero,D3D.DriverType.Warp,creationFlags,featureLevels,out d3D11Device, out featureLevel, out d3D11DeviceContext);// 如果失败,就不能继续result.CheckError();}// 大部分情况下,用的是 ID3D11Device1 和 ID3D11DeviceContext1 类型// 从 ID3D11Device 转换为 ID3D11Device1 类型var d3D11Device1 = d3D11Device.QueryInterface<D3D11.ID3D11Device1>();var d3D11DeviceContext1 = d3D11DeviceContext.QueryInterface<D3D11.ID3D11DeviceContext1>();// 转换完成,可以减少对 ID3D11Device1 的引用计数// 调用 Dispose 不会释放掉刚才申请的 D3D 资源,只是减少引用计数d3D11Device.Dispose();d3D11DeviceContext.Dispose();// 创建设备,接下来就是关联窗口和交换链DXGI.Format colorFormat = DXGI.Format.B8G8R8A8_UNorm;const int FrameCount = 2;DXGI.SwapChainDescription1 swapChainDescription = new(){Width = clientSize.Width,Height = clientSize.Height,Format = colorFormat,BufferCount = FrameCount,BufferUsage = DXGI.Usage.RenderTargetOutput,SampleDescription = DXGI.SampleDescription.Default,Scaling = DXGI.Scaling.Stretch,SwapEffect = DXGI.SwapEffect.FlipDiscard,AlphaMode = AlphaMode.Ignore,};// 设置是否全屏DXGI.SwapChainFullscreenDescription fullscreenDescription = new DXGI.SwapChainFullscreenDescription{Windowed = true};// 给创建出来的窗口挂上交换链DXGI.IDXGISwapChain1 swapChain = dxgiFactory2.CreateSwapChainForHwnd(d3D11Device1, hWnd, swapChainDescription, fullscreenDescription);// 不要被按下 alt+enter 进入全屏dxgiFactory2.MakeWindowAssociation(hWnd, DXGI.WindowAssociationFlags.IgnoreAltEnter);D3D11.ID3D11Texture2D backBufferTexture = swapChain.GetBuffer<D3D11.ID3D11Texture2D>(0);// 获取到 dxgi 的平面,这个平面就约等于窗口渲染内容DXGI.IDXGISurface dxgiSurface = backBufferTexture.QueryInterface<DXGI.IDXGISurface>();// 对接 D2D 需要创建工厂D2D.ID2D1Factory1 d2DFactory = D2D.D2D1.D2D1CreateFactory<D2D.ID2D1Factory1>();// 方法1://var renderTargetProperties = new D2D.RenderTargetProperties(PixelFormat.Premultiplied);在窗口的 dxgi 的平面上创建 D2D 的画布,如此即可让 D2D 绘制到窗口上//D2D.ID2D1RenderTarget d2D1RenderTarget =//    d2DFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, renderTargetProperties);//var renderTarget = d2D1RenderTarget;// 方法2:// 创建 D2D 设备,通过设置 ID2D1DeviceContext 的 Target 输出为 dxgiSurface 从而让 ID2D1DeviceContext 渲染内容渲染到窗口上// 如 https://learn.microsoft.com/en-us/windows/win32/direct2d/images/devicecontextdiagram.png 图// 获取 DXGI 设备,用来创建 D2D 设备DXGI.IDXGIDevice dxgiDevice = d3D11Device1.QueryInterface<DXGI.IDXGIDevice>();D2D.ID2D1Device d2dDevice = d2DFactory.CreateDevice(dxgiDevice);D2D.ID2D1DeviceContext d2dDeviceContext = d2dDevice.CreateDeviceContext();D2D.ID2D1Bitmap1 d2dBitmap = d2dDeviceContext.CreateBitmapFromDxgiSurface(dxgiSurface);d2dDeviceContext.Target = d2dBitmap;var renderTarget = d2dDeviceContext;// 开启后台渲染线程,无限刷新var stopwatch = Stopwatch.StartNew();var count = 0;Task.Factory.StartNew(() =>{var ellipseInfoList = new List<DrawingInfo>();for (int i = 0; i < 100; i++){// 随意创建颜色var color = new Color4((byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255));D2D.ID2D1SolidColorBrush brush = renderTarget.CreateSolidColorBrush(color);ellipseInfoList.Add(new DrawingInfo(new System.Numerics.Vector2(Random.Shared.Next(clientSize.Width), Random.Shared.Next(clientSize.Height)), new Size(Random.Shared.Next(10, 100)), brush));}while (true){// 开始绘制逻辑renderTarget.BeginDraw();// 清空画布renderTarget.Clear(new Color4(0xFF, 0xFF, 0xFF));// 在下面绘制漂亮的界面for (var i = 0; i < ellipseInfoList.Count; i++){var drawingInfo = ellipseInfoList[i];var vector2 = drawingInfo.Offset;vector2.X += Random.Shared.Next(200) - 100;vector2.Y += Random.Shared.Next(200) - 100;while (vector2.X < 100 || vector2.X > clientSize.Width - 100){vector2.X = Random.Shared.Next(clientSize.Width);}while (vector2.Y < 100 || vector2.Y > clientSize.Height - 100){vector2.Y = Random.Shared.Next(clientSize.Height);}ellipseInfoList[i] = drawingInfo with { Offset = vector2 };renderTarget.FillEllipse(new D2D.Ellipse(vector2, drawingInfo.Size.Width, drawingInfo.Size.Height), drawingInfo.Brush);}renderTarget.EndDraw();swapChain.Present(1, DXGI.PresentFlags.None);// 等待刷新d3D11DeviceContext1.Flush();// 统计刷新率count++;if (stopwatch.Elapsed >= TimeSpan.FromSeconds(1)){Console.WriteLine($"FPS: {count / stopwatch.Elapsed.TotalSeconds}");stopwatch.Restart();count = 0;}}}, TaskCreationOptions.LongRunning);}
}static IEnumerable<DXGI.IDXGIAdapter1> GetHardwareAdapter(DXGI.IDXGIFactory2 factory)
{DXGI.IDXGIFactory6? factory6 = factory.QueryInterfaceOrNull<DXGI.IDXGIFactory6>();if (factory6 != null){// 先告诉系统,要高性能的显卡for (int adapterIndex = 0;factory6.EnumAdapterByGpuPreference(adapterIndex, DXGI.GpuPreference.HighPerformance,out DXGI.IDXGIAdapter1? adapter).Success;adapterIndex++){if (adapter == null){continue;}DXGI.AdapterDescription1 desc = adapter.Description1;if ((desc.Flags & DXGI.AdapterFlags.Software) != DXGI.AdapterFlags.None){// Don't select the Basic Render Driver adapter.adapter.Dispose();continue;}//factory6.Dispose();Console.WriteLine($"枚举到 {adapter.Description1.Description} 显卡");yield return adapter;}factory6.Dispose();}// 如果枚举不到,那系统返回啥都可以for (int adapterIndex = 0;factory.EnumAdapters1(adapterIndex, out DXGI.IDXGIAdapter1? adapter).Success;adapterIndex++){DXGI.AdapterDescription1 desc = adapter.Description1;if ((desc.Flags & DXGI.AdapterFlags.Software) != DXGI.AdapterFlags.None){// Don't select the Basic Render Driver adapter.adapter.Dispose();continue;}Console.WriteLine($"枚举到 {adapter.Description1.Description} 显卡");yield return adapter;}
}static LRESULT WndProc(HWND hWnd, uint message, WPARAM wParam, LPARAM lParam)
{return DefWindowProc(hWnd, message, wParam, lParam);
}readonly record struct DrawingInfo(System.Numerics.Vector2 Offset, Size Size, D2D.ID2D1SolidColorBrush Brush);

本文以上代码放在github 和 gitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 66f9fe05baba8ad30495069aebd447b160484215

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 66f9fe05baba8ad30495069aebd447b160484215

获取代码之后,进入 CedageawhakairnerewhalNaibiferenagifee 文件夹

更多关于 DirectX 和 D2D 相关技术请参阅我的 博客导航

交流 Vortice 技术,欢迎加群: 622808968

http://www.ds6.com.cn/news/54106.html

相关文章:

  • 宁波公司做网站贵阳seo网站管理
  • 受欢迎的句容网站建设寻找客户资源的网站
  • 旅游网站首页图片全球搜怎么样
  • 门户网站开展集约化建设的情况目前疫情最新情况
  • 能够做外贸的网站有哪些永久免费客服系统软件
  • 琼海网站建设公司网站上做推广
  • 免费建站软件开发工程师
  • 网站建设开发教程劳动局免费培训电工
  • 校园网站建设规划书怎么样做网站推广
  • 免费可信网站认证免费ip地址代理
  • 淄博桓台学校网站建设哪家好2022最好的百度seo
  • 广西网站建设公司网站维护主要做什么
  • 注册公司后才可以做独立网站吗有效获客的六大渠道
  • 视频网站如何做seoseo行业网
  • 品牌网站制作产品推广策划方案
  • 最好科技上海网站建设广告策划公司
  • 兰州市委网站公司网站建设费
  • 网站开发加盟商怎么做青岛seo排名扣费
  • 网站开发工具链接服务器网络营销方案3000字
  • 邢台度网网站建设友情链接格式
  • 北京地产网站建设营销心得体会感悟300字
  • 沈阳网站开发久宁波建站模板系统
  • 网站的百度地图怎么做查询网
  • 做网站推广有用吗郑志平爱站网创始人
  • 网站开发3687474企鹅长沙弧度seo
  • 减肥网站开发目的百度seo新算法
  • 做网站建设优化的公司南宁百度推广代理公司
  • 网站主题定位成人短期技能培训
  • 网站静态和动态那个好google移动服务应用优化
  • 快速网站建设费用百度广告投放价格