You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用SendMessage操作RichEdit时调试中断,回调未触发求助

问题描述

尝试使用C#通过SendMessage向RichEdit控件发送EM_STREAMOUT消息,搭配SF_BINARY和SF_HTML标志获取HTML内容,但回调函数从未被调用。调用SendMessage时Visual Studio调试中断且未抛出任何异常,请问原因是什么?如何获取调试信息?

相关代码

private static MemoryStream? memoryStream;

public static class WMConstants
{
    public const uint WM_USER = 0x0400;
    public const uint EM_STREAMOUT = WM_USER + 74;

    public const uint SF_HTML = 0x00100000;
    public const uint SF_BINARY = 0x0008;
    public const uint SF_CLIPBOARD = 0x80000000;
}

[DllImport("user32.dll", CharSet = CharSet.Auto)]
[SuppressMessage("Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "<Pending>")]
private static extern IntPtr SendMessage(IntPtr hwnd, uint msg, uint wParam, ref EDITSTREAM lParam);

internal static string ReadHtml(IntPtr handle)
{
    memoryStream = new MemoryStream();
    var result = string.Empty;

    var editStream = new EDITSTREAM
    {
        dwCookie = IntPtr.Zero,
        pfnCallback = EditStreamProc,
    };

    try
    {
        _ = SendMessage(
            hwnd: handle, 
            msg: WMConstants.EM_STREAMOUT,
            wParam: WMConstants.SF_BINARY | WMConstants.SF_HTML, // | WMConstants.SF_CLIPBOARD,
            lParam: ref editStream);

        memoryStream.Seek(0, SeekOrigin.Begin);
        using (var reader = new StreamReader(memoryStream))
        {
            result = reader.ReadToEnd();
        }
    }
    catch (Exception ex)
    {
        // Log the exception if needed
        throw new InvalidOperationException("Failed to read HTML from the RichTextBox.", ex);
    }
    finally
    {
        memoryStream?.Dispose();
        memoryStream = null;
    }

    return result;
}

public static int EditStreamProc(IntPtr dwCookie, IntPtr pbBuff, int cb, out int pcb)
{
    pcb = cb;
    var buffer = new byte[cb];
    Marshal.Copy(pbBuff, buffer, 0, cb);
    memoryStream!.Write(buffer, 0, cb);
    return 0;
}

[StructLayout(LayoutKind.Sequential)]
private struct EDITSTREAM
{
    public IntPtr dwCookie;
    public uint dwError;
    public EditStreamCallback pfnCallback;
}
原因分析

1. 委托定义缺失/签名不匹配

代码仅声明了EditStreamCallback类型,但未定义符合Win32规范的委托签名,也未指定StdCall调用约定。Win32的EDITSTREAMCALLBACK要求固定调用约定,签名不匹配会导致栈不平衡,触发调试中断,同时RichEdit控件无法正确调用回调函数。

2. RichEdit控件版本不支持HTML导出

SF_HTML标志仅在RichEdit 4.1及以上版本(如msftedit.dll中的RICHEDIT50W控件)支持,若操作的是旧版本控件(如riched32.dll),EM_STREAMOUT会直接失败,不会触发回调。

3. 缺少错误检查逻辑

代码未检查SendMessage的返回值和EDITSTREAM.dwError字段,无法及时发现调用失败的情况。

解决方法

1. 修正委托和结构体定义

添加符合Win32规范的委托定义,确保调用约定正确:

// 匹配Win32 EDITSTREAMCALLBACK的委托定义
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int EditStreamCallback(IntPtr dwCookie, IntPtr pbBuff, int cb, out int pcb);

[StructLayout(LayoutKind.Sequential)]
private struct EDITSTREAM
{
    public IntPtr dwCookie;
    public uint dwError;
    public EditStreamCallback pfnCallback;
}

2. 验证RichEdit控件版本

确认目标控件是支持HTML的版本:

  • 调用GetClassName函数获取控件类名,应为RICHEDIT50W或RICHEDIT20W(需引入user32.dll的GetClassName方法)。
  • 若使用自定义控件,确保加载的是msftedit.dll而非旧版本的riched32.dll。

3. 添加错误检查

在SendMessage调用后添加结果验证:

var sendResult = SendMessage(handle, WMConstants.EM_STREAMOUT, WMConstants.SF_BINARY | WMConstants.SF_HTML, ref editStream);
if (sendResult == IntPtr.Zero || editStream.dwError != 0)
{
    throw new InvalidOperationException($"EM_STREAMOUT调用失败,错误码: {editStream.dwError}");
}
调试方法
  • 启用本机代码调试:在Visual Studio的工具 -> 选项 -> 调试 -> 常规中勾选启用本机代码调试,可捕获P/Invoke时的本机异常,查看具体错误信息。
  • 验证控件句柄有效性:通过GetClassName确认控件类名,确保句柄指向正确的RichEdit控件。
  • 检查回调触发情况:在EditStreamProc开头添加断点,若未触发,说明EM_STREAMOUT未执行成功,重点排查控件版本和参数组合是否正确。

内容的提问来源于stack exchange,提问作者Steve Andrews

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:46:02