diff --git a/src/main/Cpp/FridaNative/FridaNative.vcxproj b/src/main/Cpp/FridaNative/FridaNative.vcxproj new file mode 100644 index 0000000..fa20f3e --- /dev/null +++ b/src/main/Cpp/FridaNative/FridaNative.vcxproj @@ -0,0 +1,164 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {e7f36e99-a575-47f5-ba40-c4877b08c517} + FridaNative + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + C:\Users\Administrator\.jdks\corretto-20.0.2.1\include\win32\bridge;C:\Users\Administrator\.jdks\corretto-20.0.2.1\include\win32;C:\Users\Administrator\.jdks\corretto-20.0.2.1\include;C:\Users\Administrator\Desktop\WindowsHook资源\Frida资源包\include;$(IncludePath) + + + + Level3 + true + WIN32;_DEBUG;FRIDANATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;FRIDANATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;FRIDANATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;FRIDANATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + C:\Users\Administrator\Desktop\WindowsHook资源\Frida资源包\lib.x64;%(AdditionalLibraryDirectories) + frida-core.lib;winmm.lib;setupapi.lib;dbghelp.lib;%(AdditionalDependencies) + + + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/src/main/Cpp/FridaNative/FridaNative.vcxproj.filters b/src/main/Cpp/FridaNative/FridaNative.vcxproj.filters new file mode 100644 index 0000000..83e48fa --- /dev/null +++ b/src/main/Cpp/FridaNative/FridaNative.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/src/main/Cpp/FridaNative/FridaNative.vcxproj.user b/src/main/Cpp/FridaNative/FridaNative.vcxproj.user new file mode 100644 index 0000000..5df420f --- /dev/null +++ b/src/main/Cpp/FridaNative/FridaNative.vcxproj.user @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/src/main/Cpp/FridaNative/dllmain.cpp b/src/main/Cpp/FridaNative/dllmain.cpp new file mode 100644 index 0000000..daed8c8 --- /dev/null +++ b/src/main/Cpp/FridaNative/dllmain.cpp @@ -0,0 +1,19 @@ +// dllmain.cpp : 定义 DLL 应用程序的入口点。 +#include "pch.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/src/main/Cpp/FridaNative/framework.h b/src/main/Cpp/FridaNative/framework.h new file mode 100644 index 0000000..80cbbc9 --- /dev/null +++ b/src/main/Cpp/FridaNative/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/src/main/Cpp/FridaNative/org_tzd_frida_windows_FridaNative.cpp b/src/main/Cpp/FridaNative/org_tzd_frida_windows_FridaNative.cpp new file mode 100644 index 0000000..9786f1f --- /dev/null +++ b/src/main/Cpp/FridaNative/org_tzd_frida_windows_FridaNative.cpp @@ -0,0 +1,224 @@ +#include "pch.h" + +#include "org_tzd_frida_windows_FridaNative.h" + + +#include +#include +#include + +static void on_message(FridaScript* script, const gchar* message, GBytes* data, gpointer user_data); + +static gboolean stop(gpointer user_data); +static GMainLoop* loop = NULL; +FridaScript* script; +FridaSession* session; +JavaVM* jvm; + +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: injection + * Signature: (JLjava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_tzd_frida_windows_FridaNative_injection +(JNIEnv* env, jclass clazz, jlong pid, jstring jsCode) +{ + if (jsCode == NULL) { + std::cerr << "The injected code is a null pointer " << std::endl; + return JNI_FALSE; + } + if (env == NULL) + { + std::cerr << "JNI environmental issues " << std::endl; + return JNI_FALSE; + } + + env->GetJavaVM(&jvm); + + const char* functionName = env->GetStringUTFChars(jsCode, nullptr); + frida_init(); + + GMainContext* context = g_main_context_new(); + if (context == nullptr) { + std::cerr << "Failed to create a new GMainContext" << std::endl; + return JNI_FALSE; // + } + + loop = g_main_loop_new(NULL, TRUE); + + FridaDeviceManager* manager = frida_device_manager_new(); + GError* error = nullptr; + FridaDeviceList* devices = frida_device_manager_enumerate_devices_sync(manager, nullptr, &error); + + if (error != nullptr) { + std::cerr << "Failed to enumerate devices: " << error->message << std::endl; + g_error_free(error); + return JNI_FALSE; + } + FridaDevice* local_device = nullptr; + guint num_devices = frida_device_list_size(devices); + for (guint i = 0; i < num_devices; i++) { + FridaDevice* device = frida_device_list_get(devices, i); + if (frida_device_get_dtype(device) == FRIDA_DEVICE_TYPE_LOCAL) { + local_device = g_object_ref(device); + break; + } + } + if (local_device == nullptr) { + std::cerr << "Local device not found" << std::endl; + return JNI_FALSE; + } + session = frida_device_attach_sync(local_device, pid, nullptr, nullptr, &error); + if (error != nullptr) { + std::cerr << "Failed to attach to process: " << error->message << std::endl; + g_error_free(error); + return JNI_FALSE; + } + script = frida_session_create_script_sync(session, functionName, nullptr, nullptr, &error); + if (error != nullptr) { + jclass jsCodeErrorClass = env->FindClass("org/tzd/frida/windows/JsCodeError"); + if (jsCodeErrorClass == nullptr) { + std::cerr << "Failed to find JsCodeError class" << std::endl; + return JNI_FALSE; + } + jmethodID jsCodeErrorCtor = env->GetMethodID(jsCodeErrorClass, "", "(Ljava/lang/String;)V"); + if (jsCodeErrorCtor == nullptr) { + std::cerr << "Failed to find JsCodeError constructor" << std::endl; + return JNI_FALSE; + } + jstring errorMessage = env->NewStringUTF(error->message); + jobject jsCodeErrorObj = env->NewObject(jsCodeErrorClass, jsCodeErrorCtor, errorMessage); + env->ThrowNew(jsCodeErrorClass, error->message); + return JNI_FALSE; + } + g_signal_connect(script, "message", G_CALLBACK(on_message), NULL); + frida_script_load_sync(script, nullptr, &error); + if (error != nullptr) { + std::cerr << "Failed to load script: " << error->message << std::endl; + return JNI_FALSE; + } + env->ReleaseStringUTFChars(jsCode, functionName); + return JNI_TRUE; +} + +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: update + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_tzd_frida_windows_FridaNative_update +(JNIEnv*, jclass) +{ + g_main_loop_run(loop); + return JNI_TRUE; +} + +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: isRunning + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_tzd_frida_windows_FridaNative_isRunning +(JNIEnv*, jclass) +{ + return g_main_loop_is_running(loop); +} +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: release + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_tzd_frida_windows_FridaNative_release +(JNIEnv*, jclass) +{ + frida_unref(script); + frida_unref(session); + return JNI_TRUE; +} + +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: getStringMember + * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_tzd_frida_windows_FridaNative_getStringMember +(JNIEnv* env, jclass, jstring message, jstring member_name) +{ + const char* message_cstr = env->GetStringUTFChars(message, nullptr); + const char* member_cstr = env->GetStringUTFChars(member_name, nullptr); + + JsonParser* parser = json_parser_new(); + GError* error = nullptr; + if (!json_parser_load_from_data(parser, message_cstr, -1, &error)) { + std::cerr << "Failed to parse JSON: " << error->message << std::endl; + g_error_free(error); + env->ReleaseStringUTFChars(message, message_cstr); + env->ReleaseStringUTFChars(member_name, member_cstr); + return nullptr; + } + JsonObject* root = json_node_get_object(json_parser_get_root(parser)); + const char* value = json_object_get_string_member(root, member_cstr); + + env->ReleaseStringUTFChars(message, message_cstr); + env->ReleaseStringUTFChars(member_name, member_cstr); + + if (value == nullptr) { + g_object_unref(parser); + return env->NewStringUTF(""); + } + + jstring result = env->NewStringUTF(value); + g_object_unref(parser); + return result; +} + +void callJavaOnMessage(const gchar* message) { + JNIEnv* env = nullptr; + jint attachResult = jvm->AttachCurrentThread((void**)&env, nullptr); + if (attachResult != JNI_OK) { + std::cerr << "Error: Unable to attach current thread to JVM" << std::endl; + return; + } + const char* className = "org/tzd/frida/windows/Frida0"; + jclass cls = env->FindClass(className); + if (cls == nullptr) { + std::cerr << "Error: Unable to find class " << className << std::endl; + return; + } + jmethodID methodId = env->GetStaticMethodID(cls, "onMessage", "(Ljava/lang/String;)V"); + if (methodId == nullptr) { + std::cerr << "Error: Unable to find method onMessage" << std::endl; + return; + } + jstring javaMessage = env->NewStringUTF(message); + env->CallStaticVoidMethod(cls, methodId, javaMessage); + env->DeleteLocalRef(javaMessage); + env->DeleteLocalRef(cls); +} + +static void +on_message(FridaScript* script, + const gchar* message, + GBytes* data, + gpointer user_data) +{ + JsonParser* parser; + parser = json_parser_new(); + json_parser_load_from_data(parser, message, -1, NULL); + callJavaOnMessage(message); + g_object_unref(parser); +} + +static void +on_signal(int signo) +{ + g_idle_add(stop, NULL); +} + +static gboolean +stop(gpointer user_data) +{ + g_main_loop_quit(loop); + + return FALSE; +} \ No newline at end of file diff --git a/src/main/Cpp/FridaNative/org_tzd_frida_windows_FridaNative.h b/src/main/Cpp/FridaNative/org_tzd_frida_windows_FridaNative.h new file mode 100644 index 0000000..a539a2b --- /dev/null +++ b/src/main/Cpp/FridaNative/org_tzd_frida_windows_FridaNative.h @@ -0,0 +1,53 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_tzd_frida_windows_FridaNative */ + +#ifndef _Included_org_tzd_frida_windows_FridaNative +#define _Included_org_tzd_frida_windows_FridaNative +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: injection + * Signature: (JLjava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_tzd_frida_windows_FridaNative_injection + (JNIEnv *, jclass, jlong, jstring); + +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: update + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_tzd_frida_windows_FridaNative_update + (JNIEnv *, jclass); + +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: isRunning + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_tzd_frida_windows_FridaNative_isRunning + (JNIEnv *, jclass); + +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: release + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_tzd_frida_windows_FridaNative_release + (JNIEnv *, jclass); + +/* + * Class: org_tzd_frida_windows_FridaNative + * Method: getStringMember + * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_tzd_frida_windows_FridaNative_getStringMember + (JNIEnv *, jclass, jstring, jstring); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/main/Cpp/FridaNative/pch.cpp b/src/main/Cpp/FridaNative/pch.cpp new file mode 100644 index 0000000..b6fb8f4 --- /dev/null +++ b/src/main/Cpp/FridaNative/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: 与预编译标头对应的源文件 + +#include "pch.h" + +// 当使用预编译的头时,需要使用此源文件,编译才能成功。 diff --git a/src/main/Cpp/FridaNative/pch.h b/src/main/Cpp/FridaNative/pch.h new file mode 100644 index 0000000..9660927 --- /dev/null +++ b/src/main/Cpp/FridaNative/pch.h @@ -0,0 +1,13 @@ +// pch.h: 这是预编译标头文件。 +// 下方列出的文件仅编译一次,提高了将来生成的生成性能。 +// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 +// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。 +// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。 + +#ifndef PCH_H +#define PCH_H + +// 添加要在此处预编译的标头 +#include "framework.h" + +#endif //PCH_H diff --git a/src/main/Cpp/LM/LM.vcxproj b/src/main/Cpp/LM/LM.vcxproj new file mode 100644 index 0000000..f3d2bc1 --- /dev/null +++ b/src/main/Cpp/LM/LM.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {a3131b71-dd4e-41c6-927a-20b8b287fd6c} + LM + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + C:\Users\Administrator\Desktop\llama资源\include;C:\Users\Administrator\.jdks\corretto-20.0.2.1\include\win32;C:\Users\Administrator\.jdks\corretto-20.0.2.1\include;$(IncludePath) + + + + Level3 + true + WIN32;_DEBUG;LM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;LM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;LM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;LM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp17 + + + Windows + true + true + true + false + C:\Users\Administrator\Desktop\llama资源\lib;%(AdditionalLibraryDirectories) + llama.lib;%(AdditionalDependencies) + + + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/src/main/Cpp/LM/LM.vcxproj.filters b/src/main/Cpp/LM/LM.vcxproj.filters new file mode 100644 index 0000000..0bb15f7 --- /dev/null +++ b/src/main/Cpp/LM/LM.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/src/main/Cpp/LM/LM.vcxproj.user b/src/main/Cpp/LM/LM.vcxproj.user new file mode 100644 index 0000000..c3809e5 --- /dev/null +++ b/src/main/Cpp/LM/LM.vcxproj.user @@ -0,0 +1,9 @@ + + + + false + + + WindowsLocalDebugger + + \ No newline at end of file diff --git a/src/main/Cpp/LM/dllmain.cpp b/src/main/Cpp/LM/dllmain.cpp new file mode 100644 index 0000000..22af23d --- /dev/null +++ b/src/main/Cpp/LM/dllmain.cpp @@ -0,0 +1,21 @@ +// dllmain.cpp : 定义 DLL 应用程序的入口点。 +#include "pch.h" + +#include "llama.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/src/main/Cpp/LM/framework.h b/src/main/Cpp/LM/framework.h new file mode 100644 index 0000000..80cbbc9 --- /dev/null +++ b/src/main/Cpp/LM/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/src/main/Cpp/LM/org_tzd_lm_LM.cpp b/src/main/Cpp/LM/org_tzd_lm_LM.cpp new file mode 100644 index 0000000..0b529ab --- /dev/null +++ b/src/main/Cpp/LM/org_tzd_lm_LM.cpp @@ -0,0 +1,419 @@ +#include "pch.h" +#include "org_tzd_lm_LM.h" + +#include +#include + +#include "llama.h" +#include +#include +#include +#include +#include + +// صȫ +static jmethodID gMessageCallbackMethodID = nullptr; +static jmethodID gProgressCallbackMethodID = nullptr; +JNIEnv* env_; +jobject messageCallback_; +jobject progressCallback__; + +static bool isRun = true; + +bool ToCppBool(jboolean value) { + return value == JNI_TRUE; +} + +bool llamaProgressCallback(float progress, void* user_data) { + JNIEnv* env = (JNIEnv*)user_data; + jint j_progress = progress; + jboolean ret = env->CallBooleanMethod(progressCallback__, gProgressCallbackMethodID, j_progress); + return ToCppBool(ret); +} + +//-------------------------------------------------- +// ģͼغͷ +//-------------------------------------------------- + +JNIEXPORT jlong JNICALL Java_org_tzd_lm_LM_llamaLoadModelFromFile +(JNIEnv* env, jclass clazz, jstring pathModel, + jboolean vocab_only_jboolean, + jboolean use_mmap_jboolean, jboolean use_mlock_jboolean,jboolean check_tensors_jboolean, jobject progressCallback) { + const char* path = env->GetStringUTFChars(pathModel, nullptr); + if (!path) { + return 0; + } + progressCallback__ = progressCallback; + llama_model_params params = llama_model_default_params(); + if (progressCallback && !gProgressCallbackMethodID) { + jclass callbackClass = env->GetObjectClass(progressCallback); + if (!callbackClass) { + return 0; + } + gProgressCallbackMethodID = env->GetMethodID(callbackClass, "onModelLoad", "(F)Z"); + if (!gProgressCallbackMethodID) { + return 0; + } + params.progress_callback = llamaProgressCallback; + params.progress_callback_user_data = env; + } + + bool vocab_only = ToCppBool(vocab_only_jboolean); + bool use_mmap = ToCppBool(use_mmap_jboolean); + bool use_mlock = ToCppBool(use_mlock_jboolean); + bool check_tensors = ToCppBool(check_tensors_jboolean); + + params.vocab_only = static_cast(vocab_only); + params.use_mmap = static_cast(use_mmap); + params.use_mlock = static_cast(use_mlock); + params.check_tensors = static_cast(check_tensors); + + llama_model* model = llama_model_load_from_file(path, params); + env->ReleaseStringUTFChars(pathModel, path); + + if (!model) { + jclass exClass = env->FindClass("java/io/IOException"); + if (exClass) { + env->ThrowNew(exClass, "Failed to load model: check path and parameters"); + } + return 0; + } + + return reinterpret_cast(model); +} + +JNIEXPORT void JNICALL Java_org_tzd_lm_LM_llamaFreeModel +(JNIEnv* env, jclass clazz, jlong modelHandle) { + llama_model* model = reinterpret_cast(modelHandle); + llama_model_free(model); // ʹµ API +} + +//-------------------------------------------------- +// Ĵ +//-------------------------------------------------- +JNIEXPORT jlong JNICALL Java_org_tzd_lm_LM_createContext +(JNIEnv* env, jclass clazz, jlong modelHandle, jint nCtx, + jint nBatch, + jint nSeqMax, + jint nThreads, + jint nThreadsBatch, + jboolean logitsAll, + jboolean embeddings, + jboolean offloadKqv, + jboolean flashAttn, + jboolean noPerf +) { + llama_model* model = reinterpret_cast(modelHandle); + if (!model) { + jclass exClass = env->FindClass("java/lang/IllegalArgumentException"); + if (exClass) { + env->ThrowNew(exClass, "Invalid model handle"); + } + return 0; + } + + llama_context_params ctx_params = llama_context_default_params(); + + if (nCtx != 0) { + ctx_params.n_ctx = nCtx; + } + if (nBatch != 0) { + ctx_params.n_batch = nBatch; + } + if (nSeqMax != 0) { + ctx_params.n_seq_max = nSeqMax; + } + if (nThreads != 0) { + ctx_params.n_threads = nThreads; + } + if (nThreadsBatch != 0) { + ctx_params.n_threads_batch = nThreadsBatch; + } + + ctx_params.logits_all = static_cast(logitsAll); + ctx_params.embeddings = static_cast(embeddings); + ctx_params.offload_kqv = static_cast(offloadKqv); + ctx_params.flash_attn = static_cast(flashAttn); + ctx_params.no_perf = static_cast(noPerf); + + llama_context* ctx = llama_init_from_model(model, ctx_params); + if (!ctx) { + jclass exClass = env->FindClass("java/io/IOException"); + if (exClass) { + env->ThrowNew(exClass, "Failed to create context"); + } + return 0; + } + + return reinterpret_cast(ctx); +} + +JNIEXPORT void JNICALL Java_org_tzd_lm_LM_llamaFreeContext +(JNIEnv* env, jclass clazz, jlong ctxHandle) +{ + llama_context* ctx = reinterpret_cast(ctxHandle); + llama_kv_cache_clear(ctx); + llama_free(ctx); + isRun = false; +} + +static int tokenize_prompt( + const llama_vocab* vocab, + const std::string& prompt, + std::vector& prompt_tokens, + llama_context* context +) { + const bool is_first = llama_get_kv_cache_used_cells(context) == 0; + + const int n_prompt_tokens = -llama_tokenize(vocab, + prompt.c_str(), + prompt.size(), + NULL, + 0, + is_first, + true); + prompt_tokens.resize(n_prompt_tokens); + if (llama_tokenize(vocab, prompt.c_str(), prompt.size(), prompt_tokens.data(), prompt_tokens.size(), is_first, + true) < 0) { + printf("failed to tokenize the prompt\n"); + return -1; + } + return n_prompt_tokens; +} + +static int check_context_size( + const llama_context* ctx, + const llama_batch& batch +) { + const int n_ctx = llama_n_ctx(ctx); + const int n_ctx_used = llama_get_kv_cache_used_cells(ctx); + if (n_ctx_used + batch.n_tokens > n_ctx) { + printf("context size exceeded\n"); + return 1; + } + + return 0; +} + +static int convert_token_to_string(const llama_vocab* vocab, const llama_token token_id, std::string& piece) { + char buf[256]; + int n = llama_token_to_piece(vocab, token_id, buf, sizeof(buf), 0, true); + if (n < 0) { + printf("failed to convert token to piece\n"); + return 1; + } + + piece = std::string(buf, n); + return 0; +} +static void print_word_and_concatenate_to_response(const std::string& piece, std::string& response) { + jstring message = env_->NewStringUTF(piece.c_str()); + if (message) { + env_->CallVoidMethod(messageCallback_, gMessageCallbackMethodID, message); + env_->DeleteLocalRef(message); + } + fflush(stdout); + response += piece; +} + +static int apply_chat_template_with_error_handling(const bool append, std::string response, int& output_length) { + if (!append) + { + const int new_len = response.length(); + if (new_len < 0) { + printf("failed to apply the chat template\n"); + return -1; + } + + output_length = new_len; + } + return 0; +} + +std::vector tokens; + +static int generate( + llama_model* llama_data, + llama_context* context, + llama_sampler* smpl, + const std::string& prompt, + std::string& response +) { + //llama_kv_cache_clear(context); + + const llama_vocab* vocab = llama_model_get_vocab(llama_data); + isRun = true; + if (tokenize_prompt(vocab, prompt, tokens, context) < 0) { + return 1; + } + + llama_batch batch = llama_batch_get_one(tokens.data(), tokens.size()); + llama_token new_token_id; + while (true) { + check_context_size(context, batch); + if (llama_decode(context, batch)) { + printf("\nfailed to decode\n"); + return 1; + } + + new_token_id = llama_sampler_sample(smpl, context, -1); + if (llama_vocab_is_eog(vocab, new_token_id)) { + break; + } + + std::string piece; + if (convert_token_to_string(vocab, new_token_id, piece)) { + return 1; + } + print_word_and_concatenate_to_response(piece, response); + batch = llama_batch_get_one(&new_token_id, 1); + if (!isRun) { + return 0; + } + } + return 0; +} + +llama_sampler* initialize_sampler(float temperature, + float min_p, + float top_k, + float top_p, + float dist, + int32_t penalty_last_n, + float penalty_repeat, + float penalty_freq, + float penalty_present +) { + if (!dist) { + dist = LLAMA_DEFAULT_SEED; + } + llama_sampler_chain_params params = llama_sampler_chain_default_params(); + llama_sampler* sampler = llama_sampler_chain_init(params); + + // ˳ʾ˳򣬸 + llama_sampler_chain_add(sampler, llama_sampler_init_penalties(penalty_last_n, penalty_repeat, penalty_freq, penalty_present)); + llama_sampler_chain_add(sampler, llama_sampler_init_top_k(top_k)); + llama_sampler_chain_add(sampler, llama_sampler_init_top_p(top_p, 1)); + llama_sampler_chain_add(sampler, llama_sampler_init_temp(temperature)); + llama_sampler_chain_add(sampler, llama_sampler_init_min_p(min_p, 1)); + llama_sampler_chain_add(sampler, llama_sampler_init_dist(dist)); + + // ƳDZҪ̰ + // llama_sampler_chain_add(sampler, llama_sampler_init_greedy()); + + return sampler; +} + + +// UTF-16 jstring תΪ UTF-8 std::string +std::string jstringToUTF8(JNIEnv* env, jstring jstr) { + if (!jstr) return ""; + + const jchar* raw = env->GetStringChars(jstr, nullptr); + if (!raw) return ""; + + jsize len = env->GetStringLength(jstr); + + // UTF-16 תΪ UTF-8 + int utf8Size = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast(raw), len, nullptr, 0, nullptr, nullptr); + std::string utf8(utf8Size, 0); + WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast(raw), len, &utf8[0], utf8Size, nullptr, nullptr); + + env->ReleaseStringChars(jstr, raw); + return utf8; +} + +// UTF-8 std::string תΪ jstring +jstring utf8ToJstring(JNIEnv* env, const std::string& utf8) { + // UTF-8 תΪ UTF-16 + int utf16Size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0); + std::wstring utf16(utf16Size, 0); + MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &utf16[0], utf16Size); + + return env->NewString(reinterpret_cast(utf16.c_str()), utf16Size - 1); +} + +//-------------------------------------------------- +// ߼ʵ +//-------------------------------------------------- +JNIEXPORT jstring JNICALL Java_org_tzd_lm_LM_inference +(JNIEnv* env, jclass clazz, jlong modelHandle, jlong ctxHandle, jfloat temperature, jfloat minP, + jfloat topK, jfloat topP, jfloat dist, jint penaltyLastN, jfloat penaltyRepeat, jfloat penaltyFreq, + jfloat penaltyPresent, jstring prompt, jobject messageCallback) { + llama_context* ctx = reinterpret_cast(ctxHandle); + llama_model* model = reinterpret_cast(modelHandle); + env_ = env; + messageCallback_ = messageCallback; + + // ctx ǷЧ + if (!ctx) { + jclass exClass = env->FindClass("java/lang/IllegalArgumentException"); + if (exClass) env->ThrowNew(exClass, "Invalid context handle"); + return nullptr; + } + + + // ʼصID + if (!gMessageCallbackMethodID) { + jclass callbackClass = env->GetObjectClass(messageCallback); + if (!callbackClass) { + return nullptr; + } + gMessageCallbackMethodID = env->GetMethodID(callbackClass, "onMessage", "(Ljava/lang/String;)V"); + if (!gMessageCallbackMethodID) { + return nullptr; + } + } + + // Ӧַ + std::string response; + std::string prompt_(jstringToUTF8(env, prompt)); + // ʹóʼIJɽ + llama_sampler* sampler = initialize_sampler(temperature, minP, topK, + topP, dist, penaltyLastN, penaltyRepeat, + penaltyFreq, penaltyPresent); + + if (generate(model, ctx, sampler, prompt_, response) != 0) { + return nullptr; + } + + return utf8ToJstring(env, response); +} + +JNIEXPORT jboolean JNICALL Java_org_tzd_lm_LM_llamaStateLoadFile( + JNIEnv* env, jobject obj, jlong ctx, jstring pathSession, + jlongArray tokensOut, jint nTokenCapacity, jintArray nTokenCountOut +) { + const char* path = env->GetStringUTFChars(pathSession, NULL); + + jlong* tokens_out = env->GetLongArrayElements(tokensOut, NULL); + jint* n_token_count_out = env->GetIntArrayElements(nTokenCountOut, NULL); + + bool result = llama_state_load_file((struct llama_context*)ctx, path, + (llama_token*)tokens_out, + nTokenCapacity, + (size_t*)n_token_count_out); + + env->ReleaseStringUTFChars(pathSession, path); + env->ReleaseLongArrayElements(tokensOut, tokens_out, 0); + env->ReleaseIntArrayElements(nTokenCountOut, n_token_count_out, 0); + + return result ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL Java_org_tzd_lm_LM_llamaStateSaveFile( + JNIEnv* env, jobject obj, jlong ctx, jstring pathSession, + jlongArray tokens, jint nTokenCount +) { + const char* path = env->GetStringUTFChars(pathSession, NULL); + + jlong* tokens_array = env->GetLongArrayElements(tokens, NULL); + + bool result = llama_state_save_file((struct llama_context*)ctx, path, + (const llama_token*)tokens_array, + nTokenCount); + + env->ReleaseStringUTFChars(pathSession, path); + env->ReleaseLongArrayElements(tokens, tokens_array, 0); + return result ? JNI_TRUE : JNI_FALSE; +} \ No newline at end of file diff --git a/src/main/Cpp/LM/org_tzd_lm_LM.h b/src/main/Cpp/LM/org_tzd_lm_LM.h new file mode 100644 index 0000000..8408a06 --- /dev/null +++ b/src/main/Cpp/LM/org_tzd_lm_LM.h @@ -0,0 +1,67 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_tzd_lm_LM */ + +#ifndef _Included_org_tzd_lm_LM +#define _Included_org_tzd_lm_LM +#ifdef __cplusplus +extern "C" { +#endif + /* + * Class: org_tzd_lm_LM + * Method: llamaLoadModelFromFile + * Signature: (Ljava/lang/String;)J + */ + JNIEXPORT jlong JNICALL Java_org_tzd_lm_LM_llamaLoadModelFromFile + (JNIEnv*, jclass, jstring, jboolean , jboolean, jboolean, jboolean, jobject); + + /* + * Class: org_tzd_lm_LM + * Method: llamaFreeModel + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_org_tzd_lm_LM_llamaFreeModel + (JNIEnv*, jclass, jlong); + + /* + * Class: org_tzd_lm_LM + * Method: createContext + * Signature: (J)J + */ + JNIEXPORT jlong JNICALL Java_org_tzd_lm_LM_createContext + (JNIEnv* env, jclass clazz, jlong modelHandle, jint nCtx, + jint nBatch, + jint nSeqMax, + jint nThreads, + jint nThreadsBatch, + jboolean logitsAll, + jboolean embeddings, + jboolean offloadKqv, + jboolean flashAttn, + jboolean noPerf); + + JNIEXPORT void JNICALL Java_org_tzd_lm_LM_llamaFreeContext + (JNIEnv*, jclass, jlong); + + /* + * Class: org_tzd_lm_LM + * Method: inference + * Signature: (JLjava/lang/String;Lorg/tzd/lm/LM/MessageCallback;)Ljava/lang/String; + */ + JNIEXPORT jstring JNICALL Java_org_tzd_lm_LM_inference + (JNIEnv* env, jclass clazz, jlong modelHandle, jlong ctxHandle, jfloat temperature, jfloat minP, jfloat topK, jfloat topP, jfloat dist, jint penaltyLastN, jfloat penaltyRepeat, jfloat penaltyFreq, jfloat penaltyPresent, jstring prompt, jobject messageCallback); + + JNIEXPORT jboolean JNICALL Java_org_tzd_lm_LM_llamaStateSaveFile( + JNIEnv* env, jobject obj, jlong ctx, jstring pathSession, + jlongArray tokens, jint nTokenCount + ); + + JNIEXPORT jboolean JNICALL Java_org_tzd_lm_LM_llamaStateLoadFile( + JNIEnv* env, jobject obj, jlong ctx, jstring pathSession, + jlongArray tokensOut, jint nTokenCapacity, jintArray nTokenCountOut + ); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/main/Cpp/LM/pch.cpp b/src/main/Cpp/LM/pch.cpp new file mode 100644 index 0000000..b6fb8f4 --- /dev/null +++ b/src/main/Cpp/LM/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: 与预编译标头对应的源文件 + +#include "pch.h" + +// 当使用预编译的头时,需要使用此源文件,编译才能成功。 diff --git a/src/main/Cpp/LM/pch.h b/src/main/Cpp/LM/pch.h new file mode 100644 index 0000000..9660927 --- /dev/null +++ b/src/main/Cpp/LM/pch.h @@ -0,0 +1,13 @@ +// pch.h: 这是预编译标头文件。 +// 下方列出的文件仅编译一次,提高了将来生成的生成性能。 +// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 +// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。 +// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。 + +#ifndef PCH_H +#define PCH_H + +// 添加要在此处预编译的标头 +#include "framework.h" + +#endif //PCH_H diff --git a/src/main/Cpp/LM/tiktoken.h b/src/main/Cpp/LM/tiktoken.h new file mode 100644 index 0000000..8624b34 --- /dev/null +++ b/src/main/Cpp/LM/tiktoken.h @@ -0,0 +1,269 @@ +#pragma once + +#include +#include "unordered_dense.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tiktoken { + +static auto _byte_pair_merge( + const std::string &piece, + const ankerl::unordered_dense::map &ranks, + std::function func +) -> std::vector { + std::vector> parts; + parts.reserve(piece.size() + 1); + for (auto idx = 0U; idx < piece.size() + 1; ++idx) { + parts.emplace_back(idx, std::numeric_limits::max()); + } + + auto get_rank = [&piece, &ranks]( + const std::vector> &parts, + int start_idx, + int skip + ) -> std::optional { + if (start_idx + skip + 2 < parts.size()) { + auto s = parts[start_idx].first; + auto e = parts[start_idx + skip + 2].first; + auto key = piece.substr(s, e - s); + auto iter = ranks.find(key); + if (iter != ranks.end()) { + return iter->second; + } + } + return std::nullopt; + }; + + for (auto i = 0U; i < parts.size() - 2; ++i) { + auto rank = get_rank(parts, i, 0); + if (rank) { + assert(*rank != std::numeric_limits::max()); + parts[i].second = *rank; + } + } + + while (true) { + if (parts.size() == 1) break; + + auto min_rank = std::make_pair(std::numeric_limits::max(), 0); + for (auto i = 0U; i < parts.size() - 1; ++i) { + auto rank = parts[i].second; + if (rank < min_rank.first) { + min_rank = { rank, i }; + } + } + + if (min_rank.first != std::numeric_limits::max()) { + auto i = min_rank.second; + auto rank = get_rank(parts, i, 1); + if (rank) { + parts[i].second = *rank; + } else { + parts[i].second = std::numeric_limits::max(); + } + if (i > 0) { + auto rank = get_rank(parts, i - 1, 1); + if (rank) { + parts[i - 1].second = *rank; + } else { + parts[i - 1].second = std::numeric_limits::max(); + } + } + + parts.erase(parts.begin() + (i + 1)); + } else { + break; + } + } + std::vector out; + out.reserve(parts.size() - 1); + for (auto i = 0U; i < parts.size() - 1; ++i) { + out.push_back(func(parts[i].first, parts[i + 1].first)); + } + return out; +} + +static auto byte_pair_encode( + const std::string &piece, + const ankerl::unordered_dense::map &ranks +) -> std::vector { + if (piece.size() == 1) { + return {ranks.at(piece)}; + } + + auto func = [&piece, &ranks](int start, int stop) -> int { + std::string key = piece.substr(start, stop - start); + return ranks.at(key); + }; + + return _byte_pair_merge(piece, ranks, func); +} + +class tiktoken { + public: + tiktoken() = default; + tiktoken( + ankerl::unordered_dense::map encoder, + ankerl::unordered_dense::map special_encoder, + const std::string &pattern + ) { + regex_ = std::make_unique("(" + pattern + ")"); + + std::string special_pattern; + for (const auto &item : special_encoder) { + if (!special_pattern.empty()) { + special_pattern += "|"; + } + special_pattern += re2::RE2::QuoteMeta(item.first); + } + if (special_pattern.empty()) { + special_regex_ = nullptr; + } else { + special_regex_ = std::make_unique("(" + special_pattern + ")"); + } + + encoder_ = std::move(encoder); + special_tokens_encoder = std::move(special_encoder); + + for (const auto &[k, v] : encoder_) { + decoder_.emplace(v, k); + } + assert(encoder_.size() != decoder_.size() && "Encoder and decoder must be of equal length; maybe you had duplicate token indices in your encoder?"); + + for (const auto &[k, v] : special_tokens_encoder) { + special_tokens_decoder.emplace(v, k); + } + } + + auto encode_ordinary(const std::string &text) const -> std::vector { + return _encode_ordinary_native(text); + } + + auto encode(const std::string &text) const -> std::vector { + return _encode_native(text, special_tokens_encoder).first; + } + + auto encode_single_piece(const std::string &text) const -> std::vector { + auto iter = encoder_.find(text); + if (iter != encoder_.end()) { + return {iter->second}; + } + return byte_pair_encode(text, encoder_); + } + + auto decode(const std::vector &tokens) const -> std::string { + return _decode_native(tokens); + } + + private: + auto split_with_allowed_special_token( + re2::StringPiece &input, + const ankerl::unordered_dense::map &allowed_special + ) const -> std::pair, re2::StringPiece> { + if (special_regex_ == nullptr) return { std::nullopt, input }; + + auto start = input.begin(); + std::string special; + while (true) { + if (!re2::RE2::FindAndConsume(&input, *special_regex_, &special)) { + break; + } + + if (allowed_special.count(special) == 1) { + return { std::move(special), re2::StringPiece(start, input.begin() - start - special.size()) }; + } + } + + return { std::nullopt, input }; + } + + auto _encode_ordinary_native(const std::string &text) const -> std::vector { + std::vector ret; + re2::StringPiece input(text); + + std::string piece; + while (re2::RE2::FindAndConsume(&input, *regex_, &piece)) { + auto iter = encoder_.find(piece); + if (iter != encoder_.end()) { + ret.push_back(iter->second); + continue; + } + auto tokens = byte_pair_encode(piece, encoder_); + ret.insert(ret.end(), tokens.begin(), tokens.end()); + } + return ret; + } + + auto _encode_native( + const std::string &text, + const ankerl::unordered_dense::map &allowed_special + ) const -> std::pair, int> { + std::vector ret; + int last_piece_token_len = 0; + re2::StringPiece input(text); + + while (true) { + auto [special, sub_input] = split_with_allowed_special_token(input, allowed_special); + std::string piece; + while (re2::RE2::FindAndConsume(&sub_input, *regex_, &piece)) { + auto iter = encoder_.find(piece); + if (iter != encoder_.end()) { + last_piece_token_len = 1; + ret.push_back(iter->second); + continue; + } + auto tokens = byte_pair_encode(piece, encoder_); + last_piece_token_len = tokens.size(); + ret.insert(ret.end(), tokens.begin(), tokens.end()); + } + + if (special) { + int token = special_tokens_encoder.at(*special); + ret.push_back(token); + last_piece_token_len = 0; + } else { + break; + } + } + + return { ret, last_piece_token_len }; + } + + auto _decode_native(const std::vector &tokens) const -> std::string { + std::string ret; + ret.reserve(tokens.size() * 2); + for (auto token : tokens) { + std::string token_bytes; + auto iter = decoder_.find(token); + if (iter != decoder_.end()) { + token_bytes = iter->second; + } else { + iter = special_tokens_decoder.find(token); + if (iter != special_tokens_decoder.end()) { + token_bytes = iter->second; + } else { + throw std::runtime_error("unknown token: " + std::to_string(token)); + } + } + ret += token_bytes; + } + return ret; + } + + ankerl::unordered_dense::map encoder_; + ankerl::unordered_dense::map special_tokens_encoder; + ankerl::unordered_dense::map decoder_; + ankerl::unordered_dense::map special_tokens_decoder; + std::unique_ptr regex_; + std::unique_ptr special_regex_; +}; + +} // namespace tiktoken diff --git a/src/main/Cpp/RegisterTray/RegisterTray.vcxproj b/src/main/Cpp/RegisterTray/RegisterTray.vcxproj new file mode 100644 index 0000000..2d5a493 --- /dev/null +++ b/src/main/Cpp/RegisterTray/RegisterTray.vcxproj @@ -0,0 +1,164 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {8afd6796-6969-4cd3-a0e9-e9320a3d2163} + RegisterTray + 10.0.18362.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + C:\Users\Administrator\.jdks\corretto-20.0.2.1\include\win32;C:\Users\Administrator\.jdks\corretto-20.0.2.1\include;$(IncludePath) + + + + Level3 + true + WIN32;_DEBUG;REGISTERTRAY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;REGISTERTRAY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;REGISTERTRAY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;REGISTERTRAY_EXPORTS;_WINDOWS;_USRDLL;NOMINMAX;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp17 + + + Windows + true + true + true + false + jvm.lib;%(AdditionalDependencies) + C:\Users\Administrator\.jdks\corretto-20.0.2.1\lib + + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/src/main/Cpp/RegisterTray/RegisterTray.vcxproj.filters b/src/main/Cpp/RegisterTray/RegisterTray.vcxproj.filters new file mode 100644 index 0000000..e262026 --- /dev/null +++ b/src/main/Cpp/RegisterTray/RegisterTray.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/src/main/Cpp/RegisterTray/RegisterTray.vcxproj.user b/src/main/Cpp/RegisterTray/RegisterTray.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/src/main/Cpp/RegisterTray/RegisterTray.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/Cpp/RegisterTray/com_axis_innovators_box_tools_RegisterTray.h b/src/main/Cpp/RegisterTray/com_axis_innovators_box_tools_RegisterTray.h new file mode 100644 index 0000000..cf07f79 --- /dev/null +++ b/src/main/Cpp/RegisterTray/com_axis_innovators_box_tools_RegisterTray.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_axis_innovators_box_tools_RegisterTray */ + +#ifndef _Included_com_axis_innovators_box_tools_RegisterTray +#define _Included_com_axis_innovators_box_tools_RegisterTray +#ifdef __cplusplus +extern "C" { +#endif + /* + * Class: com_axis_innovators_box_tools_RegisterTray + * Method: register + * Signature: (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lcom/axis/innovators/box/tools/RegisterTray/Event;)J + */ + JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register + (JNIEnv*, jclass, jstring, jobject, jstring, jstring, jobject); + + /* + * Class: com_axis_innovators_box_tools_RegisterTray + * Method: unregister + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_axis_innovators_box_tools_RegisterTray_unregister + (JNIEnv*, jclass, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/main/Cpp/RegisterTray/dllmain.cpp b/src/main/Cpp/RegisterTray/dllmain.cpp new file mode 100644 index 0000000..940bcf2 --- /dev/null +++ b/src/main/Cpp/RegisterTray/dllmain.cpp @@ -0,0 +1,291 @@ +#include "pch.h" +#include +#include +#include +#include +#include +#include "com_axis_innovators_box_tools_RegisterTray.h" + +// 调试输出 +#define DEBUG_LOG(msg) OutputDebugStringW(L"[Tray] " msg L"\n") + +struct MenuItemData { + jmethodID onClickMethod; + jobject eventObj; + int menuId; +}; + +struct TrayData { + HMENU hMenu = NULL; + std::vector menuItems; + jobject eventObj = NULL; + jmethodID onClickMethod = NULL; + HWND hwnd = NULL; + UINT trayId = 0; + HICON hIcon = NULL; +}; + +std::vector trayDataList; + +HICON LoadTrayIcon(const wchar_t* path) { + HICON hIcon = (HICON)LoadImageW( + NULL, path, IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + LR_LOADFROMFILE | LR_SHARED + ); + return hIcon ? hIcon : LoadIconW(NULL, IDI_APPLICATION); +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + TrayData* pData = (TrayData*)GetWindowLongPtrW(hwnd, GWLP_USERDATA); + + switch (msg) { + case WM_USER + 1: + switch (lParam) { + case WM_RBUTTONUP: + if (pData && pData->hMenu) { + POINT pt; + GetCursorPos(&pt); + SetForegroundWindow(hwnd); + TrackPopupMenuEx(pData->hMenu, + TPM_RIGHTALIGN | TPM_BOTTOMALIGN, + pt.x, pt.y, hwnd, NULL); + PostMessageW(hwnd, WM_NULL, 0, 0); + } + return 0; + case WM_LBUTTONDOWN: + if (pData && pData->onClickMethod) { + JavaVM* jvm; + JNIEnv* env; + if (JNI_GetCreatedJavaVMs(&jvm, 1, NULL) == JNI_OK && + jvm->AttachCurrentThread((void**)&env, NULL) == JNI_OK) { + env->CallVoidMethod(pData->eventObj, pData->onClickMethod, (jlong)pData->trayId); + jvm->DetachCurrentThread(); + } + } + return 0; + } + break; + case WM_COMMAND: { + int menuId = LOWORD(wParam); + if (pData) { + for (auto& item : pData->menuItems) { + if (item.menuId == menuId) { + JavaVM* jvm; + JNIEnv* env; + if (JNI_GetCreatedJavaVMs(&jvm, 1, NULL) == JNI_OK && + jvm->AttachCurrentThread((void**)&env, NULL) == JNI_OK) { + env->CallVoidMethod(item.eventObj, item.onClickMethod, (jlong)pData->trayId); + jvm->DetachCurrentThread(); + } + break; + } + } + } + return 0; + } + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + return DefWindowProcW(hwnd, msg, wParam, lParam); +} + +JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register +(JNIEnv* env, jclass, jstring name, jobject menuItems, jstring icon, jstring, jobject event) { + // 注册窗口类 + WNDCLASSEXW wc = { sizeof(WNDCLASSEXW) }; + wc.lpfnWndProc = WndProc; + wc.hInstance = GetModuleHandleW(NULL); + wc.lpszClassName = L"TrayWindowClass"; + if (!RegisterClassExW(&wc)) return -1; + + // 创建消息窗口 + HWND hwnd = CreateWindowExW(0, L"TrayWindowClass", L"", 0, 0, 0, 0, 0, + HWND_MESSAGE, NULL, NULL, NULL); + if (!hwnd) return -1; + + TrayData* pData = new TrayData(); + pData->hwnd = hwnd; + pData->trayId = GetTickCount(); + pData->hMenu = CreatePopupMenu(); + + // 解析菜单项 + jclass listClass = env->GetObjectClass(menuItems); + jint size = env->CallIntMethod(menuItems, env->GetMethodID(listClass, "size", "()I")); + + for (int i = 0; i < size; ++i) { + jobject item = env->CallObjectMethod(menuItems, + env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"), i); + + jstring name = (jstring)env->GetObjectField(item, + env->GetFieldID(env->GetObjectClass(item), "name", "Ljava/lang/String;")); + jobject eventObj = env->GetObjectField(item, + env->GetFieldID(env->GetObjectClass(item), "event", + "Lcom/axis/innovators/box/tools/RegisterTray$Event;")); + + const jchar* nameChars = env->GetStringChars(name, NULL); + std::wstring menuName(nameChars, nameChars + env->GetStringLength(name)); + env->ReleaseStringChars(name, nameChars); + + MenuItemData menuItem; + menuItem.menuId = 1000 + i; + menuItem.eventObj = env->NewGlobalRef(eventObj); + jclass eventClass = env->GetObjectClass(eventObj); + menuItem.onClickMethod = env->GetMethodID(eventClass, "onClick", "(J)V"); + + AppendMenuW(pData->hMenu, MF_STRING, menuItem.menuId, menuName.c_str()); + pData->menuItems.push_back(menuItem); + } + + // 事件回调 + jclass eventClass = env->GetObjectClass(event); + pData->onClickMethod = env->GetMethodID(eventClass, "onClick", "(J)V"); + pData->eventObj = env->NewGlobalRef(event); + + // 配置托盘 + NOTIFYICONDATAW nid = { sizeof(NOTIFYICONDATAW) }; + nid.hWnd = hwnd; + nid.uID = pData->trayId; + nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; + nid.uCallbackMessage = WM_USER + 1; + + // 加载图标 + const wchar_t* iconPath = (const wchar_t*)env->GetStringChars(icon, NULL); + pData->hIcon = LoadTrayIcon(iconPath); + nid.hIcon = pData->hIcon; + env->ReleaseStringChars(icon, (const jchar*)iconPath); + + // 设置提示 + const wchar_t* tip = (const wchar_t*)env->GetStringChars(name, NULL); + wcsncpy_s(nid.szTip, _countof(nid.szTip), tip, _TRUNCATE); + env->ReleaseStringChars(name, (const jchar*)tip); + + if (!Shell_NotifyIconW(NIM_ADD, &nid)) { + delete pData; + return -1; + } + + SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)pData); + trayDataList.push_back(pData); + + // 启动消息循环 + MSG msg; + while (GetMessageW(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + return (jlong)pData->trayId; +} + +TrayData* FindTrayData(UINT trayId) { + for (auto data : trayDataList) { + if (data->trayId == trayId) return data; + } + return nullptr; +} + + +JNIEXPORT void JNICALL Java_com_axis_innovators_box_tools_RegisterTray_unregister +(JNIEnv* env, jclass clazz, jlong id) { + const UINT trayId = static_cast(id); + TrayData* pData = FindTrayData(trayId); + + if (!pData) { + OutputDebugStringW(L"[unregister] 找不到对应的托盘数据"); + return; + } + + // 1. 删除系统托盘图标 + NOTIFYICONDATAW nid = { sizeof(NOTIFYICONDATAW) }; + nid.hWnd = pData->hwnd; + nid.uID = trayId; + + if (!Shell_NotifyIconW(NIM_DELETE, &nid)) { + DWORD err = GetLastError(); + wchar_t errMsg[256]; + swprintf_s(errMsg, _countof(errMsg), L"[unregister] 销毁窗口失败 (错误码: 0x%08X)", err); + OutputDebugStringW(errMsg); + } + + // 2. 释放图标资源 + if (pData->hIcon) { + if (!DestroyIcon(pData->hIcon)) { + OutputDebugStringW(L"[unregister] 销毁图标失败"); + } + else { + OutputDebugStringW(L"[unregister] 图标资源已释放"); + } + pData->hIcon = NULL; + } + + // 3. 销毁菜单 + if (pData->hMenu) { + if (!DestroyMenu(pData->hMenu)) { + OutputDebugStringW(L"[unregister] 销毁菜单失败"); + } + else { + OutputDebugStringW(L"[unregister] 菜单已销毁"); + } + pData->hMenu = NULL; + } + + // 4. 销毁窗口 + if (pData->hwnd) { + if (!DestroyWindow(pData->hwnd)) { + DWORD err = GetLastError(); + wchar_t errMsg[256]; + swprintf_s(errMsg, _countof(errMsg), // 修改点 + L"[unregister] 销毁窗口失败 (错误码: 0x%08X)", + err); + OutputDebugStringW(errMsg); + } + else { + OutputDebugStringW(L"[unregister] 窗口已销毁"); + } + pData->hwnd = NULL; + } + + // 5. 释放JNI全局引用 + if (pData->eventObj) { + env->DeleteGlobalRef(pData->eventObj); + pData->eventObj = NULL; + OutputDebugStringW(L"[unregister] 事件全局引用已释放"); + } + + // 6. 释放菜单项全局引用 + for (auto& item : pData->menuItems) { + if (item.eventObj) { + env->DeleteGlobalRef(item.eventObj); + item.eventObj = NULL; + } + } + pData->menuItems.clear(); + OutputDebugStringW(L"[unregister] 菜单项资源已清理"); + + // 7. 从全局列表移除 + auto it = std::remove_if(trayDataList.begin(), trayDataList.end(), + [pData](TrayData* data) { return data == pData; }); + + if (it != trayDataList.end()) { + trayDataList.erase(it, trayDataList.end()); + OutputDebugStringW(L"[unregister] 已从全局列表移除"); + } + + // 8. 释放内存 + delete pData; + OutputDebugStringW(L"[unregister] 内存已释放"); + + // 9. 强制重绘任务栏 + HWND taskbar = FindWindowW(L"Shell_TrayWnd", NULL); + if (taskbar) { + RedrawWindow(taskbar, NULL, NULL, + RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN); + } +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { + return TRUE; +} \ No newline at end of file diff --git a/src/main/Cpp/RegisterTray/framework.h b/src/main/Cpp/RegisterTray/framework.h new file mode 100644 index 0000000..80cbbc9 --- /dev/null +++ b/src/main/Cpp/RegisterTray/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/src/main/Cpp/RegisterTray/pch.cpp b/src/main/Cpp/RegisterTray/pch.cpp new file mode 100644 index 0000000..b6fb8f4 --- /dev/null +++ b/src/main/Cpp/RegisterTray/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: 与预编译标头对应的源文件 + +#include "pch.h" + +// 当使用预编译的头时,需要使用此源文件,编译才能成功。 diff --git a/src/main/Cpp/RegisterTray/pch.h b/src/main/Cpp/RegisterTray/pch.h new file mode 100644 index 0000000..9660927 --- /dev/null +++ b/src/main/Cpp/RegisterTray/pch.h @@ -0,0 +1,13 @@ +// pch.h: 这是预编译标头文件。 +// 下方列出的文件仅编译一次,提高了将来生成的生成性能。 +// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 +// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。 +// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。 + +#ifndef PCH_H +#define PCH_H + +// 添加要在此处预编译的标头 +#include "framework.h" + +#endif //PCH_H diff --git a/src/main/Cpp/ThrowSafely/ThrowSafely.vcxproj b/src/main/Cpp/ThrowSafely/ThrowSafely.vcxproj new file mode 100644 index 0000000..4cf9f1e --- /dev/null +++ b/src/main/Cpp/ThrowSafely/ThrowSafely.vcxproj @@ -0,0 +1,157 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {08b41ee2-1bc6-4aab-a7bb-5ae88d658d33} + ThrowSafely + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;THROWSAFELY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;THROWSAFELY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;THROWSAFELY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;THROWSAFELY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/src/main/Cpp/ThrowSafely/ThrowSafely.vcxproj.filters b/src/main/Cpp/ThrowSafely/ThrowSafely.vcxproj.filters new file mode 100644 index 0000000..2689155 --- /dev/null +++ b/src/main/Cpp/ThrowSafely/ThrowSafely.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/src/main/Cpp/ThrowSafely/ThrowSafely.vcxproj.user b/src/main/Cpp/ThrowSafely/ThrowSafely.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/src/main/Cpp/ThrowSafely/ThrowSafely.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/Cpp/ThrowSafely/dllmain.cpp b/src/main/Cpp/ThrowSafely/dllmain.cpp new file mode 100644 index 0000000..a778850 --- /dev/null +++ b/src/main/Cpp/ThrowSafely/dllmain.cpp @@ -0,0 +1,172 @@ +// dllmain.cpp : 定义 DLL 应用程序的入口点。 +#include "pch.h" +#include +#include +#include +#include +#include + +#pragma comment(lib, "DbgHelp.lib") + +// 函数声明 +LONG WINAPI TopLevelExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo); +void GenerateMiniDump(EXCEPTION_POINTERS* pExceptionInfo); +std::wstring GetExceptionInfo(EXCEPTION_POINTERS* pExceptionInfo); +std::wstring GetStackTrace(PCONTEXT pContext); +void ShowErrorDialog(const std::wstring& message); + +// 全局变量用于存储模块句柄 +HMODULE g_hModule = NULL; + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + g_hModule = hModule; + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + // 初始化符号系统 + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + SymInitialize(GetCurrentProcess(), NULL, TRUE); + + // 设置未处理异常过滤器 + SetUnhandledExceptionFilter(TopLevelExceptionFilter); + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + //SymCleanup(GetCurrentProcess()); + break; + } + return TRUE; +} + +LONG WINAPI TopLevelExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) +{ + // 生成崩溃转储文件 + GenerateMiniDump(pExceptionInfo); + + // 收集异常信息 + std::wstring errorMessage = GetExceptionInfo(pExceptionInfo); + errorMessage += L"\n\nStack Trace:\n" + GetStackTrace(pExceptionInfo->ContextRecord); + + // 显示错误对话框 + ShowErrorDialog(errorMessage); + + // 结束进程 + return EXCEPTION_EXECUTE_HANDLER; +} + +void GenerateMiniDump(EXCEPTION_POINTERS* pExceptionInfo) +{ + HANDLE hFile = CreateFileW( + L"CrashReport.dmp", + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (hFile != INVALID_HANDLE_VALUE) + { + MINIDUMP_EXCEPTION_INFORMATION mdei; + mdei.ThreadId = GetCurrentThreadId(); + mdei.ExceptionPointers = pExceptionInfo; + mdei.ClientPointers = FALSE; + + MiniDumpWriteDump( + GetCurrentProcess(), + GetCurrentProcessId(), + hFile, + MiniDumpWithDataSegs, + &mdei, + NULL, + NULL); + + CloseHandle(hFile); + } +} + +std::wstring GetExceptionInfo(EXCEPTION_POINTERS* pExceptionInfo) +{ + std::wstringstream ss; + ss << L"Exception Code: 0x" << std::hex << pExceptionInfo->ExceptionRecord->ExceptionCode << std::endl; + ss << L"Exception Address: 0x" << pExceptionInfo->ExceptionRecord->ExceptionAddress << std::endl; + + // 添加更多异常信息... + return ss.str(); +} + +std::wstring GetStackTrace(PCONTEXT pContext) +{ + std::wstringstream ss; + STACKFRAME64 stackFrame = { 0 }; + DWORD machineType = IMAGE_FILE_MACHINE_AMD64; + +#ifdef _M_IX86 + machineType = IMAGE_FILE_MACHINE_I386; + stackFrame.AddrPC.Offset = pContext->Eip; + stackFrame.AddrFrame.Offset = pContext->Ebp; + stackFrame.AddrStack.Offset = pContext->Esp; +#else + machineType = IMAGE_FILE_MACHINE_AMD64; + stackFrame.AddrPC.Offset = pContext->Rip; + stackFrame.AddrFrame.Offset = pContext->Rbp; + stackFrame.AddrStack.Offset = pContext->Rsp; +#endif + + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrStack.Mode = AddrModeFlat; + + for (ULONG frameNum = 0; ; frameNum++) + { + if (!StackWalk64( + machineType, + GetCurrentProcess(), + GetCurrentThread(), + &stackFrame, + pContext, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL)) + { + break; + } + + DWORD64 displacement = 0; + IMAGEHLP_SYMBOL64* pSymbol = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + 256); + pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + pSymbol->MaxNameLength = 255; + + if (SymGetSymFromAddr64(GetCurrentProcess(), stackFrame.AddrPC.Offset, &displacement, pSymbol)) + { + ss << L"0x" << (PVOID)stackFrame.AddrPC.Offset << L" " << pSymbol->Name; + if (displacement) ss << L" + 0x" << displacement; + ss << std::endl; + } + + free(pSymbol); + } + + return ss.str(); +} + +void ShowErrorDialog(const std::wstring& message) +{ + std::wstring fullMessage = message + + L"\n\nA crash dump file (CrashReport.dmp) has been generated." + + L"\nPlease contact support and provide this file for analysis." + + L"\n\nClick OK to terminate the application."; + + MessageBoxW(NULL, + fullMessage.c_str(), + L"Critical Error Occurred", + MB_ICONERROR | MB_OK); +} \ No newline at end of file diff --git a/src/main/Cpp/ThrowSafely/framework.h b/src/main/Cpp/ThrowSafely/framework.h new file mode 100644 index 0000000..80cbbc9 --- /dev/null +++ b/src/main/Cpp/ThrowSafely/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/src/main/Cpp/ThrowSafely/pch.cpp b/src/main/Cpp/ThrowSafely/pch.cpp new file mode 100644 index 0000000..b6fb8f4 --- /dev/null +++ b/src/main/Cpp/ThrowSafely/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: 与预编译标头对应的源文件 + +#include "pch.h" + +// 当使用预编译的头时,需要使用此源文件,编译才能成功。 diff --git a/src/main/Cpp/ThrowSafely/pch.h b/src/main/Cpp/ThrowSafely/pch.h new file mode 100644 index 0000000..9660927 --- /dev/null +++ b/src/main/Cpp/ThrowSafely/pch.h @@ -0,0 +1,13 @@ +// pch.h: 这是预编译标头文件。 +// 下方列出的文件仅编译一次,提高了将来生成的生成性能。 +// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 +// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。 +// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。 + +#ifndef PCH_H +#define PCH_H + +// 添加要在此处预编译的标头 +#include "framework.h" + +#endif //PCH_H