VB.NET Core调用C#.NET Core DLL非托管函数程序闪退问题排查
问题核心原因
程序无征兆闪退是以下几个问题叠加导致的,和非托管调用的边界规则直接相关:
- 普通.NET Core编译产出的C#类库是托管DLL,不支持直接被
DllImport调用。如果你没有用NativeAOT将C#项目发布为原生DLL,或者没有通过DNNE(.NET Native Exports)生成原生导出层,强行P/Invoke托管DLL会直接触发进程级崩溃,且不会抛出可捕获的托管异常。 [UnmanagedCallersOnly]特性默认不会按C#方法名导出原生入口点,未显式指定EntryPoint时,导出的函数名会被编译器修饰,VB侧DllImport找不到匹配的入口点时,若调用约定不匹配触发栈错位,会直接终止进程,无法进入你写的EntryPointNotFoundException捕获块。- 两侧调用约定未显式对齐,32位模式下栈布局不一致会直接导致内存访问违规闪退。
- 你写的VB代码存在明显问题:
Catch块中定义的异常变量是ex,块内却调用了不存在的e.ToString();另外代码末尾的End语句会强制直接终止进程,不会执行任何资源回收逻辑;你通过Marshal.StringToHGlobalAnsi分配的非托管内存从未释放,存在内存泄漏问题。 - 标记
[UnmanagedCallersOnly]的方法不允许托管异常穿越非托管边界,一旦异常漏出方法边界,进程会被立即终止,你在C#方法内写的try/catch只能捕获方法内部处理的异常,无法拦截非托管层面的栈损坏、内存访问错误。
排查定位步骤
按以下顺序排查可以快速定位根因:
- 验证DLL导出函数:打开VS开发者命令提示符,执行
dumpbin /exports TestLibrary1NE.dll查看导出表,如果没有名为AddNE的导出项,说明C#侧未正确配置导出。 - 启用混合模式调试:在VB项目属性-调试页勾选「启用本机代码调试」,再次触发调用时VS会停在具体的崩溃位置,给出访问冲突、栈损坏、入口点找不到的明确错误信息,不会直接无征兆退出。
- 检查平台目标一致性:确认C# DLL和VB项目的平台目标完全统一,不能一个是x86一个是x64,禁止使用Any CPU配置(NativeAOT/DNNE生成的原生DLL是架构绑定的)。
- 确认DLL部署路径:确保
TestLibrary1NE.dll放在VB编译输出的EXE同级目录下,不要放在系统目录或其他不被P/Invoke搜索的路径。
修复方案
第一步:修正C#导出函数代码
注意必须将C#项目配置为NativeAOT发布,或引入DNNE包生成原生导出层,普通编译产出的托管DLL无法被直接P/Invoke调用。
using System; using System.Runtime.InteropServices; namespace TestLibrary1 { public class Class1 { // 显式指定导出入口点名、调用约定,确保和VB侧完全对齐 [UnmanagedCallersOnly(EntryPoint = nameof(AddNE), CallingConvention = CallingConvention.StdCall)] public static IntPtr AddNE(IntPtr a) { // 提前做参数校验,禁止异常漏出非托管边界 if (a == IntPtr.Zero) return IntPtr.Zero; return a; } } }
C#项目配置(csproj文件)需要添加以下配置才能正确生成原生导出:
<PropertyGroup> <OutputType>Library</OutputType> <PublishAot>true</PublishAot> <!-- NativeAOT发布原生DLL --> <TargetFramework>net8.0</TargetFramework> <!-- 使用7.0以上版本的.NET才支持NativeAOT --> <Platforms>x64;x86</Platforms> </PropertyGroup>
第二步:修正VB侧调用代码
Imports System.Runtime.InteropServices Public Class Form1 Private Const DllName As String = "TestLibrary1NE.dll" ' 显式指定入口点、调用约定、字符集,和C#侧完全对齐 <DllImport(DllName, EntryPoint:="AddNE", CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Ansi)> Private Shared Function AddNE(ByVal a As IntPtr) As IntPtr End Function Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click Dim ptrAddress As IntPtr = IntPtr.Zero Try ptrAddress = Marshal.StringToHGlobalAnsi(txtAddress.Text.Trim()) Dim ptrResponse As IntPtr = AddNE(ptrAddress) If ptrResponse = IntPtr.Zero Then MessageBox.Show("函数调用返回空指针") Return End If Dim strResponse As String = Marshal.PtrToStringAnsi(ptrResponse) MessageBox.Show(strResponse) Catch ex As Exception ' 捕获所有异常方便调试,不要只捕获入口点找不到 MessageBox.Show($"调用失败:{ex.Message}") Finally ' 释放手动分配的非托管内存,避免泄漏 If ptrAddress <> IntPtr.Zero Then Marshal.FreeHGlobal(ptrAddress) End If End Try ' 删除原代码中粗暴终止进程的End语句 End Sub End Class
额外注意事项
- 如果后续你修改C#函数逻辑返回DLL内部分配的非托管内存,必须在C#侧同步提供对应的内存释放函数,不要在VB侧释放非当前进程分配的内存,否则会触发堆损坏。
- 不要在标记
[UnmanagedCallersOnly]的方法内传递托管对象引用,所有跨边界传递的参数必须是可 blittable 的值类型或者非托管指针。 - 发布时如果用SelfContained模式部署NativeAOT生成的DLL,不需要目标机器安装.NET运行时;如果用框架依赖模式,需要确保目标机器安装了对应版本的.NET运行时。
内容的提问来源于stack exchange,提问作者Raj
相关产品推荐
相关产品推荐

