吐槽:如果有Qt的开发经验,会发现其实在比较底层编程理念UE4和Qt极其相识.很多类名和用法甚至都是相同的.
Q.UE4执行Windows Shell脚本:
例如使用系统默认图片查看器打开某张图片:
#if PLATFORM_WINDOWS #include "Windows/MinWindows.h" #include <shellapi.h> #endif void UCommonBPLibrary::ExecuteCMD(const FString & cmd) { //FPlatformProcess const char *str = TCHAR_TO_ANSI(*cmd); #if PLATFORM_WINDOWS ShellExecuteA(nullptr,"open", str, NULL, NULL, SW_SHOW); #else UE_LOG(LogTemp, Log, TEXT("platform is not support")); #endif }
Q.创建线程类:
UE4文档没有特别介绍关于线程模块的文章,这里自己简单记录一下,备查.
目前来说UE4的线程模块还是比较简陋的,命名风格像C#和Qt的结合体-_,-
UE4的线程的有两种比较常用的方式:
1.异步编程便捷帮助函数(线程池的封装)
2.自定义线程类
有复杂线程逻辑、涉及线程通讯的,的用自定义类.简单的用便捷帮助函数即可
说明:
1.异步编程便捷帮助函数:
跟C#的异步编程Task类基本一致,一是便捷,二是防止重复开线程的性能消耗(Task是基于线程池的)
主要涉及的相关函数和类:
Async | wait |
AsyncThread | wait |
AsyncTask | wait |
AsyncPool | wait |
Note: .h在
#include "Async/Async.h"
例子(来自UE4源码):
Async
* Usage examples: * * // using global function * int TestFunc() * { * return 123; * } * * TUniqueFunction<int()> Task = TestFunc(); * auto Result = Async(EAsyncExecution::Thread, Task); * * // using lambda * TUniqueFunction<int()> Task = []() * { * return 123; * } * * auto Result = Async(EAsyncExecution::Thread, Task); * * * // using inline lambda * auto Result = Async(EAsyncExecution::Thread, []() { * return 123; * }
2.自定义线程类:
自定义线程类:一般UE4线程主要涉及FRunnableThread类和FRunnable类
FRunnableThread用于管理线程实例(如创建、暂停,销毁等)
FRunnable则是用于实现线程逻辑的具体实现(继承方式)
有三个虚函数和一个纯虚函数
Init() 初始化调用
Stop() //暂停时调用
Exit() 结束时调用
Run() //运行时调用
会按照线程状态依次执行.
用到线程的话,难免会涉及到互斥锁:
UE4的互斥锁是:FCriticalSection类。
FCriticalSection还有与之相关个FScopeLock类
FScopeLock主要是防止线程异常抛出异常死锁的.
最后说明一下,和Qt/C# 一样,只有Run函数体里面的代码和New的变量,属于子线程.而FRunnable类对象本身是属于创建他的线程的
这里列一个我用于IO本地图片的Thread栗子:
.h
class FThread_IO : public FRunnable { public: FThread_IO(); ~FThread_IO(); public: bool Init() override{ return true; } uint32 Run() override; void Stop() override{}
void Exit override();
TArray<FData_LoadTexture2DCommand> arr_waitLoadFile;
};
.cpp
FThread_IO::FThread_IO() { } FThread_IO::~FThread_IO() { this->Stop(); } uint32 FThread_IO::Run() { /** 进行本地图片的IO读取处理 线程 */ static int i= 1; static int iWidth, iHeight; static UIOHelper *helper = NewObject<UIOHelper>(); while (true) { FCriticalSection Thread_lock; if (arr_waitLoadFile.Num() > 0) { i++; Thread_lock.Lock(); bool bvaild = false; auto command = arr_waitLoadFile[0]; arr_waitLoadFile.RemoveAt(0); auto tue = helper->LoadTexture2D_FromFile(command.target_UIItem->ImageLocalFilePath, EJoyImageFormats::JPG, bvaild, iWidth, iHeight); command.target_UIItem->SetImage_texture(tue); GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT(" load finished : %d"), arr_waitLoadFile.Num())); Thread_lock.Unlock(); } else { FPlatformProcess::Sleep(0.03f); } } return 0; } void FThread_IO::Exit() { GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT(" thread finished")); }
创建线程实例:
FString newThreadName("MyThread");
FThread_IO *thread_ResReader = nullptr;
auto mgr = FRunnableThread::Create(thread_ResReader, newThreadName.GetCharArray().GetData(), 0, TPri_BelowNormal);
Q.创建进程,打开额外程序方法:
FPlatformProcess::CreateProc(TEXT("程序路径");
Q.UE4进程IPC通讯(NamedPipe方式):
------------------------------------------- 说明与吐槽 ----------------------------------------------------------
IPC几种常用有邮槽/Pipe管线/共享内存等方式
需要与外部的exe进程做双工数据通信,就需要使用到namedPipe命名管线...
note: (如果是俩个UE4游戏进程的通讯还可以考虑UE4 的IPC模块..我大概翻了下ipc模块的源码实现,是进程共享内存..是所有IPC方式里性能最高的,如果应用支持多开且有大量共享资源需要数据通信建议使用共享内存+Pipe)
我之前很麻瓜的用Windows API在UE4里实现了一遍pipe进程通讯,最后某天在源码里发现UE4已经根据平台(UE4也仅对Windows平台进行pipe封装)对pipe进行了封装=_=,发现真相的我,眼泪都掉下来了..记录一下避免有人跟我一样采坑
关于Windows下 Pipe详细介绍:请看msdn https://docs.microsoft.com/zh-cn/windows/win32/ipc/pipes
------------------------------------------- 割割割割 ----------------------------------------------------------
关于UE4对Pipe管道的封装全都在FPlatformNamedPipe类中
#include "PlatformNamedPipe.h"
例子(实际使用请开线程轮询...):
以与C++ 控制台为服务器的通讯例子:

#include <iostream> #include <windows.h> using namespace std; int main( int argc, const char** argv ) { wcout << "Creating an instance of a named pipe..." << endl; // Create a pipe to send data HANDLE pipe = CreateNamedPipe( L"\\.\pipe\my_pipe", // name of the pipe PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, // 1-way pipe -- send only PIPE_TYPE_BYTE | PIPE_WAIT, // send data as a byte stream 1, // only allow 1 instance of this pipe 0, // no outbound buffer 0, // no inbound buffer 0, // use default wait time NULL // use default security attributes ); if ( pipe == NULL || pipe == INVALID_HANDLE_VALUE ) { wcout << "Failed to create outbound pipe instance."; // look up error code here using GetLastError() system("pause"); return 1; } wcout << "Waiting for a client to connect to the pipe..." << endl; // This call blocks until a client process connects to the pipe BOOL result = ConnectNamedPipe(pipe, NULL); if (!result) { wcout << "Failed to make connection on named pipe." << endl; // look up error code here using GetLastError() CloseHandle(pipe); // close the pipe system("pause"); return 1; } wcout << "Sending data to pipe..." << endl; // This call blocks until a client process reads all the data const wchar_t* data = L"###linqing###"; DWORD numBytesWritten = 0; result = WriteFile( pipe, // handle to our outbound pipe data, // data to send wcslen(data) * sizeof(wchar_t), // length of data to send (bytes) &numBytesWritten, // will store actual amount of data sent NULL // not using overlapped IO ); if (result) { wcout << "Number of bytes sent: " << numBytesWritten << endl; } else { wcout << "Failed to send data." << endl; // look up error code here using GetLastError() } // Close the pipe (automatically disconnects client too) CloseHandle(pipe); wcout << "Done." << endl; return 0; }
UE4 Client端 接受连接并接受消息:
static FPlatformNamedPipe p; const int BufferSize = 128;
const FString& PipeName = TEXT("\\.\pipe\my_pipe");
bool bServer = false;
bool bAsync = true uint8 buffer[BufferSize] = {0}; //static bCreate if (!p.IsCreated()) { bool isSuccess = p.Create(PipeName, bServer, bAsync); if (!isSuccess) { return; } } while (true) { if (!p.IsReadyForRW()) { FPlatformProcess::Sleep(0.03f); continue; } bool read_result = UCoreBPLibrary::GetNamedPipe()->ReadBytes(BufferSize * sizeof(uint8), buffer); std::string cstr(reinterpret_cast<const char*>(buffer), 128 * sizeof(uint8)); FString temp_str = FString(cstr.c_str()); UE_LOG(LogTemp, Log, TEXT("----------------------- %s , ----------------------- %s"), buffer, *temp_str); break; }