[Dart] flutter(dart)에서 dll 불러오기

Flutter|2022. 3. 24. 15:48

flutter에서 사용할 dll 만들기는 이쪽을 참고.

 

[C/C++] dll 작성 (window)

visual studio 2019사용, 환경은 window 64-bit. 처음엔 gcc 쓰려고 했는데 gcc로 컴파일한 걸 다른 언어로 작성한 프로그램에서 실행하자니 오류가 나서...일단 간단하고 편하게 하기 위해 visual studio 2019 기

temphi20.tistory.com

 

크게 어렵지 않다.

1. pub.dev에서 ffi package 다운로드

2. project root folder에 준비된 dll을 추가

3. 아래 소스 코드처럼 library와 함수를 불러온다.

final DynamicLibrary func = DynamicLibrary.open('func.dll');
final int Function(int n) intFunc =
    func.lookup<NativeFunction<Int32 Function(Int32)>>('int_func').asFunction();

NativeFunction class를 살펴보녀 NativeFunction<T extends Function>라고 되어 있는데, T는 dll에서 return하는 type이고 Funtion은 Function(NativeType)의 형식으로 사용해주면 된다. 괄호 안에 들어가는 것은 당연히 dll에 전달할 argument.

Dart코드로 정의하는 함수 타입 역시 NativeFunction에 적은 NativeType과 맞춰 작성해준다.

NativeFunction에는 Dart의 int, double, String 등이 들어갈 수 없으니 주의하자. (어차피 오류나서 못 쓴다.)

C/C++과 Dart 간의 type mapping(Dart type ↔ NativeType class)은 아래 링크를 참고.

 

[Dart] dart:ffi NativeType 관련 정리 및 Pointer 사용법 (추후 추가)

아직 이것저것 실험해보는 중이라 부족할 수 있다. Flutter 2.10.3, window10 64bit 환경에서 테스트. 1. int inteager 관련은 종류가 많다. signed: Int8, Int16, Int32, Int64 unsigned: Uint8, Uint16, Uint32,..

temphi20.tistory.com

 

+위는 윈도우 환경에서 테스트한 코드이며, 플랫폼에 다라 사용 방법이 조금씩 다른 모양이다. 공식 문서 참고.

 

Binding to native code using dart:ffi

To use C code in your Flutter program, use the dart:ffi library (currently in preview).

flutter-ko.dev

 

테스트 중 겪었던 몇 가지 에러 및 주의사항들:

  • Failed to load dynamic library (error code 193): 64bit 환경인데 32bit로 dll을 컴파일해서 생긴 오류였다. 몇 비트인지 확인 제대로 할 것.
  • Failed to load dynamic library (error code 126): dll을 위한 함수 작성시 extern "C"를 사용하지 않아 생긴 오류였다. 같은 C++로 테스트했을 땐 문제가 없었는데, dart로 돌리니 문제가 생겨서 찾기 힘들었음...
  • dll의 내용이 바뀔 경우, hot reload로는 update가 되지 않는다. 껐다가 다시 실행하면 적용된다.

댓글()

[Dart] dart:ffi NativeType 관련 정리 및 Pointer 사용법 (추후 추가)

Flutter|2022. 3. 24. 15:48

아직 이것저것 실험해보는 중이라 부족할 수 있다.

Flutter 2.10.3, window10 64bit 환경에서 테스트.

 

1. int

inteager 관련은 종류가 많다.

  • signed: Int8, Int16, Int32, Int64
  • unsigned: Uint8, Uint16, Uint32, Uint64

ffi/native_type.dart 문서 내에 적힌 설명을 읽어보면, 뒤의 숫자는 각각 몇 bit짜리 정수(C 기준)에 해당하는지 알 수 있다. 테스트로 사용한 dll에서는 int형을 사용하였으므로, Int32로 테스트. bit 수 맞추지 않아도 오류는 나지 않는데...왠지 예상치 못한 동작이 있을 것 같으니 주의하는 편이 좋을 것 같다.

 

2. double, float, bool, void

얘네는 크게 어렵지 않다. 이름도 클래스라 대문자로 시작할 뿐이지, 똑같이 Double, Float, Bool, Void.

 

