WIP HWP ABI 판 확인 (관리자 지시 10/20, 28 지적 - 옛 .so 에 새 인수를 넘기면 힙 손상): Rust jj_rhwp_abi_version() = 2 · Dart 는 처음 HWP 를 열 때 꼭 있어야 할 함수 7개와 판을 보고 다르면 앱 안 보기를 쓰지 않고 다른 앱으로 · check_apk 가 APK 안 libjj_rhwp.so 의 내보낸 기호를 NDK llvm-nm 으로 보고 없으면 실패 (986 의 옛 .so 를 잡음) · 시험 hwp_abi_test (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>hwp2
parent
0d2b14ce68
commit
76fd2f3cbf
|
|
@ -16,11 +16,33 @@ class RhwpEngine implements HwpPdfEngine {
|
|||
/// 관리자 판단 (10/20): 한글 글꼴 컴포넌트를 받으면 그 폴더를 앞에 더한다 (아직 없음)
|
||||
static const fontDirs = ['/system/fonts'];
|
||||
|
||||
/// 라이브러리를 읽을 수 있는지 (없으면 다른 앱으로 연다)
|
||||
static bool get available {
|
||||
/// 이 Dart 가 기대하는 C ABI 의 판 (tool/rhwp_ffi 의 JJ_RHWP_ABI_VERSION 과 같아야 함)
|
||||
static const abiVersion = 2;
|
||||
|
||||
/// 꼭 있어야 하는 C 함수
|
||||
static const requiredSymbols = [
|
||||
'jj_rhwp_abi_version', 'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_cancel', 'jj_rhwp_close', 'jj_rhwp_free', //
|
||||
];
|
||||
|
||||
static bool? _available;
|
||||
|
||||
/// 라이브러리를 읽을 수 있고 ABI 가 맞는지 (아니면 다른 앱으로 연다). 처음 HWP 를 열 때 한 번 본다.
|
||||
/// 관리자 지시 (10/20, 28 지적): 옛 .so (판 1 - code 칸 · cancel 없음) 에 새 인수를 넘기면 힙이 망가지므로, 기호와 판이 맞을 때만
|
||||
static bool get available => _available ??= () {
|
||||
try {
|
||||
final lib = DynamicLibrary.open(libName);
|
||||
return abiCompatible(
|
||||
has: lib.providesSymbol, abi: () => lib.lookupFunction<Int32 Function(), int Function()>('jj_rhwp_abi_version')());
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
/// [requiredSymbols] 가 모두 있고 [abi] 가 [abiVersion] 인지 (시험용으로 따로 - 기호를 찾다 · 부르다 실패하면 false)
|
||||
static bool abiCompatible({required bool Function(String name) has, required int Function() abi}) {
|
||||
try {
|
||||
DynamicLibrary.open(libName);
|
||||
return true;
|
||||
if (!requiredSymbols.every(has)) return false;
|
||||
return abi() == abiVersion;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jj_mkvmaker/platform/android/hwp_pdf_ffi.dart';
|
||||
|
||||
/// 관리자 지시 (10/20, 28 지적): 옛 libjj_rhwp.so (ABI 판 1 - jj_rhwp_cancel · jj_rhwp_abi_version 없음, 오류 code 칸 없음) 이면
|
||||
/// 앱 안 보기를 쓰지 않고 다른 앱으로 연다 (새 인수를 넘기면 힙이 망가진다)
|
||||
void main() {
|
||||
const v1 = {'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_close', 'jj_rhwp_free', 'jj_rhwp_version'};
|
||||
final v2 = {...v1, 'jj_rhwp_cancel', 'jj_rhwp_abi_version'};
|
||||
|
||||
test('옛 .so (기호 없음) · 판이 다름 · 부르다 실패 → 쓰지 않음, 맞으면 씀', () {
|
||||
expect(RhwpEngine.abiCompatible(has: v1.contains, abi: () => throw ArgumentError('no symbol')), isFalse, reason: '판 1');
|
||||
expect(RhwpEngine.abiCompatible(has: {...v1, 'jj_rhwp_abi_version'}.contains, abi: () => 2), isFalse, reason: 'cancel 없음');
|
||||
expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => 1), isFalse, reason: '판이 다름');
|
||||
expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => 3), isFalse, reason: '더 새 판도');
|
||||
expect(RhwpEngine.abiCompatible(has: (_) => throw StateError('x'), abi: () => 2), isFalse);
|
||||
expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => RhwpEngine.abiVersion), isTrue);
|
||||
});
|
||||
|
||||
test('Dart 의 판 = tool/rhwp_ffi 의 JJ_RHWP_ABI_VERSION · 꼭 있어야 하는 함수는 lib.rs 에 모두', () {
|
||||
final rs = File('tool/rhwp_ffi/src/lib.rs').readAsStringSync();
|
||||
expect(rs, contains('pub const JJ_RHWP_ABI_VERSION: c_int = ${RhwpEngine.abiVersion};'));
|
||||
for (final s in RhwpEngine.requiredSymbols) {
|
||||
expect(rs, contains('pub extern "C" fn $s('), reason: s);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
# 새 버전 확인도 "최신과 같음" 이 된다. 시험판은 flutter build apk --build-number 9xx 로 빌드한다. 정식 판 APK 는 -Release.
|
||||
# - 순번 900 이상의 v 태그 (시험판 Pre-release, 예: v2026.10.18_977) 는 정식 판으로 보지 않는다 (관리자 지시 10/19, 28 지적:
|
||||
# 시험판에 태그를 단 뒤 같은 APK 를 다시 검사하면 "정식 판 번호와 같음" 으로 거짓 실패). 정식 판 (-Release) 인데 순번이 900 이상이면 실패.
|
||||
# - HWP 보기 (관리자 지시 10/20, 28 지적): APK 안 libjj_rhwp.so 가 앱의 Dart 와 같은 C ABI 인지 - 내보낸 기호 (NDK llvm-nm) 에
|
||||
# jj_rhwp_cancel · jj_rhwp_abi_version 등이 모두 있어야 한다 (옛 .so 는 이름 · 목록만으로는 못 가린다).
|
||||
# 사용: powershell -ExecutionPolicy Bypass -File tool\check_apk.ps1 -Apk build\app\outputs\flutter-apk\app-release.apk [-Release]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Apk,
|
||||
|
|
@ -41,7 +43,7 @@ if (-not $Libs) { $Libs = Join-Path $app 'android\app\third_party_libs.txt' }
|
|||
$listed = @(Get-Content $Libs | ForEach-Object { $_.Trim() } | Where-Object { $_ -and -not $_.StartsWith('#') })
|
||||
# third_party 에 있는 파일 (목록 밖이면 APK 에 없어야 함)
|
||||
$tp = @()
|
||||
foreach ($d in 'third_party\rsync\android', 'third_party\sdcpp\android', 'third_party\tor\android', 'third_party\7zip\android') {
|
||||
foreach ($d in 'third_party\rsync\android', 'third_party\sdcpp\android', 'third_party\tor\android', 'third_party\7zip\android', 'third_party\rhwp\android') {
|
||||
$dir = Join-Path $app $d
|
||||
if (Test-Path $dir) { $tp += @(Get-ChildItem $dir -Recurse -Filter *.so | ForEach-Object { $_.Name }) }
|
||||
}
|
||||
|
|
@ -50,6 +52,13 @@ try {
|
|||
$libsIn = @($zip.Entries | Where-Object { $_.FullName -like 'lib/arm64-v8a/*.so' } | ForEach-Object { $_.Name })
|
||||
$dexes = @($zip.Entries | Where-Object { $_.FullName -match '^classes\d*\.dex$' })
|
||||
if ($dexes.Count -eq 0) { throw "dex 가 없습니다: $Apk" }
|
||||
# HWP 보기 라이브러리는 꺼내 두고 아래에서 기호를 본다
|
||||
$rhwpSo = $null
|
||||
$rhwpEntry = $zip.Entries | Where-Object { $_.FullName -eq 'lib/arm64-v8a/libjj_rhwp.so' } | Select-Object -First 1
|
||||
if ($rhwpEntry) {
|
||||
$rhwpSo = Join-Path ([System.IO.Path]::GetTempPath()) ("jj_check_rhwp_{0}.so" -f [guid]::NewGuid().ToString('N'))
|
||||
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($rhwpEntry, $rhwpSo)
|
||||
}
|
||||
$found = $null
|
||||
foreach ($e in $dexes) {
|
||||
$s = $e.Open()
|
||||
|
|
@ -61,6 +70,21 @@ try {
|
|||
$zip.Dispose()
|
||||
}
|
||||
$bad = @()
|
||||
# HWP 보기: libjj_rhwp.so 의 내보낸 기호 (lib/platform/android/hwp_pdf_ffi.dart 의 requiredSymbols 와 같게)
|
||||
$rhwpNote = ''
|
||||
if ($rhwpSo) {
|
||||
try {
|
||||
$nm = Get-ChildItem (Join-Path $app '..\tools\android-sdk\ndk') -Recurse -Filter llvm-nm.exe -ErrorAction SilentlyContinue | Select-Object -Last 1
|
||||
if (-not $nm) { throw 'llvm-nm.exe 를 찾지 못했습니다 (tools\android-sdk\ndk)' }
|
||||
$syms = @(& $nm.FullName -D --defined-only $rhwpSo 2>$null | ForEach-Object { ($_ -split '\s+')[-1] })
|
||||
$need = 'jj_rhwp_abi_version', 'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_cancel', 'jj_rhwp_close', 'jj_rhwp_free'
|
||||
$miss = @($need | Where-Object { $syms -notcontains $_ })
|
||||
if ($miss) { $bad += "libjj_rhwp.so 가 옛 C ABI 입니다 - 없는 함수: $($miss -join ', ') (sh tool/build_rhwp_android.sh 로 다시 빌드)" }
|
||||
else { $rhwpNote = " · libjj_rhwp.so 함수 $($need.Count)개 있음" }
|
||||
} finally {
|
||||
Remove-Item $rhwpSo -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
# 시험판 번호: aapt2 로 versionCode · versionName 을 읽어 정식 판 태그의 번호와 견준다 (build.gradle.kts 와 같은 셈: 년%100 · 월 · 일 · 번호)
|
||||
$aapt = Get-ChildItem (Join-Path $app '..\tools\android-sdk\build-tools') -Recurse -Filter aapt2.exe -ErrorAction SilentlyContinue | Select-Object -Last 1
|
||||
if (-not $aapt -and $env:ANDROID_HOME) { $aapt = Get-ChildItem (Join-Path $env:ANDROID_HOME 'build-tools') -Recurse -Filter aapt2.exe -ErrorAction SilentlyContinue | Select-Object -Last 1 }
|
||||
|
|
@ -90,4 +114,4 @@ if (-not $found) {
|
|||
Write-Host "실패: GeneratedPluginRegistrant 가 dex 에 없습니다 (켜자마자 꺼지는 APK). flutter pub get 뒤 --no-pub 없이 다시 빌드하세요." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host "통과: GeneratedPluginRegistrant 있음 ($found) · third_party 실행 파일 $($listed.Count)개 목록과 같음 · versionCode $code ($vname)$(if ($Release) { ' (정식 판)' } else { ' (시험판 - 정식 판 번호와 다름)' })"
|
||||
Write-Host "통과: GeneratedPluginRegistrant 있음 ($found) · third_party 실행 파일 $($listed.Count)개 목록과 같음$rhwpNote · versionCode $code ($vname)$(if ($Release) { ' (정식 판)' } else { ' (시험판 - 정식 판 번호와 다름)' })"
|
||||
|
|
|
|||
|
|
@ -100,6 +100,16 @@ fn open_kind(msg: &str, with_password: bool) -> c_int {
|
|||
}
|
||||
}
|
||||
|
||||
/// 이 C ABI 의 판 - Dart (lib/platform/android/hwp_pdf_ffi.dart 의 RhwpEngine.abiVersion) 와 같아야 앱 안 보기를 쓴다
|
||||
/// (관리자 지시 10/20, 28 지적: 옛 .so (판 1 - code 칸 · cancel 없음) 에 새 인수를 넘기면 힙이 망가진다).
|
||||
/// 함수의 인수 · 오류 코드를 바꾸면 올린다
|
||||
pub const JJ_RHWP_ABI_VERSION: c_int = 2;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn jj_rhwp_abi_version() -> c_int {
|
||||
JJ_RHWP_ABI_VERSION
|
||||
}
|
||||
|
||||
/// 판 (예: "0.8.7") - 라이선스 화면 · 작업 기록용. jj_rhwp_free 로 푼다
|
||||
#[no_mangle]
|
||||
pub extern "C" fn jj_rhwp_version() -> *mut c_char {
|
||||
|
|
|
|||
Loading…
Reference in New Issue