一、意图

本篇文章用于记载运用Flutter编写 win 插件简略调用exe文件并在后台运转。

二、背景

继上篇文章:go打包aar,flutter调用aar 之后,有大佬提出怎么调用exe文件,网上调研一番后写下该笔记,用于记载flutter编写插件怎么与win打交道,怎么调用exe文件。

我手上的资源有一个http exe文件,运用flutter调用该exe并保持后台运转。

本人非C++开发成员,对C++一知半解,假如代码有不正确的当地,请指教。

参阅文章/项目:

flutter_barcode_sdk:生成二维码的flutter plugin项目,参阅其间的win插件代码,个人觉得写的挺不错的,开发插件很有参阅价值。

flutter-desktop-embedding:官方的flutter plugin项目,还挺不错。

三、流程

问题:

  1. flutter plugin调用windows中办法怎么传参:一般参数与Map参数怎么获取。

  2. 怎么调用exe文件而且后台保活。

问题一:flutter plugin调用windows中办法怎么传参:一般参数与Map参数怎么获取。

介绍plugin项目文件

先创立一个flutter plugin项目:

flutter create –template=plugin –platform=windows win_plugin

dart三文件

在lib目录下有三个文件:

kernel_plugin_platform_interface.dart —抽象类,需求与第三方渠道交互的办法都要再次写上

在下面增加三个办法:

// plugin项目自带办法  
  Future<String?> getPlatformVersion() {
    throw UnimplementedError('platformVersion() has not been implemented.');
  }
// 一般的加法办法
  Future<int?> sum(int num1, int num2) {
    throw UnimplementedError('platformVersion() has not been implemented.');
  }
// 传递一般参数,调用exe文件
  Future<String?> startKernel(String cmd, String args) {
    throw UnimplementedError('platformVersion() has not been implemented.');
  }
// 传递map参数,调用exe文件
  Future<String?> startKernelMap(Map<String, Object> param) {
    throw UnimplementedError('platformVersion() has not been implemented.');
  }

kernel_plugin.dart — kernel_plugin_platform_interface 的完成类,用于约好调用第三方渠道办法进口。

其间 getPlatformVersion,startKernel,startKernelMap,sum 字符串在第三方代码也必须如出一辙,相当于用于约好交互的key

  @override
  Future<String?> getPlatformVersion() async {
    final version =
        await methodChannel.invokeMethod<String>('getPlatformVersion');
    return version;
  }
  @override
  Future<int?> sum(int num1, int num2) async {
    final result = await methodChannel.invokeMethod<int>('sum', [num1, num2]);
    return result;
  }
  @override
  Future<String?> startKernel(String cmd, String args) async {
    final result =
        await methodChannel.invokeMethod<String>('startKernel', [cmd, args]);
    return result;
  }
  @override
  Future<String?> startKernelMap(Map<String, Object> param) async {
    final result =
        await methodChannel.invokeMethod<String>('startKernelMap', param);
    return result;
  }

kernel_plugin_method_channel.dart — 用于提供给加载该插件的项目调用的办法,咱们调用插件就是调用该类中的办法。

class KernelPlugin {
  Future<String?> getPlatformVersion() {
    return KernelPluginPlatform.instance.getPlatformVersion();
  }
  Future<int?> sum(int num1, int num2) {
    return KernelPluginPlatform.instance.sum(num1, num2);
  }
  Future<String?> startKernel(String cmd, String args) {
    return KernelPluginPlatform.instance.startKernel(cmd, args);
  }
  Future<String?> startKernelMap(Map<String, Object> param) async {
    return KernelPluginPlatform.instance.startKernelMap(param);
  }
}

win中文件

Flutter编写win plugin调用第三方exe后台运行

咱们需求重视的只有 kernel_plugin.cpp​文件,由于该文件是用于完成咱们与dart交互的文件

