Fix native candidate and schema list handling#1
Closed
amorphobia wants to merge 1 commit into
Closed
Conversation
- copy candidate data before iterator-owned pointers become invalid - guarantee candidate iterators and schema lists are released - call the correct API for selected switcher schemas
Member
Author
|
让 AI 写了个例子,看看上次 new 出来的 text 地址是不是下次 delete 的地址 probe.cpp#include <cstdint>
#include <cstdio>
#include <cstring>
struct Candidate {
char* text;
char* comment;
void* reserved;
};
struct Iterator {
std::uintptr_t ptr;
int index;
Candidate candidate;
};
static const char* const kTexts[] = { "first", "second", "third" };
static const char* const kComments[] = { "one", "two", "three" };
static constexpr int kCount = sizeof(kTexts) / sizeof(kTexts[0]);
static char* g_last_owned_text = nullptr;
static char* copy_string(const char* source) {
const size_t length = std::strlen(source) + 1;
auto* copy = new char[length];
std::memcpy(copy, source, length);
return copy;
}
extern "C" __declspec(dllexport)
int candidate_begin(Iterator* iterator) {
std::memset(iterator, 0, sizeof(*iterator));
iterator->index = -1;
return 1;
}
extern "C" __declspec(dllexport)
int candidate_next(Iterator* iterator) {
++iterator->index;
if (iterator->index >= kCount) {
return 0;
}
// Mirrors librime: the iterator owns and replaces these pointers.
std::fprintf(
stderr,
"[next] deleting text=%p, comment=%p\n",
static_cast<void*>(iterator->candidate.text),
static_cast<void*>(iterator->candidate.comment));
std::fflush(stderr);
delete[] iterator->candidate.text;
delete[] iterator->candidate.comment;
iterator->candidate.text = copy_string(kTexts[iterator->index]);
iterator->candidate.comment = copy_string(kComments[iterator->index]);
iterator->candidate.reserved = nullptr;
g_last_owned_text = iterator->candidate.text;
std::fprintf(
stderr,
"[next] allocated text=%p\n",
static_cast<void*>(iterator->candidate.text));
std::fflush(stderr);
return 1;
}
extern "C" __declspec(dllexport)
void candidate_end(Iterator* iterator) {
std::fprintf(
stderr,
"[end] deleting text=%p, comment=%p\n",
static_cast<void*>(iterator->candidate.text),
static_cast<void*>(iterator->candidate.comment));
std::fflush(stderr);
delete[] iterator->candidate.text;
delete[] iterator->candidate.comment;
std::memset(iterator, 0, sizeof(*iterator));
g_last_owned_text = nullptr;
}
extern "C" __declspec(dllexport)
void* candidate_last_owned_text() {
return g_last_owned_text;
}probe.csproj<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Update="probe.dll" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>Program.csusing System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Candidate
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string? Text;
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string? Comment;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct Iterator
{
public UIntPtr Ptr;
public int Index;
[MarshalAs(UnmanagedType.Struct)]
public Candidate Candidate;
}
internal static class Native
{
[DllImport("probe", CallingConvention = CallingConvention.Cdecl)]
internal static extern int candidate_begin(ref Iterator iterator);
[DllImport("probe", CallingConvention = CallingConvention.Cdecl)]
internal static extern int candidate_next(ref Iterator iterator);
[DllImport("probe", CallingConvention = CallingConvention.Cdecl)]
internal static extern void candidate_end(ref Iterator iterator);
[DllImport("probe", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr candidate_last_owned_text();
}
internal static class Program
{
private static void Main()
{
var iterator = default(Iterator);
if (Native.candidate_begin(ref iterator) == 0)
{
throw new InvalidOperationException("begin failed");
}
try
{
Console.WriteLine("Calling first next...");
Native.candidate_next(ref iterator);
Console.WriteLine($"Managed text: {iterator.Candidate.Text}");
Console.WriteLine(
$"Native-owned pointer: 0x{Native.candidate_last_owned_text().ToInt64():X}");
Console.WriteLine();
Console.WriteLine("Calling second next...");
Native.candidate_next(ref iterator);
Console.WriteLine($"Managed text: {iterator.Candidate.Text}");
Console.WriteLine(
$"Native-owned pointer: 0x{Native.candidate_last_owned_text().ToInt64():X}");
}
finally
{
Native.candidate_end(ref iterator);
}
}
}编译 DLL cl /LD /EHsc probe.cpp /Fe:probe.dll运行 dotnet run我的结果 C++ 一开始 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
在跨 DLL 调用 librime 候选迭代 API 时,
RimeCandidateListIterator直接使用包含托管字符串字段的RimeCandidate接收原生结构。候选项中的字符串指针由 librime 管理,并可能在迭代过程中被替换或在迭代结束后失效。直接依赖自动封送会导致生命周期边界不明确,也可能在原生指针失效后读取数据。
此外,Switcher 的 Schema 列表处理存在释放路径不完整和 selected list 调用错误的问题。
修改内容
NativeRimeCandidate。try/finally保证始终调用CandidateListEnd。try/finally保证 Switcher Schema 列表始终通过SchemaListDestroy释放。GetSelectedSchemaList错误调用GetAvailableSchemaList的问题。影响
GetSelectedSchemaList现在返回正确的已选 Schema 列表。RimeCandidateListIterator.Candidate从公共字段调整为只读属性。现有源码读取方式不变,但已经针对旧程序集编译的消费者需要重新编译。