C#调用C++ DLL实战指南:从P/Invoke原理到复杂数据类型处理
1. 项目概述为什么C#需要调用C DLL在桌面应用、游戏开发、工业控制或者高性能计算的后台服务里我们经常会遇到一个场景核心算法或者与特定硬件交互的模块是用C写的已经编译成了动态链接库DLL但整个应用的主体框架是用C#构建的。这时候让C#去调用C的DLL就成了一个必须跨越的技术门槛。这不仅仅是简单的“调用”它涉及到两种不同语言、不同运行时环境、不同内存管理模型之间的“握手”。我见过不少项目前期为了快速验证把所有逻辑都堆在C#里等到性能瓶颈出现或者需要集成某个只有C版本的第三方库时才手忙脚乱地开始研究互操作往往踩坑无数。所以这个教程的目的很明确就是帮你把C#调用C DLL这条路彻底走通从最基础的声明开始到复杂数据类型的传递再到内存管理和异常处理我会把每一步的原理、常见的“坑”以及我实际调试中总结的技巧都摊开来讲清楚。无论你是需要在C#上位机软件里集成一个C的图像处理库还是想复用一些遗留的C业务逻辑这篇文章都能给你一套可以直接“抄作业”的完整方案。2. 核心概念与前置知识扫盲在动手写代码之前我们必须先统一几个关键概念的理解。很多调用失败的问题根源都在于对这些基础概念的混淆。2.1 什么是DLLC DLL有何特殊DLLDynamic Link Library是Windows平台上动态链接库的文件扩展名。你可以把它理解为一个代码仓库里面打包了编译好的函数、类或资源。程序在运行时可以动态地加载这个仓库并使用里面的东西。C编写的DLL之所以“特殊”主要体现在两个方面名称修饰Name ManglingC支持函数重载编译器为了区分同名但参数不同的函数会对函数名进行“修饰”生成一个唯一的内部名称。这个修饰后的名字对于C#来说是“看不懂”的。因此在C侧我们通常需要用extern C来告诉编译器“这个函数请用C语言的规则来编译和链接”从而避免名称修饰产生一个C#能识别的简单函数名。调用约定Calling Convention这规定了函数调用时参数是如何压栈、由谁调用者还是被调用者来清理栈以及函数名如何修饰等规则。C常见的调用约定有__cdecl,__stdcall,__fastcall等。而C#的P/Invoke默认使用__stdcall在Windows API中广泛使用。如果两边约定不一致程序会立刻崩溃。2.2 C#的“外交官”P/Invoke平台调用P/InvokePlatform Invocation Services是.NET Framework和.NET Core/.NET 5中提供的一套机制专门用于托管代码如C#调用非托管代码如C DLL。你可以把P/Invoke想象成C#派出的“外交官”和“翻译官”。外交官DllImport特性负责定位DLL文件并声明要调用哪个函数。翻译官Marshal类负责在托管堆C#管理的内存和非托管堆C管理的内存之间转换数据类型。这是最复杂也最容易出错的部分比如把C#的string转换成C的char*或者处理结构体、数组。理解P/Invoke是成功调用的基石。接下来我们就从一个最简单的例子开始看看这位“外交官”是如何工作的。3. 从零开始一个最简单的C#调用C DLL实例我们从一个“Hello World”级别的例子开始确保环境通路。这个例子将演示如何传递一个整数并返回它的平方。3.1 第一步创建并编译C DLL首先我们用Visual Studio创建一个C动态链接库项目。创建项目打开VS选择“创建新项目” - “动态链接库(DLL)”命名为SimpleMathLib。编写头文件.h在头文件中声明我们的导出函数。关键点在于使用extern C和__declspec(dllexport)。// SimpleMathLib.h #pragma once // 使用extern C防止C名称修饰使用__stdcall调用约定与C#默认匹配 extern C { __declspec(dllexport) int __stdcall Square(int value); }extern C确保函数以C语言风格链接导出名称为简单的Square。__declspec(dllexport)告诉编译器这个函数需要从DLL中导出。__stdcall指定调用约定。这里显式指定与C# P/Invoke默认行为一致是好习惯。编写源文件.cpp实现这个函数。// SimpleMathLib.cpp #include pch.h // 预编译头VS项目自带 #include SimpleMathLib.h int __stdcall Square(int value) { return value * value; }编译生成选择合适的平台如x64编译项目。成功后在项目的输出目录如x64/Debug/下会找到SimpleMathLib.dll文件。请记下这个路径。3.2 第二步在C#项目中声明并调用现在我们创建一个C#控制台应用来调用这个DLL。创建C#项目在同一个解决方案或新解决方案中创建一个C#控制台应用命名为DllCaller。将DLL文件放置到正确位置为了让C#程序在运行时能找到DLL最简单的方法是将SimpleMathLib.dll复制到C#项目的输出目录如bin\Debug\net6.0\下。也可以在代码中指定绝对路径但不利于部署。编写C#调用代码使用DllImport特性来声明外部函数。using System; using System.Runtime.InteropServices; // 必须引入此命名空间 namespace DllCaller { class Program { // 声明外部方法 // DllImport 特性用于指定DLL路径和函数名 // EntryPoint Square 指明DLL中的函数名 // CallingConvention CallingConvention.StdCall 指明调用约定可省略因为这是默认值 [DllImport(SimpleMathLib.dll, EntryPoint Square)] public static extern int Square(int value); static void Main(string[] args) { int number 5; int result Square(number); // 像调用普通C#方法一样调用 Console.WriteLine($The square of {number} is {result}); // 输出The square of 5 is 25 } } }运行测试运行C#程序。如果一切顺利你将看到正确的输出。如果遇到“无法加载DLL”或“找不到入口点”的错误请跳转到本文最后的“问题排查”章节。注意DllImport中的DLL名称不包含路径时Windows会按特定顺序搜索一系列目录包括应用程序所在目录、系统目录等。将DLL放在程序输出目录是最简单的做法。这个最简单的例子打通了基本流程。但真实世界的需求远不止传递一个整数这么简单。接下来我们将深入更复杂的数据类型。4. 处理复杂数据类型字符串、结构体与数组实际开发中我们经常需要传递字符串、自定义结构体或者数组。这些类型在C#和C中的内存表示差异很大需要“翻译官”Marshal精心处理。4.1 字符串传递从C#的string到C的char*字符串传递是高频需求也是陷阱高发区。核心在于理解C#的string是不可变的托管对象而C通常期望一个以空字符\0结尾的字符数组char*或wchar_t*。场景C#传递一个名字给C DLLDLL返回一句问候语。C DLL端// StringManip.h extern C { // 注意接收const char*表示我们不会修改输入字符串 __declspec(dllexport) void __stdcall Greet(const char* name, char* greeting, int bufferSize); } // StringManip.cpp #include string.h // for strcpy_s void __stdcall Greet(const char* name, char* greeting, int bufferSize) { // 安全地组合字符串防止缓冲区溢出 std::string message Hello, ; message name; message !; // 确保不超过C#提供的缓冲区大小 strcpy_s(greeting, bufferSize, message.c_str()); }C#客户端端class Program { // 关键点1使用MarshalAs指示如何封送字符串 // UnmanagedType.LPStr 表示将string作为ANSI字符串指针(char*)传递 [DllImport(StringLib.dll)] public static extern void Greet( [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] StringBuilder greeting, // 关键点2使用StringBuilder接收输出 int bufferSize); static void Main(string[] args) { string inputName World; // 关键点3为输出参数预分配足够大小的缓冲区 StringBuilder outputGreeting new StringBuilder(256); int bufferSize outputGreeting.Capacity; Greet(inputName, outputGreeting, bufferSize); Console.WriteLine(outputGreeting.ToString()); // 输出Hello, World! } }实操心得输入字符串C# - C对于string输入参数使用[MarshalAs(UnmanagedType.LPStr)]通常可以自动处理。如果是宽字符Unicode则使用LPWStr。输出字符串C - C#绝不能使用string类型作为输出参数因为string是不可变的C#无法为非托管代码分配内存。必须使用StringBuilder它内部有一个字符缓冲区可以被非托管代码修改。务必在调用前通过构造函数new StringBuilder(容量)分配足够的空间并将容量值传递给C函数防止缓冲区溢出。字符编码确保两端编码一致。LPStr对应ANSI多字节LPWStr对应Unicode宽字符。现代Windows应用更推荐使用Unicodewchar_t*和LPWStr。4.2 结构体传递确保内存布局一致当需要传递一组相关的数据时使用结构体。最大的挑战是保证C#和C中结构体的内存布局字段顺序、对齐方式完全相同。场景传递一个“点”的坐标x, y到C DLL进行计算。C DLL端// PointLib.h #pragma pack(push, 1) // 关键设置1字节对齐消除编译器填充带来的不一致性 typedef struct { int x; int y; } MyPoint; #pragma pack(pop) // 恢复默认对齐 extern C { __declspec(dllexport) int __stdcall CalculateDistance(MyPoint p1, MyPoint p2); }C#客户端端using System.Runtime.InteropServices; // 关键使用StructLayout特性显式控制内存布局 [StructLayout(LayoutKind.Sequential, Pack 1)] // Pack1 对应C的#pragma pack(1) public struct MyPoint { public int x; public int y; // 字段顺序和类型必须与C完全一致 } class Program { [DllImport(PointLib.dll)] public static extern int CalculateDistance(MyPoint p1, MyPoint p2); static void Main(string[] args) { MyPoint pointA new MyPoint { x 0, y 0 }; MyPoint pointB new MyPoint { x 3, y 4 }; int distance CalculateDistance(pointA, pointB); Console.WriteLine($Distance: {distance}); // 输出5 } }注意事项LayoutKind.Sequential强制字段按照在类或结构中的出现顺序依次排列。Pack指定字段的内存对齐字节数。Pack 1表示按1字节对齐即紧密排列无填充。必须与C端的#pragma pack值匹配这是避免因平台和编译器差异导致内存错位的关键。字段类型C#的int对应C的int通常都是4字节但像long这种类型在两者中长度可能不同C#中固定为8字节C中可能为4或8字节需要使用intptr_t或显式指定大小的类型如int32_t。4.3 数组传递指针与长度的配合传递数组本质上是传递一个指向数组首元素的指针以及数组的长度。场景C#传递一个整数数组给C DLL求和。C DLL端// ArrayLib.h extern C { __declspec(dllexport) int __stdcall SumArray(int* arr, int length); } // ArrayLib.cpp int __stdcall SumArray(int* arr, int length) { int sum 0; for (int i 0; i length; i) { sum arr[i]; } return sum; }C#客户端端class Program { // 方法一将数组作为指针和长度分开传递最接近C原生方式 [DllImport(ArrayLib.dll)] public static extern int SumArray(IntPtr arrPtr, int length); // 方法二使用MarshalAs将数组自动封送更简洁 [DllImport(ArrayLib.dll)] public static extern int SumArray2([MarshalAs(UnmanagedType.LPArray, SizeParamIndex 1)] int[] arr, int length); static void Main(string[] args) { int[] numbers { 1, 2, 3, 4, 5 }; // 使用方法一 IntPtr ptr Marshal.AllocHGlobal(numbers.Length * sizeof(int)); // 非托管堆分配内存 Marshal.Copy(numbers, 0, ptr, numbers.Length); // 复制数据 int sum1 SumArray(ptr, numbers.Length); Marshal.FreeHGlobal(ptr); // 切记释放内存 Console.WriteLine($Sum (Method1): {sum1}); // 使用方法二推荐 int sum2 SumArray2(numbers, numbers.Length); Console.WriteLine($Sum (Method2): {sum2}); } }核心技巧方法二MarshalAs更安全便捷指定UnmanagedType.LPArray并利用SizeParamIndex属性告诉Marshal哪个参数是数组长度.NET会帮你自动完成内存复制和固定。这是首选方案。方法一IntPtr更灵活当你需要更精细地控制内存比如复用缓冲区、处理非托管代码返回的数组指针时需要手动使用Marshal.AllocHGlobal、Marshal.Copy和Marshal.FreeHGlobal。务必成对使用分配和释放否则会导致内存泄漏。数组内容修改如果C函数会修改数组内容在C#端需要确保数组是可修改的并且理解修改何时生效。对于LPArray修改通常在调用返回后立即反映在托管数组中。5. 高级话题与性能优化掌握了基本数据类型的传递后我们可以关注一些更高级的场景和性能优化技巧。5.1 回调函数Callbacks让C调用C#有时C DLL需要异步通知C#某些事件如进度更新、数据到达这就需要回调函数。C#将一个函数指针委托传递给CC在适当时机调用它。C DLL端// CallbackLib.h // 定义回调函数类型 typedef void (__stdcall *ProgressCallback)(int percent); extern C { __declspec(dllexport) void __stdcall StartLongTask(ProgressCallback callback); }C#客户端端class Program { // 1. 定义与C回调函数签名匹配的委托 [UnmanagedFunctionPointer(CallingConvention.StdCall)] // 必须指定调用约定 public delegate void ProgressCallback(int percent); // 2. 声明外部方法 [DllImport(CallbackLib.dll)] public static extern void StartLongTask(ProgressCallback callback); // 3. 实现回调方法 public static void OnProgressUpdate(int percent) { Console.WriteLine($Progress: {percent}%); } static void Main(string[] args) { // 4. 创建委托实例并传递 ProgressCallback callback new ProgressCallback(OnProgressUpdate); StartLongTask(callback); // 注意必须确保委托实例在回调可能发生的整个生命周期内不被垃圾回收 // 一个简单的方法是将其保存在类的成员变量中。 GC.KeepAlive(callback); } }关键点[UnmanagedFunctionPointer]这个特性至关重要它确保委托被正确转换为非托管函数指针并且调用约定匹配。生命周期管理传递给非托管代码的委托实例必须在其可能被调用的期间保持“存活”即不能被垃圾回收。通常的做法是将其存储在一个类级别的静态或实例变量中。GC.KeepAlive在方法结束时强制保留引用也是一种保护手段。5.2 内存管理最佳实践谁分配谁释放这是C#/C互操作中最容易出错的地方遵循“谁分配谁释放”的黄金法则可以避免绝大多数内存泄漏和访问冲突。C#分配C#释放使用Marshal.AllocHGlobal、Marshal.StringToHGlobalAnsi等分配的内存必须在C#端用对应的Marshal.FreeHGlobal释放。通常用在为C函数提供输入/输出缓冲区时。C分配C释放如果C函数返回一个指针并期望调用者释放它那么C必须提供一个配套的释放函数如FreeBuffer(void* ptr)并在C#中通过P/Invoke调用它。绝不能用C#的Marshal.FreeHGlobal去释放Cnew出来的内存反之亦然因为它们可能来自不同的堆。C分配C#“借用”如果C返回指向其内部静态缓冲区或全局变量的指针且该内存生命周期与DLL本身一致则C#可以安全地读取但不应尝试释放它。这种情况下文档必须清晰说明。5.3 性能优化减少封送开销P/Invoke调用和数据类型封送Marshaling是有开销的。对于高频调用的简单函数或者需要传递大量数据的场景优化至关重要。减少调用次数设计接口时尽量让一次调用完成更多工作而不是频繁进行大量的小调用。例如传递一个结构体数组而不是多次调用传递单个结构体。使用blittable类型blittable类型是指在托管和非托管内存中具有相同位表示形式的类型如int,double,long在64位系统上以及仅包含blittable类型的结构体。传递blittable类型时CLR可以直接进行内存块复制效率极高。非blittable类型如bool,char,string则需要转换。固定Pinning对于需要传递大型数组且C函数只是读取不修改指针的情况可以使用fixed语句或GCHandle.Alloc(obj, GCHandleType.Pinned)将托管数组固定在内存中然后直接传递指针。这避免了复制整个数组的开销。但固定会阻碍垃圾回收器移动该对象时间过长会影响GC效率应谨慎使用。使用unsafe代码和指针在C#中启用unsafe上下文可以直接操作指针与非托管代码进行零拷贝交互。这需要较高的技巧和对内存安全的深刻理解但能带来最大的性能提升。6. 实战问题排查与调试技巧实录即使按照教程一步步来在实际集成时也难免会遇到各种问题。下面是我总结的几个最常见错误及其解决方法。6.1 “无法加载DLL‘XXX.dll’或它的一个依赖项。”这是最常见的错误之一。检查DLL位置确保DLL在应用程序的探测路径下。最简单的方法就是复制到bin\Debug\[框架]目录下。也可以使用SetDllDirectoryAPI或在DllImport中指定绝对路径不推荐降低可移植性。检查平台位数x86/x64这是重中之重你的C#项目平台目标Any CPU, x86, x64必须与C DLL编译的平台匹配。一个32位x86进程无法加载64位x64的DLL反之亦然。在Visual Studio中确保解决方案配置管理器里两个项目的活动解决方案平台一致例如都是x64。检查运行时依赖C DLL可能依赖于其他DLL如特定的VC运行时库msvcp140.dll,vcruntime140.dll。使用Dependency Walker或Visual Studio 的 dumpbin /dependents命令查看依赖。确保这些依赖库也存在于目标机器上。通常需要安装对应版本的Visual C Redistributable。6.2 “找不到入口点”检查函数名和调用约定使用dumpbin /exports YourDll.dll命令查看DLL实际导出的函数名。确认C#中DllImport的EntryPoint名称与导出名称完全一致包括大小写。检查调用约定CallingConvention是否匹配。检查extern C确认C函数声明在extern C块内以防止名称修饰。如果C函数被重载即使有extern C也可能产生修饰名应避免重载导出函数。检查函数签名参数类型、返回类型必须严格匹配。一个int*和一个int[]在C#端封送处理可能不同。6.3 “尝试读取或写入受保护的内存”这通常是内存访问违规原因可能很复杂。缓冲区溢出C#传递给C的缓冲区如StringBuilder大小不足C写入了超出边界的内存。确保传递了正确的缓冲区容量。结构体布局不一致C#和C的结构体大小或对齐方式不同导致C访问了错误的内存偏移。使用Marshal.SizeOf()和sizeof()分别检查C#和C中结构体的大小是否一致。严格使用[StructLayout]和#pragma pack。错误的指针类型比如将IntPtr当作指向整数的指针使用但实际上它指向一个结构体。仔细检查指针操作。使用已释放的内存在C#端过早释放了固定或分配的内存但C还在使用。确保内存生命周期覆盖整个调用周期。6.4 调试技巧在C DLL中输出日志这是最直接有效的方法。使用OutputDebugString函数输出调试信息可以在Visual Studio的“输出”窗口或使用DebugView工具查看。附加调试器在Visual Studio中可以同时调试C#和C代码。将C#项目设为启动项目在C项目的函数入口处设置断点然后启动调试F5。当C#调用到C函数时调试器会跳转到C代码中。使用Marshal.GetLastWin32Error在P/Invoke调用后可以调用Marshal.GetLastWin32Error()获取Win32 API设置的错误代码有助于诊断系统调用失败的原因。需要在DllImport中设置SetLastError true。7. 封装与设计构建健壮的互操作层当需要调用的C函数很多时直接在业务代码中到处写DllImport会非常混乱且难以维护。一个好的实践是创建一个专门的互操作层Interop Layer来封装所有原生调用。创建原生方法静态类将所有DllImport声明集中在一个或多个静态类中例如NativeMethods或SafeNativeMethods。类名加上internal修饰符仅对程序集内部暴露。提供托管友好包装不要将原始的P/Invoke方法直接暴露给业务代码。创建一些包装方法处理复杂的参数封送、错误检查、资源管理和异常转换。例如将一个需要手动分配/释放缓冲区的C函数包装成一个返回string的C#方法。实现IDisposable管理资源如果封装了需要释放的非托管资源如句柄、内存指针实现IDisposable接口确保在Dispose方法或析构函数中正确释放资源。统一的错误处理在包装层将C返回的错误代码或Win32错误码转换为有意义的.NET异常如InvalidOperationException,ArgumentException等让业务逻辑可以用标准的try-catch处理。我个人在大型项目中倾向于采用这种模式。它隔离了不安全的原生代码为上层提供了干净、安全、符合.NET习惯的API大大降低了使用复杂度和出错概率。当底层C DLL更换或升级时也只需要修改这一层封装即可。

