游戏上线测试,会有很多错误日志生成,也不可能一个个去点过来,因为错误信息是根据日期和账号来区分的,便于管理。所以就需要来处理下,其实很简单,就是把后台PHP存储的txt格式的报错信息写入到一个CSV文件中。
其实这功能我之前就有写过,那时候是在Unity上写了个工具,所以用的是C#,最近为了锻炼一下自己的C++,就写了份C++的,写是写出来了,但是各种不好用(主要是没有去重和分割,C++的字符串处理真TM难),就只好换回C#来。
为了不放弃C++代码,把C++的一部分代码写成了DLL,还是遇到各种问题,到最后还是放弃了使用DLL,因为BROWSEINFO这个选择文件夹的结构体会在DLL中一直卡死,没有报错和异常。所以就用了网上搜到的一个方法来实现选择文件夹功能。
虽然没有用上DLL,但是也学习到了不少东西,也明白了extern "C" __declspec(dllexport)的意思,这玩意经常看到但不知道什么意思,现在知道是为了处理C++符号修饰别名(mangled name)。
C#调用DLL提示"试图加载格式不正确的程序"处理方法是32位和64位的问题:
C#调试C++DLL的方法:
代码保留一份,避免下次再进行重复工作:
class Program
{
#region 选择文件夹功能
[DllImport("E:\\ZP\\Utility\\x64\\Release\\Utility.dll", EntryPoint = "BrowseDirectory")]
public static extern bool BrowseDirectory(IntPtr hwnd, StringBuilder title, ref StringBuilder result);
[DllImport("E:\\ZP\\Utility\\x64\\Release\\Utility.dll", EntryPoint = "Add")]
public static extern int Add(int v1, int v2);
[DllImport("shell32.dll")]
static extern IntPtr SHBrowseForFolder(ref BROWSEINFO lpbi);
[DllImport("shell32.dll")]
public static extern Int32 SHGetPathFromIDList(IntPtr pidl, StringBuilder pszPath);
public delegate int BrowseCallBackProc(IntPtr hwnd, int msg, IntPtr lp, IntPtr wp);
struct BROWSEINFO
{
public IntPtr hwndOwner;
public IntPtr pidlRoot;
public string pszDisplayName;
public string lpszTitle;
public uint ulFlags;
public BrowseCallBackProc lpfn;
public IntPtr lParam;
public int iImage;
}
public static string SelectDirectoryFromCpp(StringBuilder title)
{
StringBuilder inputPath = new StringBuilder();
try
{
IntPtr hwnd = Process.GetCurrentProcess().MainWindowHandle;
if (BrowseDirectory(hwnd, title, ref inputPath))
{
Console.WriteLine("获取错误日志存放目录成功");
}
else
{
Console.WriteLine("获取错误日志存放目录失败");
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
return inputPath.ToString();
}
public static string SelectDirectory(StringBuilder title)
{
StringBuilder sb = new StringBuilder(256);
IntPtr pidl = IntPtr.Zero;
BROWSEINFO bi;
bi.hwndOwner = Process.GetCurrentProcess().MainWindowHandle; ;
bi.pidlRoot = IntPtr.Zero;
bi.pszDisplayName = null;
bi.lpszTitle = title.ToString();
bi.ulFlags = 0; // BIF_NEWDIALOGSTYLE | BIF_SHAREABLE;
bi.lpfn = null; // new BrowseCallBackProc(OnBrowseEvent);
bi.lParam = IntPtr.Zero;
bi.iImage = 0;
try
{
pidl = SHBrowseForFolder(ref bi);
if (0 == SHGetPathFromIDList(pidl, sb))
{
return null;
}
}
finally
{
// Caller is responsible for freeing this memory.
Marshal.FreeCoTaskMem(pidl);
}
return sb.ToString();
}
#endregion
/// <summary>
/// 错误日志路径列表
/// </summary>
private static List<string> errorFileList;
/// <summary>
/// 错误日志存放目录
/// </summary>
private static string inputPath;
/// <summary>
/// 错误日志输出目录
/// </summary>
private static string outputPath;
/// <summary>
/// 已经报过的错误
/// </summary>
private static List<string> existErrorList;
static void Main(string[] args)
{
bool result;
result = Initialize();
if (result)
{
ErrorTxtConverToCSV();
}
Console.ReadKey();
}
private static void ErrorTxtConverToCSV()
{
if (null == errorFileList || errorFileList.Count <= 0) return;
string outputFilePath = outputPath + "\\error.csv";
if (File.Exists(outputFilePath))
File.Delete(outputFilePath);
var file = File.Create(outputFilePath);
if(file != null)
{
file.Close();
file = null;
}
int size = errorFileList.Count;
int count = 0;
StreamReader reader = null;
StreamWriter writer = null;
writer = new StreamWriter(outputFilePath, true, Encoding.GetEncoding("gb2312"));//解决中文写入乱码问题
if (null == writer) return;
for (int i = 0; i < size; ++i)
{
++count;
Console.WriteLine("处理进度: " + count + "/" + size);
try
{
reader = new StreamReader(errorFileList[i]);
if (null == reader) continue;
var content = reader.ReadToEnd().Trim();
reader.Close();
var result = OperateString(content);
if (writer != null)
{
for (int j = 0; j < result.Length; ++j)
{
if (BIsNewError(result[j]))
{
writer.WriteLine(result[j]);
writer.Flush();
}
}
}
}
catch(Exception e)
{
Console.WriteLine("Exception: " + e.ToString());
}
finally
{
if(reader != null)
{
reader.Close();
reader = null;
}
}
}
if (writer != null)
{
writer.Close();
writer = null;
}
Console.WriteLine("转换完成!");
}
/// <summary>
/// 是否有重复错误
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
private static bool BIsNewError(string error)
{
bool bIsHaveSameError = false;
if (null == existErrorList)
existErrorList = new List<string>();
if (string.IsNullOrEmpty(error))
bIsHaveSameError = true;
else
{
for (int i = 0; i < existErrorList.Count; ++i)
{
if (error.Equals(existErrorList[i]))
{
bIsHaveSameError = true;
break;
}
}
}
if (!bIsHaveSameError)
existErrorList.Add(error);
return !bIsHaveSameError;
}
private static string[] OperateString(string content)
{
//处理CSV中写入的一些特殊符号
content = content.Replace("\r", ",");
content = content.Replace("\n", ",");
content = content.Replace(",", ",");
content = content.Replace("\t", ",");
content = content.Replace(" ", ",");
//使用正则表达式分割字符串,蛋疼的是不能使用(.*)来处理,如果用(.*),只会分割出最后的字符串
string[] result = Regex.Split(content, @"\[错误\(...................\)\]:");
var resultList = result.ToList<string>();
for (int i = resultList.Count - 1; i >= 0; --i)
{
if (string.IsNullOrEmpty(resultList[i]))
resultList.RemoveAt(i);
}
return resultList.ToArray<string>();
}
private static bool Initialize()
{
var v = Add(5, 10);
StringBuilder title = new StringBuilder("请选择错误日志存放目录");
//var inputPath = SelectDirectoryFromCpp(title);//调用C++ DLL
inputPath = SelectDirectory(title);
title = new StringBuilder("请选择错误日志输出目录");
outputPath = SelectDirectory(title);
if (string.IsNullOrEmpty(inputPath) || string.IsNullOrEmpty(outputPath))
{
Console.WriteLine("错误日志存放目录或者输出目录有误");
return false;
}
if (null == errorFileList)
errorFileList = new List<string>();
else
errorFileList.Clear();
FindFile(inputPath);
return true;
}
private static void FindFile(string inputPath)
{
if (!Directory.Exists(inputPath))
return;
DirectoryInfo dirInfo = new DirectoryInfo(inputPath);
var files = dirInfo.GetFileSystemInfos();
for (int i = 0; i < files.Length; ++i)
{
var fileName = files[i].Name;
if (fileName.Contains("."))//文件
{
if (files[i].Name.Contains(".txt"))
{
errorFileList.Add(files[i].FullName);
}
}
else//文件夹
{
if(!files[i].Name.Equals("PC"))//过滤UnityEditor开发版本的错误
FindFile(files[i].FullName);
}
}
}
}
C++DLL中的代码:
#include "stdafx.h"
#include <ShlObj.h>
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
DWORD curProcessID = *((DWORD*)lParam);
DWORD tempProcessID = 0;
GetWindowThreadProcessId(hwnd, &tempProcessID);
if (tempProcessID == curProcessID && nullptr == GetParent(hwnd))
{
*((HWND *)lParam) = hwnd;
return true;
}
return false;
}
HWND GetMainWindow()
{
DWORD curProcessID = GetCurrentProcessId();
if (!EnumWindows(EnumWindowsProc, (LPARAM)&curProcessID))
{
return (HWND)curProcessID;
}
return nullptr;
}
extern "C" __declspec(dllexport) bool BrowseDirectory(HWND hwnd, char* title, char** resultStr)
{
bool result;
BROWSEINFO bi;
LPITEMIDLIST pidl;
int length;
WCHAR* tempTitle;
WCHAR* tempResult;
char* tempResult2;
result = (nullptr == title || nullptr == resultStr);
if (result)
return false;
length = MultiByteToWideChar(CP_ACP, 0, title, -1, nullptr, 0);
tempTitle = new WCHAR[length + 1];
memset(tempTitle, 0, sizeof(tempTitle));
MultiByteToWideChar(CP_ACP, 0, title, -1, tempTitle, length);
memset(&bi, 0, sizeof(BROWSEINFO));
//HMODULE hModule = GetModuleHandle(nullptr);
//HWND hwnd = GetMainWindow();
bi.hwndOwner = hwnd;
bi.pidlRoot = nullptr;
bi.lpszTitle = tempTitle;
bi.ulFlags = BIF_BROWSEFORCOMPUTER | BIF_EDITBOX | BIF_NEWDIALOGSTYLE;
bi.lpfn = nullptr;
bi.lParam = 0;
bi.iImage = 0;
pidl = SHBrowseForFolder(&bi);
result = pidl != nullptr;
if (!result)
return false;
tempResult = new WCHAR[1024];
memset(tempResult, 0, sizeof(tempResult));
SHGetPathFromIDList(pidl, tempResult);
length = WideCharToMultiByte(CP_ACP, 0, tempResult, -1, nullptr, 0, nullptr, nullptr);
tempResult2 = new char[length + 1];
WideCharToMultiByte(CP_ACP, 0, tempResult, -1, tempResult2, length, nullptr, nullptr);
strcpy_s(*resultStr, strnlen_s(tempResult2, 1024), tempResult2);
return true;
}
extern "C" __declspec(dllexport) int Add(int v1, int v2)
{
return v1 + v2;
}
最后再留一份之前C++实现的,望以后能继续用C++修改出一份完美的。
#include <iostream>
#include <Windows.h>
#include <ShlObj.h>
#include <list>
#include <fstream>
#define MAX_PATH_LENGTH 1024
#define TXT_EXTENSION TEXT("txt")
TCHAR* inputPath = new TCHAR[MAX_PATH_LENGTH]; //错误日志存放目录
TCHAR* outputPath = new TCHAR[MAX_PATH_LENGTH]; //错误日志输出目录
std::list<TCHAR*> errorFileList; //错误日志文件路径
/* 初始化 */
bool Initialize();
/* 将txt内容写入csv中 */
void ErrorTxtConverToCSV();
/* 将UTF8格式转成ANSI格式 */
char* ConvertUTF8ToANSI(char* text);
/* 替换字符串 */
char* DeleteChar(char* text, const char* deleteChar);
/* 查找文件并存放到list中 */
void FindFile(const TCHAR* inputPath, std::list<TCHAR*> &fileList);
/* 判断文件名后缀是否匹配 */
bool IsFileHaveSameExtension(const TCHAR* fileName, const TCHAR* extension);
int main()
{
bool result;
result = Initialize();
if (result)
{
ErrorTxtConverToCSV();
}
getchar();
return 0;
}
bool Initialize()
{
BROWSEINFO bi;
LPITEMIDLIST pidl;
bool result;
inputPath[0] = TEXT('\0');
outputPath[0] = TEXT('\0');
memset(&bi, 0, sizeof(BROWSEINFO));
bi.ulFlags = BIF_BROWSEFORCOMPUTER | BIF_EDITBOX | BIF_NEWDIALOGSTYLE;
WCHAR msg[32];
wsprintf(msg, TEXT("%d"), bi.ulFlags);
MessageBox(nullptr, msg, TEXT("bi.ulFlags"), MB_OK);
bi.lpszTitle = TEXT("请选择错误日志存放目录");
pidl = SHBrowseForFolder(&bi);
result = pidl != nullptr;
if (!result)
return false;
SHGetPathFromIDList(pidl, inputPath);
pidl = nullptr;
bi.lpszTitle = TEXT("请选择错误日志输出目录");
pidl = SHBrowseForFolder(&bi);
result = pidl != nullptr;
if (!result)
return false;
SHGetPathFromIDList(pidl, outputPath);
pidl = nullptr;
if (TEXT('\0') == inputPath[0] || TEXT('\0') == outputPath[0])
{
MessageBox(nullptr, TEXT("错误日志存放目录或者输出目录有误"), TEXT("错误"), MB_OK | MB_ICONERROR);
return false;
}
errorFileList.clear();
FindFile(inputPath, errorFileList);
result = errorFileList.size() > 0;
return result;
}
void ErrorTxtConverToCSV()
{
TCHAR* outputFilePath = new TCHAR[MAX_PATH_LENGTH];
lstrcpy(outputFilePath, outputPath);
lstrcat(outputFilePath, TEXT("\\error.csv"));
std::ofstream writer(outputFilePath, std::ios::out);
if (!writer) return;
std::list<TCHAR*>::iterator listIter;
int count = 0;
int size = (int)errorFileList.size();
for (listIter = errorFileList.begin(); listIter != errorFileList.end(); ++listIter)
{
++count;
std::cout << "处理进度: " << count << "/" << size << std::endl;
std::ifstream reader((*listIter), std::ios::in);
if (!reader)
{
std::cout << "文件不能打开: " << (*listIter) << std::endl;
}
else
{
while (!reader.eof())
{
char ch1[50000];
char* temp;
reader.getline(ch1, 50000);
temp = ConvertUTF8ToANSI(ch1);
temp = DeleteChar(temp, ",");
temp = DeleteChar(temp, "\t");
temp = DeleteChar(temp, " ");
writer << temp;
}
writer << std::endl;
}
reader.close();
}
writer.close();
std::cout << "转换完成!" << std::endl;
}
char* ConvertUTF8ToANSI(char* text)
{
int wcsLen = MultiByteToWideChar(CP_UTF8, 0, text, (int)strlen(text), nullptr, 0);
wchar_t* wszString = new wchar_t[wcsLen + 1];
MultiByteToWideChar(CP_UTF8, 0, text, (int)strlen(text), wszString, wcsLen);
wszString[wcsLen] = '\0';
int ansiLen = WideCharToMultiByte(CP_ACP, 0, wszString, (int)wcslen(wszString), nullptr, 0, nullptr, nullptr);
char* szAnsi = new char[ansiLen + 1];
WideCharToMultiByte(CP_ACP, 0, wszString, (int)wcslen(wszString), szAnsi, ansiLen, nullptr, nullptr);
szAnsi[ansiLen] = '\0';
return szAnsi;
}
char* DeleteChar(char* text, const char* deleteChar)
{
char* st = text;
char* s1 = nullptr;
const char* s2 = nullptr;
while (*st && *deleteChar)
{
s1 = st;
s2 = deleteChar;
while (*s1 && *s2 && !(*s1 - *s2))
{
s1++;
s2++;
}
if (!*s2)
{
while (*st++ = *s1++);
st = text;
}
st++;
}
return text;
}
void FindFile(const TCHAR* inputPath, std::list<TCHAR*>& fileList)
{
TCHAR* path = new TCHAR[MAX_PATH_LENGTH];
WIN32_FIND_DATA findFileData;
HANDLE handle;
lstrcpy(path, inputPath);
lstrcat(path, TEXT("\\*"));
handle = FindFirstFile(path, &findFileData);
if (INVALID_HANDLE_VALUE == handle)
{
MessageBox(nullptr, path, TEXT("错误日志存放目录空目录"), MB_OK | MB_ICONERROR);
return;
}
while (true)
{
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (findFileData.cFileName[0] != TEXT('.'))
{
if (findFileData.cFileName[0] != TEXT('P') && findFileData.cFileName[0] != TEXT('C'))//过滤UnityEditor开发版本的错误
{
TCHAR* tempPath = new TCHAR[MAX_PATH_LENGTH];
lstrcpy(tempPath, inputPath);
lstrcat(tempPath, TEXT("\\"));
lstrcat(tempPath, (TCHAR*)(findFileData.cFileName));
FindFile(tempPath, fileList);
}
}
}
else
{
if (IsFileHaveSameExtension(findFileData.cFileName, TXT_EXTENSION))
{
TCHAR* tempPath = new TCHAR[MAX_PATH_LENGTH];
lstrcpy(tempPath, inputPath);
lstrcat(tempPath, TEXT("\\"));
lstrcat(tempPath, findFileData.cFileName);
fileList.push_back(tempPath);
}
}
if (!FindNextFile(handle, &findFileData))
break;
}
FindClose(handle);
}
bool IsFileHaveSameExtension(const TCHAR* fileName, const TCHAR* extension)
{
TCHAR* tempFile = new TCHAR[MAX_PATH_LENGTH];
TCHAR* tempExtension = new TCHAR[MAX_PATH_LENGTH];
int fileLength, extensionLength;
int difference;
lstrcpy(tempFile, fileName);
lstrcpy(tempExtension, extension);
fileLength = lstrlen(tempFile);
extensionLength = lstrlen(tempExtension);
difference = fileLength - extensionLength;
if (difference < 2 || fileName[difference - 1] != TEXT('.')) return false;
int count = 0;
for (int i = 0; i < extensionLength; ++i)
{
if (tempExtension[i] == fileName[difference + i])
++count;
else
break;
}
return count == extensionLength;
}