咱们首要重视的办法就这一个HandleMethodCall,该办法就是用于咱们完成dart办法的进口,该文件用vscode翻开后会报错,别理它,后续阐明怎么正确修正该c++项目。

        void KernelPlugin::HandleMethodCall(
		const flutter::MethodCall<flutter::EncodableValue>& method_call,
		std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
	{
		if (method_call.method_name().compare("getPlatformVersion") == 0)
		{
			std::ostringstream version_stream;
			version_stream << "Windows ";
			if (IsWindows10OrGreater())
			{
				version_stream << "10+";
			}
			else if (IsWindows8OrGreater())
			{
				version_stream << "8";
			}
			else if (IsWindows7OrGreater())
			{
				version_stream << "7";
			}
			result->Success(flutter::EncodableValue(version_stream.str()));
		}
		else
		{
			result->NotImplemented();
		}
	}

一般参数与Map参数怎么获取。

重要:创立项目后,先进入example直接build一遍项目,将example/build/windows 文件夹创立出来,意图就是创立出 .sln 文件,然后用 visualstudio2022 翻开该文件,这是该c++项意图正确翻开方式。

Flutter编写win plugin调用第三方exe后台运行

用vs2022翻开该文件,现在修正cpp文件就不会报错,而且能够正常进行代码提示与修正。

Flutter编写win plugin调用第三方exe后台运行

一般参数获取

完成dart中的该办法:

  @override
  Future<int?> sum(int num1, int num2) async {
    final result = await methodChannel.invokeMethod<int>('sum', [num1, num2]);
    return result;
  }

其间约好的 ‘sum’ 字符串一定不能弄错,其间的过程已在代码中标注,假如您与我相同对c++一知半解的话,可照本宣科即可。

        else if (method_call.method_name().compare("sum") == 0)
		{
                        // 获取参数数组
			const auto* arguments = std::get_if<flutter::EncodableList>(method_call.arguments());
			if (!arguments)
			{
				result->Error("no param");
				return;
			}
                        // 根据索引获取对应的参数
			auto num1a = arguments->at(0);
			auto num2a = arguments->at(1);
                        // 将变量转化为对应的类型
			int num1 = get<int>(num1a);
			int num2 = get<int>(num2a);
                        // 逻辑计算相加,并回来
			int num3 = num1 + num2;
			result->Success(flutter::EncodableValue(num3));
		}

获取map参数

咱们将完成该办法,该办法传递的是一个map参数。

  @override
  Future<String?> startKernelMap(Map<String, Object> param) async {
    final result =
        await methodChannel.invokeMethod<String>('startKernelMap', param);
    return result;
  }

其间约好 ‘startKernelMap’ 别写错,其间的过程已在代码中标注,假如您与我相同对c++一知半解的话,可照本宣科即可。

		else if (method_call.method_name().compare("startKernelMap") == 0) {
                        // 获取对应的map参数
			auto* arguments = get_if<flutter::EncodableMap>(method_call.arguments());
			if (!arguments)
			{
				cout << "error err" << endl;
				cout << arguments << endl;
				result->Error("arguments error");
				return;
			}
                        // 在map中根据对应的key获取对应的指针目标,运用的话 *path,*args 即可获取对应的值
			auto* path = std::get_if<string>(&(arguments->find(flutter::EncodableValue("cmd"))->second));
			auto* args = std::get_if<string>(&(arguments->find(flutter::EncodableValue("args"))->second));
			cout << *path << endl;
			cout << *args << endl;
                        // 此处是创立线程调用exe,暂时甭管,后边阐明
			thread t1(startMyKernel, *path, *args);
			t1.detach();
			result->Success(flutter::EncodableValue("startKernelMap"));
		}

问题二:怎么调用exe文件而且后台保活

查询了下材料,暂时选择了两种调用方式

简略的就用 system(“C:/kernel.exe”),该办法是调用cmd来履行kernel.exe办法,缺点是打包后会有一个cmd窗口弹出来,试用以下两种参数想让窗口在后台运转不弹出,但是不成功,不清楚为什么。

system("start /b C:/kernel.exe") // 没报错,但是没有完成cmd窗口躲藏
system("hiden C:/kernel.exe") // 报错没有 hiden 指令,我用的是win10,不清楚为什么没有该指令

复杂可控的可运用 CreateProcess() ,该办法用于创立一个进程,通过设置一些参数来进行办理与控制该进程,而且能够做到躲藏cmd运转后台窗口。

	    void startMyKernel(string exePath, string args) {
		// /K 参数用于保持后台运转
		std::string cmd = "/K " + exePath + " " + args;
		// 窗口
		STARTUPINFO si = { sizeof(si) };
		PROCESS_INFORMATION pi;
		ZeroMemory(&si, sizeof(si));
		si.cb = sizeof(si);
		ZeroMemory(&pi, sizeof(pi));
		si.dwFlags = STARTF_USESHOWWINDOW;
		si.wShowWindow = SW_HIDE;
		// 将string转为wchar_t*
		int len = MultiByteToWideChar(CP_UTF8, 0, cmd.c_str(), -1, NULL, 0);
		wchar_t* wstr = new wchar_t[len];
		MultiByteToWideChar(CP_UTF8, 0, cmd.c_str(), -1, wstr, len);
		std::wcout << wstr;
		// 可用下面指令直接替换wstr的位置
		// TEXT("D:\\project\\go\\event_shop_kernel\\output\\windows\\kernel.exe api --port=6905 --mode=test --dbPath=C:\\Users\\Administrator\\Documents\\event_shop\\databases\\todo_shop.db --logPath=C:\\Users\\Administrator\\Documents\\event_shop\\logs")
		// bResult用于判别创立进程是否成功
		BOOL bResult;
		bResult = CreateProcess(TEXT("C:\\Windows\\System32\\cmd.exe"), wstr, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
		// 检测是否成功发动,未发动则弹窗过错信息
		if (!bResult)
		{
			// CreateProcess办法呈现过错
			LPVOID lpMsgBuf;
			DWORD dw = GetLastError();
			cout << dw << endl;
			FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
				FORMAT_MESSAGE_FROM_SYSTEM |
				FORMAT_MESSAGE_IGNORE_INSERTS,
				NULL,
				dw,
				MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
				(LPTSTR)&lpMsgBuf,
				0, NULL);
			// 弹窗过错信息
			MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK | MB_ICONERROR);
			LocalFree(lpMsgBuf);
			CloseHandle(pi.hProcess);
			CloseHandle(pi.hThread);
		}
		// 开释内存
		delete[] wstr;
		wstr = NULL;
	}

上述是履行是的办法,假如您需求将exe文件打包进你的项目,需求增加如下装备:

把exe文件放到windows/bin中。

Flutter编写win plugin调用第三方exe后台运行

修正CMakeLists.txt文件最后几行,将/bin目录下的文件打包进项目

# List of absolute paths to libraries that should be bundled with the plugin.
# This list could contain prebuilt libraries, or libraries created by an
# external build triggered from this build file.
set(kernel_plugin_bundled_libraries
  "${PROJECT_SOURCE_DIR}/bin/"
  PARENT_SCOPE
)

打包后的成果:example\build\windows\runner\Release

Flutter编写win plugin调用第三方exe后台运行

kernel.exe会呈现在根目录中

运转后成果:

Flutter编写win plugin调用第三方exe后台运行

整体代码:

由于一些特别原因,暂时贴代码,假如您需求github地址,可留言下,我后边收拾贴上

kernel_plugin.cpp

c++的中心代码

#include "kernel_plugin.h"
// This must be included before many other Windows headers.
#include <windows.h>
// For getPlatformVersion; remove unless needed for your plugin implementation.
#include <VersionHelpers.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include <string>
#include <thread>
#include <iostream>
#include <memory>
#include <sstream>
using namespace std;
const char kMenuSetMethod[] = "startKernel";
const char kMenuSetMethodMap[] = "startKernelMap";
namespace kernel_plugin
{
	using flutter::EncodableMap;
	using flutter::EncodableValue;
	void startMyKernel(string cmd, string args);
	// static
	void KernelPlugin::RegisterWithRegistrar(
		flutter::PluginRegistrarWindows* registrar)
	{
		auto channel =
			std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
				registrar->messenger(), "kernel_plugin",
				&flutter::StandardMethodCodec::GetInstance());
		auto plugin = std::make_unique<KernelPlugin>();
		channel->SetMethodCallHandler(
			[plugin_pointer = plugin.get()](const auto& call, auto result)
			{
				plugin_pointer->HandleMethodCall(call, std::move(result));
			});
		registrar->AddPlugin(std::move(plugin));
	}
	KernelPlugin::KernelPlugin() {}
	KernelPlugin::~KernelPlugin() {}
	void KernelPlugin::HandleMethodCall(
		const flutter::MethodCall<flutter::EncodableValue>& method_call,
		std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
	{
		if (method_call.method_name().compare("getPlatformVersion") == 0)
		{
			std::ostringstream version_stream;
			version_stream << "Windows ";
			if (IsWindows10OrGreater())
			{
				version_stream << "10+";
			}
			else if (IsWindows8OrGreater())
			{
				version_stream << "8";
			}
			else if (IsWindows7OrGreater())
			{
				version_stream << "7";
			}
			result->Success(flutter::EncodableValue(version_stream.str()));
		}
		else if (method_call.method_name().compare("sum") == 0)
		{
			const auto* arguments = std::get_if<flutter::EncodableList>(method_call.arguments());
			if (!arguments)
			{
				result->Error("no param");
				return;
			}
			auto num1a = arguments->at(0);
			auto num2a = arguments->at(1);
			int num1 = get<int>(num1a);
			int num2 = get<int>(num2a);
			int num3 = num1 + num2;
			result->Success(flutter::EncodableValue(num3));
		}
		else if (method_call.method_name().compare(kMenuSetMethod) == 0) {
			const auto* arguments = std::get_if<flutter::EncodableList>(method_call.arguments());
			if (!arguments)
			{
				result->Error("arguments error");
				return;
			}
			auto pathStr = arguments->at(0);
			string path = get<string>(pathStr);
			auto argsStr = arguments->at(1);
			string args = get<string>(argsStr);
			thread t(startMyKernel, path, args);
			t.detach();
			result->Success(flutter::EncodableValue("startKernel"));
		}
		else if (method_call.method_name().compare(kMenuSetMethodMap) == 0) {
			auto* arguments = get_if<flutter::EncodableMap>(method_call.arguments());
			if (!arguments)
			{
				cout << "error err" << endl;
				cout << arguments << endl;
				result->Error("arguments error");
				return;
			}
			auto* path = std::get_if<string>(&(arguments->find(flutter::EncodableValue("cmd"))->second));
			auto* args = std::get_if<string>(&(arguments->find(flutter::EncodableValue("args"))->second));
			cout << *path << endl;
			cout << *args << endl;
			thread t1(startMyKernel, *path, *args);
			t1.detach();
			result->Success(flutter::EncodableValue("startKernelMap"));
		}
		else
		{
			result->NotImplemented();
		}
	}
	void startMyKernel(string exePath, string args) {
		// /K 参数用于保持后台运转
		std::string cmd = "/K " + exePath + " " + args;
		// 窗口
		STARTUPINFO si = { sizeof(si) };
		PROCESS_INFORMATION pi;
		ZeroMemory(&si, sizeof(si));
		si.cb = sizeof(si);
		ZeroMemory(&pi, sizeof(pi));
		si.dwFlags = STARTF_USESHOWWINDOW;
		si.wShowWindow = SW_HIDE;
		// 将string转为wchar_t*
		int len = MultiByteToWideChar(CP_UTF8, 0, cmd.c_str(), -1, NULL, 0);
		wchar_t* wstr = new wchar_t[len];
		MultiByteToWideChar(CP_UTF8, 0, cmd.c_str(), -1, wstr, len);
		std::wcout << wstr;
		// 可用下面指令直接替换wstr的位置
		// TEXT("D:\\project\\go\\event_shop_kernel\\output\\windows\\kernel.exe api --port=6905 --mode=test --dbPath=C:\\Users\\Administrator\\Documents\\event_shop\\databases\\todo_shop.db --logPath=C:\\Users\\Administrator\\Documents\\event_shop\\logs")
		// bResult用于判别创立进程是否成功
		BOOL bResult;
		bResult = CreateProcess(TEXT("C:\\Windows\\System32\\cmd.exe"), wstr, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
		// 检测是否成功发动,未发动则弹窗过错信息
		if (!bResult)
		{
			// CreateProcess办法呈现过错
			LPVOID lpMsgBuf;
			DWORD dw = GetLastError();
			cout << dw << endl;
			FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
				FORMAT_MESSAGE_FROM_SYSTEM |
				FORMAT_MESSAGE_IGNORE_INSERTS,
				NULL,
				dw,
				MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
				(LPTSTR)&lpMsgBuf,
				0, NULL);
			// 弹窗过错信息
			MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK | MB_ICONERROR);
			LocalFree(lpMsgBuf);
			CloseHandle(pi.hProcess);
			CloseHandle(pi.hThread);
		}
		// 开释内存
		delete[] wstr;
		wstr = NULL;
	}
} // namespace kernel_plugin