相关新闻

ES-Client:重塑Elasticsearch管理体验的现代化客户端解决方案

ES-Client:重塑Elasticsearch管理体验的现代化客户端解决方案

ES-Client:重塑Elasticsearch管理体验的现代化客户端解决方案 【免费下载链接】es-client elasticsearch客户端,issue请前往码云:https://gitee.com/qiaoshengda/es-client 项目地址: https://gitcode.com/gh_mirrors/es/es-client 在…

2026/10/14 23:29:33 阅读更多 →
粉笔公考980是什么?包含哪些课程和教材?

粉笔公考980是什么?包含哪些课程和教材?

本文面向准备报考国家公务员考试、省级公务员考试及事业单位考试的考生,围绕"粉笔公考980系统班"这一在公考备考社区讨论度较高的入门级全程班产品,对其课程结构、课时安排、配套书目与适用边界做客观拆解。文中数据来源于粉笔科技公开财报、粉…

2026/10/14 5:16:30 阅读更多 →
编写仿真PID程序,模拟单回路液位控制系统,支持手动修改P/I/D参数观察控制曲线。

编写仿真PID程序,模拟单回路液位控制系统,支持手动修改P/I/D参数观察控制曲线。

PID单回路液位控制仿真系统 —— 基于OOP的工业数据实战"调PID就像调一台老收音机——P是音量旋钮,I是低音补偿,D是高音抑制。不懂原理瞎拧,只会越调越吵。"—— 哈尔滨工程大学《工业过程控制》课程核心思想一、实际应用场景描述在…