3. char

Utf8과 Utf16이 있다. Utf8은 char에, Utf16은 wchar_t에 대응하는 모양이다.

char*를 사용하고 싶다면 Pointer<Utf8>을 사용하라고 ffi/lib/src/utf8.dart에 친절하게 적혀 있다. Utf16의 경우에도 마찬가지. 다만 C++의 string class는 무엇을 사용해야 하는지 아직 찾지 못했다.

/// The contents of a native zero-terminated array of UTF-8 code units.
///
/// The Utf8 type itself has no functionality, it's only intended to be used
/// through a `Pointer<Utf8>` representing the entire array. This pointer is
/// the equivalent of a char pointer (`const char*`) in C code.
class Utf8 extends Opaque {}

 

4. 포인터

각종 포인터는 Pointer와 대응한다. 이와 관련한 이야기는 아래에 추가 후술.

메모리 할당 방법 때문에 삽질을 조금 했는데, malloc과 calloc이 구현되어 있었다. package:ffi/src/allocation.dart를 참고하자.

C/C++에서 사용하던 그 malloc 및 calloc과 비슷한 기능을 하고, 비슷한 차이점이 있다.

final Pointer<Int32> int_ptr = calloc.allocate(4);
final Pointer<Utf8> str_ptr = calloc.allocate(32);

이런식으로 할당해주면 된다.

 

5. Pointer class 값 할당

이거때문에 한참 찾아 헤멨는데...

  • NativeType이 숫자(Int32, Double, etc...)일 경우: asTypedList를 이용해 해당 포인터에 대응하는 NativeType List로 바꿔준 후 fillRange 함수를 이용한다.
final Pointer<Int32> ptr = calloc.allocate(16); // malloc.allocate(4);
ptr.asTypedList(8).fillRange(0, 2, 10);

 

  • NativeType이 문자(Utf8, Utf16)일 경우: fillRange가 제공되지 않는다! 대신 다른 방법을 찾았다. 아래와 같다. 물론 이 경우 final Pointer 형식의 사용이 불가능해진다.
Pointer<Utf8> char = calloc.allocate(32);
char = 'test'.toNativeUtf8();

아래처럼 initialize해도 가능하다.

final Pointer<Utf8> ptr = 'hello'.toNativeUtf8();

 

댓글()

[Flutter] ipa build Xcode segmentfault 11 문제

Flutter|2022. 3. 22. 19:21

Xcode 업데이트 후 flutter build ipa를 하면 Xcode build 도중 segmentfault 11와 함께 중단되는 현상 발생.

로그를 읽어보면 DKImagePickerController라는 패키지에서 발생하는 문제 같으며... 직접 작성하는 코드에서 일어난 문제가 아니라 그런지 해결 방법이 없다. (file_picker에서 사용하는 패키지 같다.)

 

1. file_picker가 필수가 아닌 프로젝트일 경우

그냥 패키지 사용을 포기한다.

만약 패키지를 지웠는데도 pod install 후 DKImagePickerController가 계속 설치된다면, 아래 두 방법 중 하나로 install한다.

  • 방법1. pod install 옵션
pod install --clean-install

cache를 지우는 옵션이 아니라 cache를 무시하는 옵션이기 때문에, pod install 할 때마다 옵션을 계속 달아줘야 한다.

  • 방법2. cache clear
pod cache clean --all
pod install

 

2. file_picker가 필수인프로젝트일 경우...

Xcode 버전을 낮춰준다. 해당 문제는 13.3에서 발생하고 있으므로, 13.2로 낮춰 실행한 결과 문제 없이 build되었다.

13.2 버전의 Xcode를 다운받은 후, Xcode → [Preferences] → [Locations]로 가서 Command Line Tools에 지정되어 있는 Xcode 버전을 13.2로 바꾼다. 바꾸지 않으면 flutter 명령어는 13.3 버전을 인식한다.

이후 flutter build ipa를 실행하면 해당 문제는 사라진다.

 

+ flutter doctor 명령어로 버전이 바뀌었는지 확인할 수 있다.

