一、意图
本篇文章用于记载运用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项目,还挺不错。
三、流程
问题:
-
flutter plugin调用windows中办法怎么传参:一般参数与Map参数怎么获取。
-
怎么调用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中文件
咱们需求重视的只有 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++项意图正确翻开方式。
用vs2022翻开该文件,现在修正cpp文件就不会报错,而且能够正常进行代码提示与修正。
一般参数获取
完成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中。
修正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
kernel.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中他人的项目怎么写的。