2026/10/14 1:39:07 阅读更多 →

最新新闻

聊天机器人如何实现新闻传播全链条再造:从选题到分发的对话式智能体实践

聊天机器人如何实现新闻传播全链条再造:从选题到分发的对话式智能体实践

1. 从标题拆解这个项目的核心命题1.1 这个标题到底在说什么“聊天机器人与新闻传播的全链条再造”,这个标题我第一次看到的时候,脑子里蹦出来的第一个念头是:终于有人把这件事从“工具层”往“流程层”去想了。市面上大多数讨论还停留在“用A…

2026/10/14 23:29:06 阅读更多 →
国产AI算力地基:DS-IR与量化工具链深度解析

国产AI算力地基:DS-IR与量化工具链深度解析

1. 项目概述:为什么说“国产算力的地基”比模型本身更值得致敬“致敬,DeepSeek 最新开源的不是模型,是国产算力的地基”——这句话在技术圈刷屏时,我正调试一套在国产AI加速卡上跑不动的推理服务。看到标题第一反应不是点开链接&a…

2026/10/14 23:29:06 阅读更多 →
匿名化数据集生成与测试数据管理:从合规到工程落地

匿名化数据集生成与测试数据管理:从合规到工程落地

如果你所在的研发团队还停留在“从生产库拷贝一份数据,导入测试环境”的操作方式,那么这篇内容大概率能帮你省下未来几个月的加班时间。测试数据管理工具这几年从一个“锦上添花”的辅助设施,逐渐变成了很多团队的刚需,核心原因很…