main.dart

flutter中调用插件的中心代码

Future<Dir> initPath() async {
  var di = await getApplicationDocumentsDirectory();
  String dbPath = path.join(di.path, "event_shop", "databases");
  String logPath = path.join(di.path, "event_shop", "logs");
  var dbDir = Directory(dbPath);
  var logDir = Directory(logPath);
  dbDir.createSync(recursive: true);
  logDir.createSync(recursive: true);
  dbPath = path.join(dbPath, "xxxx.db");
  Dir dir = Dir(dbPath: dbPath, logPath: logPath);
  return dir;
}
void startKernel(RootIsolateToken rootIsolateToken) async {
  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
  final kernelPlugin = KernelPlugin();
  var dir = await initPath();
  String args =
      "api --port=6905 --mode=test --dbPath=${dir.dbPath} --logPath=${dir.logPath}";
  String kernelPath =
      "D:\\project\\flutter\\kernel_plugin\\example\\build\\windows\\runner\\Release\\kernel.exe";
  await kernelPlugin.startKernel(kernelPath, args);
}
void startKernelMap(RootIsolateToken rootIsolateToken) async {
  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
  final kernelPlugin = KernelPlugin();
  var dir = await initPath();
  String args =
      "api --port=6905 --mode=test --dbPath=${dir.dbPath} --logPath=${dir.logPath}";
  // String kernelPath =
  //     "D:\\project\\go\\event_shop_kernel\\output\\windows\\kernel.exe";
  String kernelPath =
      "D:\\project\\flutter\\kernel_plugin\\example\\build\\windows\\runner\\Release\\kernel.exe";
  Map<String, Object> param = {"cmd": kernelPath, "args": args};
  await kernelPlugin.startKernelMap(param);
}

四、结论

调用exe并不难,感觉有些困难的是获取参数那块,毕竟对c++不熟悉。

plugin的开发材料找起来比较麻烦,没有比较细致的材料,更多的还是看github中他人的项目怎么写的。