댓글()

[Flutter] ipa build 및 browser를 통한 ipa 설치 설정 방법

Flutter|2022. 3. 17. 14:20

apk build에 이은 ipa build 정리.

apple developer 및 기기등록 등이 모두 되어있다고 가정하고 진행한다. (검색하면 많이 나온다.)

 

1. flutter build ipa

반드시 xCode -> [Product] -> [Archive]가 아닌 flutter build ipa 명령어를 사용하여 Runner.xcarchive를 만든다. xCode로 빌드할 경우 이전 빌드 내역 그대로 빌드되니 주의하자.

 

2. Archives

xCode -> [Window] -> [Organizer]로 들어가 Archives를 켠다. 생성된 Runner.xcarchive 파일을 실행하는 방법으로도 켜진다. Runner.xcarchive 파일을 실행하는 방법으로 켜면... 같은 Runner.xcarchive 파일이라도 Archives list에 중복으로 뜨니 이 점 주의.

Creation Date와 Version으로 build하고자 하는 archive가 맞는지 확인하고 선택한 후 [Distribute App]을 누른다.

 

3. Distribute App

- 추출 방법 선택: release test용이라면 Ad Hoc을 선택한다.

- 옵션 선택: Include manifest for over-the-air installation를 반드시 선택해야 한다. App Thinning은 None으로 둬도 큰 문제가 없었다.

- manifest information: 이름과 3개의 url을 입력한다. url은 추후 변경이 가능하므로 아무거나 넣어도 상관 없다.

이후 서명까지 완료하면 자동으로 ipa가 추출된다.

 

4. 추출 후 배포

1. manifest.plist 파일을 수정한다. 형식은 아래와 같다.

item 0의 url은 ipa가 저장된 경로(후술), item 1과 item 2의 url은 앱 아이콘 이미지가 저장된 경로(후술).

ipa 및 이미지는 dropbox를 많이 이용한다. 추출한 ipa 파일(manifest.plist 파일과 같은 경로 혹은 Apps directory 아래에 있다.)과 이미지 파일을 dropbox에 저장한 후 그 링크를 사용하면 된다. 이때 dropbox link는 https://www.dropbox.com/s/...에서 https://dl.dropboxusercontent/s/...로 변경한다. 추후 html 파일에 사용할 link 역시 https://dl.dropboxusercontent/s/...로 변경하여 사용한다.

2. 수정한 manifest.plist를 dropbox에 올린다.

3. 아래와 같은 html 파일을 만든다. link를 이용하는 것이기 때문에 꼭 똑같이 할 필요는 없지 않을까 싶다.

<!doctype html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <title>IPA 다운로드</title>
 </head>
 <body>
  <ul>
   <li><a href="itms-services://?action=download-manifest&url=https://dl.dropboxusercontent.com/s/.../manifest.plist">다운로드</a></li>
  </ul>
 </body>
</html>

html file에 들어가는 링크는 manifest.plist의 링크이다.

 

호스팅까지 완료하면 된다. dropbox에 html 파일을 올려도 되긴 한데, html 양식으로 보이지 않거나 링크 연결이 안 되는 문제가 생기기도 해서 페이지 하나 호스팅하는 쪽이 더 안정적이고 확실한 것 같다.

 

manifest.plist link를 이용하기 때문에, ipa 업데이트 시 html을 바꿀 필요 없이 dropbox에 같은 이름으로 업로드만 하면 된다. 만약 ipa url이 바뀐다면 manifest.plist만 수정해주면 왼다.

댓글()

[Flutter] class operator override

Flutter|2021. 12. 17. 15:20

Dart는 class에서 연산자를 재정의할 수 있는 모양이다. Overriding 방법은 다른 함수를 overriding할 때와 비슷하지만, 함수명 대신 operator + 등과 같이 써주면 된다. == 연산자의 경우, argument에 covariant를 붙여준 후 hashCode도 override해줘야만 한다. 아래 예시에서 override toString()은 결과 출력을 위해 추가했다.

 

예시:

class Class {
  final String id;
  final DateTime date;
  final num number;

  const Class({
    this.id = "",
    required this.date,
    this.number = 0,
  });

  @override
  int get hashCode => date.hashCode;

  @override
  bool operator ==(covariant Class other) => date.compareTo(other.date) == 0;

  bool operator >(Class other) => date.compareTo(other.date) > 0;
  bool operator <(Class other) => date.compareTo(other.date) < 0;
  bool operator >=(Class other) => date.compareTo(other.date) >= 0;
  bool operator <=(Class other) => date.compareTo(other.date) <= 0;

  Class operator +(Class other) => Class(
        id: id,
        date: DateTime.now(),
        number: number + other.number,
      );
  Class operator -(Class other) => Class(
        id: id,
        date: DateTime.now(),
        number: number - other.number,
      );
  Class operator -() => Class(
        id: id,
        date: DateTime.now(),
        number: -number,
      );
  Class operator *(Class other) => Class(
        id: id,
        date: DateTime.now(),
        number: number * other.number,
      );
  Class operator /(Class other) => Class(
        id: id,
        date: DateTime.now(),
        number: number / other.number,
      );
  Class operator ~/(Class other) => Class(
        id: id,
        date: DateTime.now(),
        number: number ~/ other.number,
      );
  Class operator %(Class other) => Class(
        id: id,
        date: DateTime.now(),
        number: number % other.number,
      );

  @override
  String toString() {
    return number.toString();
  }
}

 

결과:

void main() {
  Class z = Class(id: "last", date: DateTime(2021, 12, 31), number: 127),
      a = Class(id: "first", date: DateTime(2022, 1, 1), number: 0),
      az = Class(id: "what", date: DateTime(2021, 12, 31), number: 0);

  print([a == a, a == z, a == az, z == az]);
  print([a > a, a > z, a > az, z > az]);
  print([a < a, a < z, a < az, z < az]);
  print([a >= a, a >= z, a >= az, z >= az]);
  print([a <= a, a <= z, a <= az, z <= az]);
  print([a + z, a - z, a * z, a / z, a ~/ z, a % z]);
  print([a += z, a]);
  print([a -= z, a]);
}
[true, false, false, true]
[false, true, true, false]
[false, false, false, false]
[true, true, true, true]
[true, false, false, true]
[127, -127, 0, 0.0, 0, 0]
[127, 127]
[0, 0]
Exited

댓글()

[Flutter] 현재 url 가져오기, 새 창으로 열기

Flutter|2021. 12. 13. 16:23

1. 현재 url 가져오기

- dart:core

final Uri uri = Uri.base;
final String url = Uri.base.toString();

- import 'dart:html'

import 'dart:html' as html;

/// ...

final String url = html.window.location.href;

- import 'dart:js'

import 'dart:js' as js;

/// ...

final String url = js.context['location']['href'];

 

2. 새 창으로 열기

- import 'dart:html'

import 'dart:html' as html;

/// ...

html.window.open(url, 'new tab');

- import 'dart:js'

import 'dart:js' as js;

/// ...

js.context.callMethod('open', [url]);

- import 'package:url_launcher/url_launcher.dart'; (pub.dev)

import 'package:url_launcher/url_launcher.dart';

/// ...

await launch(
	urlParams,
	forceWebView: true,
	forceSafariVC: true,
);

 

※ dart:html과 dart:js는 web only이므로 주의한다.

댓글()

[Flutter] GetX 컨트롤러 관리 관련 간단 정리

Flutter|2021. 11. 16. 15:54

1. 생성

Get.put(dependency, {tag, permanent = false, builder});
Get.lazyPut(builder, {tag, fenix = false});
Get.putAsync(builder, {tag, permanent = false});

가장 자주 쓰는 것은 put. 

 

2. 삭제

Get.delete<S>({tag, force = false});

delete의 경우, Future<bool>을 return한다.

 

3. 접근

Get.find<S>(). ...

위와 같은 형태로 S 안에 있는 함수와 변수에 접근할 수 있다.

 

4. 줄여쓰기

class Controller extends GetxController {
  static Controller get to => Get.find();
  static Future<bool> get del => Get.delete<Controller>();
}

위와 같은 형태로 작성하면, Get.find<Controller>(). ...는 Controller.to. ...로, Get.delete<Controller>();는 Get.del;로 사용할 수 있다.

GetX package에서 get/lib/get_instance/src/extension_instance.dart를 보면 아래와 같이 나와있으므로 참고함.

/// Finds a Instance of the required Class `<S>`(or [tag])
/// In the case of using `Get.create()`, it will generate an Instance
/// each time you call `Get.find()`.
S find<S>({String? tag}) => GetInstance().find<S>(tag: tag);

/// Deletes the `Instance<S>`, cleaning the memory and closes any open
/// controllers (`DisposableInterface`).
///
/// - [tag] Optional "tag" used to register the Instance
/// - [force] Will delete an Instance even if marked as `permanent`.
Future<bool> delete<S>({String? tag, bool force = false}) async =>
    GetInstance().delete<S>(tag: tag, force: force);

댓글()

[Flutter] java.security.NoSuchAlgorithmException: Algorithm HmacPBESHA256 not available

Flutter|2021. 8. 25. 18:38

계속 java.security.NoSuchAlgorithmException: Algorithm HmacPBESHA256 not available라며 apk build가 되지 않는 상황이 발생. (debug는 문제없음)

확인해보니, JDK 버전과 관련이 있는 문제인 것 같다.

 

1. build에 사용되는 binary file의 위치를 확인한다.

flutter doctor -v 명령어를 이용하면 되는데, Android toolchain - Java binary at: 부분에 나온 경로를 확인하면 된다.

 

2. keytool 경로 명시

이후 keytool로 key를 생성할 때 그냥 keytool만 사용하는 것이 아니라 1에서 확인한 경로를 명시하여 keytool을 사용.

내 경우에는

"C:\Program Files\Android\Android Studio\jre\bin\keytool" -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias pckey

로 생성했다.

 

3. 새로 생성한 key에 맞춰 key.properties를 변경하면 완료.

 

문제가 해결된 후, No key with alias 'key' found in keystore ~\key.jks 이런 오류도 떴는데...

전에 쓰던 명령어는 keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key여서, 비교해보니 원래 key라고 쓰던 부분을 pckey라고 썼길래 key.properties에서 keyAlias=keykeyAlias=pckey로 고쳐줬더니 문제 없이 빌드되었다.

 

이후 환경변수도 flutter doctor -v로 확인한 binary 경로로 제대로 바꿔주었다.

댓글()

[Flutter] app(apk) build

Flutter|2021. 8. 18. 17:55

release 버전의 build가 목적이므로, 이미 keystore 등의 준비가 모두 된 상태라 가정한 순서. 사용중인 주요 api로는 firebase가 있으며, flutter와 firebase의 공식 문서를 참고했다.

 

1. keytool

keytool -genkey -v -keystore c:/Users/USER\_NAME/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

Android의 경우에는 이 명령어를 사용하여 키를 생성한다고 하는데,

Warning:
JKS 키 저장소는 고유 형식을 사용합니다. "keytool -importkeystore -srckeystore c:/Users/USER_NAME/key.jks -destkeystore c:/Users/USER_NAME/key.jks -deststoretype pkcs12"를 사용하는 산업 표준 형식인 PKCS12로 이전하는 것이 좋습니다.

라는 warning이 떠서...error로 오인하고

keytool -importkeystore -srckeystore c:/Users/USER_NAME/key.jks -destkeystore c:/Users/USER_NAME/key.jks -deststoretype pkcs12

를 시도했는데, 이번엔 -genkey를 쓸 때마다

keytool 오류: java.lang.Exception: 키 쌍이 생성되지 않았으며 <key> 별칭이 존재합니다.
java.lang.Exception: 키 쌍이 생성되지 않았으며 <key> 별칭이 존재합니다.
        at sun.security.tools.keytool.Main.doGenKeyPair(Unknown Source)
        at sun.security.tools.keytool.Main.doCommands(Unknown Source)
        at sun.security.tools.keytool.Main.run(Unknown Source)
        at sun.security.tools.keytool.Main.main(Unknown Source)

가 발생. PKCS12로 이전하라는 말 대로 했을 때

Warning:
"c:/Users/USER_NAME/key.jks"을(를) Non JKS/JCEKS(으)로 이전했습니다. JKS 키 저장소가 "c:/Users/USER_NAME/key.jks.old"(으)로 백업되었습니다.

이런 warning이 떴던 걸 기억해서 해당 경로에 있는 key.jks와 key.jks.old 파일을 모두 삭제 후 시도하니 성공. 나중에 build하고 보니, PKCS12 warning은 무시해도 build와 설치에 지장은 없었다.

 

2. keystore 참조

PROJECT_ROOT/android/key.properties 생성 후

storePassword=PASSWORD
keyPassword=PASSWORD
keyAlias=key
storeFile=C:/Users/USER_NAME/key.jks

저대로 입력하면 끝. 각자 설정했던 keystore의 비밀번호, key의 비밀번호, key.jks가 위치한 경로를 적어주면 된다. keyAlias=key는 그대로 둘 것.

 

3. Gradle PROJECT_ROOT/android/app/build.gradle

  • 앱 서명을 위한 수정
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
  keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

위 코드는 android{ ... } 위에 추가한다.

 

buildTypes {
   release {
       // TODO: release 빌드 버전을 위한 서명 구성을 추가하세요.
       // 현재는 `flutter run --release`가 디버그용 키로 서명되어 동작합니다.
       signingConfig signingConfigs.debug
   }
}

위는 아래와 같이 변경한다.

signingConfigs {
   release {
       keyAlias keystoreProperties['keyAlias']
       keyPassword keystoreProperties['keyPassword']
       storeFile file(keystoreProperties['storeFile'])
       storePassword keystoreProperties['storePassword']
   }
}
buildTypes {
   release {
       signingConfig signingConfigs.release
   }
}

 

  • 난독화와 축소를 위한 수정
android {

    ...

    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            useProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

딱 저대로 추가하면 된다. 물론 이걸 위해서는 proguard-rules.pro의 작성이 필요하다. 이 외에도 다른 최적화를 위한 옵션 변경이 있는 것 같은데, 자세히 알아봐야 할 듯. 앱 빌드 할 때 shrinkResources true를 추가로 작성해보기는 했다.

 

4. Proguard

PROJECT_ROOT/android/app/proguard-rules.pro 생성, 아래와 같은 규칙 추가.

## Flutter wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.**  { *; }
-keep class io.flutter.util.**  { *; }
-keep class io.flutter.view.**  { *; }
-keep class io.flutter.**  { *; }
-keep class io.flutter.plugins.**  { *; }
-dontwarn io.flutter.embedding.**

 

5. Build

  • 앱 번들로 빌드: flutter build appbundle
  • apk로 빌드: flutter build apk --split-per-abi

빌드된 경로(aab, apk 등이 있는 경로)는 build 후 console창에 표시된다. 

 

6. Install

flutter install을 통한 설치가 가능하다고는 하지만, 일반적으로 앱 설치하듯 apk만 옮겨서 바로 설치해도 무방하다.

 

 

Android 앱 출시 준비하기

Flutter 앱을 개발하는 동안, 커멘드 라인에서의 `flutter run`을 실행하거나IDE에 있는 툴바 **Run** 과 **Debug**를 선택하여 앱을 테스트할 수 있습니다. Flutter는 기본적으로 앱의 _debug_ 버전을 빌드합니

flutter-ko.dev

 

Firebase 출시 체크리스트

이 문서에는 Firebase 앱을 출시하기 전에 고려해야 할 사항의 체크리스트가 있습니다. 연결된 앱 업데이트 Android: Firebase Console(OAuth 클라이언트 ID용)의 프로젝트 설정에 앱의 서명 인증서에 출시 S

firebase.google.com

 

앱 축소, 난독화 및 최적화  |  Android 개발자  |  Android Developers

사용하지 않는 코드와 리소스를 삭제하기 위해 출시 빌드에서 코드를 축소하는 방법을 알아보세요.

developer.android.com

 

댓글()