2026/10/14 23:29:06 阅读更多 →
基于UDP广播的局域网匿名聊天APP:零配置、零服务端、打开即用

基于UDP广播的局域网匿名聊天APP:零配置、零服务端、打开即用

1. 为什么我要做一款局域网匿名聊天工具事情的起因很简单。前阵子参加一个线下技术沙龙,几十号人坐在同一个会议室里,主持人想让大家做个自我介绍、交换联系方式,结果现场一片尴尬——没有群二维码,没有公共WiFi密码,谁…

2026/10/14 23:29:06 阅读更多 →
哈弗枭龙MAX与宋PLUS DM-i深度对比:插混SUV怎么选

哈弗枭龙MAX与宋PLUS DM-i深度对比:插混SUV怎么选

最近后台问得最多的插混SUV对比,就是哈弗枭龙和比亚迪宋PLUS DM-i这两台。一个是长城押上Hi4电四驱技术的转型之作,另一个是比亚迪DM-i超级混动的销量担当,售价都在十二三万到十七八万之间,看着参数表都差不多,但实际体…

2026/10/14 23:29:06 阅读更多 →
秋招网络岗VLAN面试全攻略:从原理到配置与排障

秋招网络岗VLAN面试全攻略:从原理到配置与排障

1. 秋招网络岗为什么绕不开VLAN这个知识点每年秋招,网络工程师、运维开发、云计算相关岗位的笔试面试里,VLAN出现的频率高得离谱。我翻过近几年的面经,也跟几个做过校招面试官的朋友聊过,发现一个规律:VLAN本身的概念不…

2026/10/14 23:28:03 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →