游戏上线测试,会有很多错误日志生成,也不可能一个个去点过来,因为错误信息是根据日期和账号来区分的,便于管理。所以就需要来处理下,其实很简单,就是把后台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; }参考: 在VS2015中用C++创建DLL并用C#调用且同时实现对DLL的调试 使用C#调用C++类库