블루투스 바코드 스캐너(BI-07) 안드로이드 라이브러리를 델파이에서 연동하는 방법

2017.03.20 17:03

S모전자에서 의뢰한 휴대용 바코드 스캐너와 연동한 내용 공유합니다.

휴대용 바코드 스캐너는 JAR 형태의 안드로이드 라이브러리를 제공합니다.

이 과정을 통해 델파이와 안드로이드 라이브러리 연동하는 내용을 소개합니다.


휴대용 바코드 스캐너 연동



위 영상의 휴대용 바코더 스캐너는 클래식 블루투스 기반으로 연결을 제공합니다.

해당 안드로이드 앱은 델파이로 제작했으며, JAR 파일을 이용해 스캐너와 연동합니다.


JAR 파일 연동과정은 아래와 같습니다.

1, 준비(라이브러리 준비 및 분석)

2, 델파이용 안드로이드 라이브러리 브릿지 파일 생성

3, 델파이 프로젝트에 안드로이드 라이브러리 추가

4, 브릿지 파일을 이용해 안드로이드 라이브러리 연동


기타 안드로이드 라이브러리 연동 시 참고사항

델파이 샘플 프로젝트


준비(라이브러리 준비 및 분석)

대상 장비(Point Mobile BI-07)

Point Mobile: BI-07

링크: http://www.pointmobile.co.kr/item.php?it_id=1381210470&ca_id=401010


라이브러리 준비 및 분석

안드로이드와 iOS 라이브러리를 제공합니다.

안드로이드 라이브러리(jar) :  PM3SDK_connectByAddress.jar

라이브러리 관련 문서 및 샘플 :  BI07PM3SDK_Document.chm

대부분의 라이브러리 제공 시 연동 문서와 샘플코드를 제공합니다.

문서를 잘 숙지하고, 샘플코드를 분석합니다. 대략적으로 소스코드를 파악하되 해당 라이브러리 호출하는 부분은 정확히 숙지해야 합니다.

델파이용 안드로이드 라이브러리 브릿지 파일 생성

델파이에서 안드로이드 라이브러리를 사용하기위해 브릿지 파일을 생성해야 합니다.

브릿지 파일 생성

Java2OP.exe를 이용 델파이 브릿지 파일을 생성합니다. Java2OP는 JAR 파일(*.jar) 또는 Java 소스파일을 호출할 수 있는 델파이 코드를 생성합니다.(참고: 파이어모키에서 외부 라이브러리 연동하기(jar, so, a))

아래 커맨드를 이용해 브릿지 파일(Androidapi.JNI.PM3SDK_BI07.pas)을 생성합니다.
D:\ProjectExt\SamsungElectronics>set path=%PATH%;"c:\Program Files (x86)\Embarcadero\Studio\18.0\bin\converters\java2op"

D:\ProjectExt\SamsungElectronics>java2op -jar .\SDK\PM3SDK_connectByAddress.jar -unit AndroidapiJNI.PM3SDK_BI07
Warning: error opening ReservedWordsOP.txt
Warning: error opening ReservedWordsC.txt
Parsing xml: c:\Program Files (x86)\Embarcadero\Studio\18.0\bin\converters\java2op\bootclasses.xml
Parsing jar: D:\ProjectExt\SamsungElectronics\SDK\PM3SDK_connectByAddress.jar

D:\ProjectExt\SamsungElectronics>

생성된 결과


델파이 프로젝트에 안드로이드 라이브러리 추가

라이브러리 추가

멀티-디바이스 폼 애플리케이션(파이어몽키) 프로젝트에서 대상 플랫폼(Target Platform)을 안드로이드로 설정 후 Libraries 항목에 JAR 파일(안드로이드 라이브러리)을 추가합니다.


델파이 브릿지 파일 추가

해당 라이브러리를 사용하기 위해 델파이 브릿지 파일(Androidapi.JNI.PM3SDK_BI07.pas)을 프로젝트에 추가합니다.

브릿지 파일 확인을 위해 컴파일 합니다.

만약, 컴파일 시 E2029 오류 발생 시 다음을 참고해 조치합니다.

E2029 오류 조치방법

E2029 오류는 예약어를 용도와 다른 곳에서 사용할 경우 발생합니다.

오류가 발생하는 예약어 키워드 앞에 "&"을 추가해 해결합니다.

(예> type -> &type, in -> &in)

(참고: http://tech.devgear.co.kr/delphi_qna/420082)

브릿지 파일을 이용해 안드로이드 라이브러리 연동

라이브러리 연동 문서와 샘플코드를 참고해 기능을 구현했습니다.

그 중 가장 빈번히 사용되는 자바 객체 생성과 처음 구현해본 자바 핸들러 콜백함수 구현, 가장 어렵게 변환했던 자바 오브젝트로 캐스팅된 바이트 배열에서 데이터 가져오기 3가지 내용 소개합니다.

자바 객체 생성

브릿지 파일은 라이브러리에 구현된 클래스 인터페이스가 다음과 같이 구현되어 있습니다.

(BluetoothChatService 클래스)

위와 같이 자바 클래스는 2개의 인터페이스와 1개의 클래스로 정의됩니다.


JBluetoothChatServiceClass 인터페이스는 클래스 함수와 속성, 상수를 제공합니다.(생성하기 전 사용)

JBluetoothChatService 인터페이스는 인스턴스 함수와 속성, 상수를 제공합니다.(생성해서 사용)


델파이에서 자바 객체 생성은 아래와 같이 클래스.JavaClass.Init(파라메터) 형식으로 구현합니다.

var
  FChatService: JBluetoothChatService;
...
  FChatService := TJBluetoothChatService.JavaClass.init(Context, FHandler);

자바 핸들러 콜백함수 구현

바코드 스캐너에서 바코드 데이터 수신은 핸들러의 콜백함수를 통해 받습니다.

그 자바코드는 아래와 같습니다.
private final Handler mHandler = new Handler() {
}

mChatService = new BluetoothChatService(mContext, mHandler);
SendCommand.SendCommandInit(mChatService, mHandler);

델파이에서는 다음과 같이 구현했습니다.

1) 핸들러 콜백함수 정의

type
  THandleMessageEvent = procedure(msg: JMessage) of object;
  TCharServiceHandlerCallback = class(TJavaLocal, JHandler_Callback)
  private
    FOnHandleMessage: THandleMessageEvent;
  public
    function handleMessage(msg: JMessage): Boolean; cdecl;

    property OnHandleMessage: THandleMessageEvent read FOnHandleMessage write FOnHandleMessage;
  end;

주의할 점은, 인터페이스(JHandler_Callback)를 상속받은 메소드(handleMessage)는 public 영역에 구현해야 합니다.

습관적으로 델파이와 같이 private 영역에 선언 시 "Segmentation fault (11)" 오류(잘못된 메모리 영역 참조)로 상당히 고생했습니다.


2) 핸들러, 콜백 객체 생성, 이벤트 연결(OnHandleMessage)

  private
    FHandler: JHandler;
    FCallback: TCharServiceHandler;

  FCallback := TCharServiceHandlerCallback.Create;
  FCallback.OnHandleMessage := ChatServiceHandleMessage;

  Context := TAndroidHelper.Context;
  Looper := TJLooper.JavaClass.getMainLooper;
  FHandler := TJHandler.JavaClass.init(Looper, FCallback);

  FChatService := TJBluetoothChatService.JavaClass.init(Context, FHandler);
  TJSendCommand.JavaClass.SendCommandInit(FChatService, FHandler);

콜백 객체 생성 후 OnHandleMessage 이벤트를 연결해 데이터를 수신합니다.

핸들러 생성 시 콜백 객체를 파라메터로 전달합니다.

핸들러 객체는 BluetoothChatService와 SendCommand 객체 생성 시 파라메터로 전달합니다.


3) 핸들러 콜백과 연결된 이벤트 발생

procedure TPointMobileBluetoothChatService.ChatServiceHandleMessage(
  msg: JMessage);
var
  DeviceName, JData: JString;
begin
  Log.d(msg.what.ToString);
  case msg.what of
  MESSAGE_STATE_CHANGE:
    ProcessHandlerStateChage;
  MESSAGE_BARCODE:
    ProcessHandlerBarcord(msg.obj, msg.arg1);
  MESSAGE_DEVICE_NAME:
    begin
      DeviceName := msg.getData.getString(StringToJString('device_name'));
      Log.d('Device name: ' + JStringToString(DeviceName));
    end;

  // 필요한 이벤트 추가 할 것
  end;
end;

스캐너 라이브러리는 바코드 정보 수신 시 핸들러 콜백과 연결된 이벤트로 데이터를 제공합니다.(이벤트가 발생합니다.)

자바 오브젝트로 캐스팅된 바이트 배열(byte[]) 데이터 가져오기

어디에서도 참고할 내용이 없어 가장 고생했던 내용입니다. 델파이 내부 구조를 분석하고 예측해서 구현했습니다.


자바에서 다음과 같이 오브젝트를 바이트 배열로 치환(캐스팅)하는 코드입니다.

byte[] BarcodeBuff = (byte[]) msg.obj;


델파이에서는 다음과 같이 구현했습니다.

function JObjectToStr(const AJObject: JObject): string;
var
  LObj: ILocalObject;
  ObjID: Pointer;
  JBytes: TJavaArray;
  JData: JString;
begin
  Result := '';

  if JStringToString(AJObject.getClass.getCanonicalName) <> 'byte[]' then
    Exit;

  if AJObject.QueryInterface(ILocalObject, LObj) = 0  then
    ObjID := LObj.GetObjectID
  else
    Exit;

  JBytes := TJavaArray(WrapJNIArray(ObjID, TypeInfo(TJavaArray)));
  JData := TJString.JavaClass.init(JBytes, 0, JBytes.Length);
  Result := JStringToString(JData);
end;

var
  Data: string;
begin
  Data := JObjectToStr(msg.obj);
end;


안드로이드 라이브러리 연동 참고사항

가장 좋은 참고자료는 파이어몽키 소스코드였습니다.


델파이 설치 경로 하위 source 디렉토리에는 파이어몽키 라이브러리 소스코드들이 포함되어 있습니다.(정식 제품의 경우에 한함)


그중 아래 경로의 소스코드들을 참고하시기 바랍니다.(Delphi 10.1 베를린 버전 기준)


안드로이드 SDK 브릿지 파일들

안드로이드 SDK 델파이 브릿지 파일이 포함되어 있습니다. 자바 소스코드의 객체이름을 다음 경로의 소스코드들에서 찾아 필요한 경우 uses 절에 추가 후 사용합니다.

C:\Program Files (x86)\Embarcadero\Studio\18.0\source\rtl\android

파이어몽키 라이브러리

다음 경로 중 FMX.AddressBook.Android.pas과 같이 Android가 포함된 파일들을 참고하기 바랍니다.

위 안드로이드 SDK 브릿지 파일들을 이용한 코드들로 구현되어 있습니다.

C:\Program Files (x86)\Embarcadero\Studio\18.0\source\fmx



델파이 샘플 프로젝트


기타 궁금한 내용은 댓글 또는 http://blog.hjf.pe.kr/452의 댓글을 참고하거나 등록해주세요.

저작자 표시 비영리 동일 조건 변경 허락
신고

험프리.김현수 파이어몽키 , , , ,

  1. Blog Icon
    silkroad99

    안녕하세요
    많은 도움을 받고 있습니다.
    혹 PM3SDK_connectByAddress.jar 파일은 어디서 다운받는건가요

  2. 이런, 제가 링크를 안걸었네요^^
    다운로드 링크 추가했습니다.

  3. Blog Icon
    barcoder

    위에 소스를 다운받아 컴파일시에 오류가 발생합니다..
    시애틀 사용자구요..
    메시지는 아래와 같습니다..

    Checking project dependencies...
    Compiling PointMobileBI07.dproj (Debug, Android)
    dccaarm command line for "PointMobileBI07.dpr"
    f:\program files (x86)\embarcadero\studio\17.0\bin\dccaarm.exe -$O- --no-config -M -Q -TX.so -AGenerics.Collections=System.Generics.Collections;
    Generics.Defaults=System.Generics.Defaults;WinTypes=Winapi.Windows;WinProcs=Winapi.Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE -DDEBUG
    -E.\Android\Debug -I"f:\program files (x86)\embarcadero\studio\17.0\lib\Android\debug";"f:\program files
    (x86)\embarcadero\studio\17.0\lib\Android\Release" -LEC:\Users\Public\Documents\Embarcadero\Studio\17.0\Bpl\Android
    -LNC:\Users\Public\Documents\Embarcadero\Studio\17.0\Dcp\Android -NU.\Android\Debug -NSSystem;Xml;Data;Datasnap;Web;Soap; -O"f:\program files
    (x86)\embarcadero\studio\17.0\lib\Android\Release" -R"f:\program files (x86)\embarcadero\studio\17.0\lib\Android\Release" -U"f:\program files
    (x86)\embarcadero\studio\17.0\lib\Android\debug";"f:\program files (x86)\embarcadero\studio\17.0\lib\Android\Release" --libpath:"F:\Program Files
    (x86)\Embarcadero\Studio\15.0\PlatformSDKs\android-ndk-r9c\platforms\android-14\arch-arm\usr\lib";"F:\Program Files
    (x86)\Embarcadero\Studio\15.0\PlatformSDKs\android-ndk-r9c\sources\cxx-stl\gnu-libstdc++\4.8\libs\armeabi-v7a" --linker:"F:\Program Files
    (x86)\Embarcadero\Studio\15.0\PlatformSDKs\android-ndk-r9c\toolchains\arm-linux-androideabi-4.6\prebuilt\windows\bin\arm-linux-androideabi-ld.exe" -V
    -VN -NO.\Android\Debug PointMobileBI07.dpr
    [DCC Error] PointMobileBluetoothChatService.pas(147): E2035 Not enough actual parameters
    [DCC Warning] PointMobileBluetoothChatService.pas(158): W1000 Symbol 'SharedActivityContext' is deprecated: 'Use TAndroidHelper.Context'
    [DCC Error] PointMobileBluetoothChatService.pas(160): E2250 There is no overloaded version of 'CallInUIThread' that can be called with these arguments
    [DCC Error] PointMobileBluetoothChatService.pas(308): E2250 There is no overloaded version of 'Queue' that can be called with these arguments
    [DCC Fatal Error] MainForm.pas(18): F2063 Could not compile used unit 'PointMobileBluetoothChatService.pas'
    Failed
    Elapsed time: 00:00:02.9
    [Warning] W1000 Symbol 'SharedActivityContext' is deprecated: 'Use TAndroidHelper.Context'
    [Warning] W1000 Symbol 'SharedActivityContext' is deprecated: 'Use TAndroidHelper.Context'

  4. 해당 프로젝트는 10.1 베를린에서 작성되었습니다.
    버전별로 함수의 파라메터가 다를 수 있으니, 시애틀로 컴파일 시 오류를 확인해 조치하시기 바랍니다.

  5. Blog Icon
    silkroad99

    안녕하세요
    샘플 프로젝트를 베를린에서 테스트 해보고 있습니다.
    프로젝트 소스에서 압축을 풀면

    begin
    Application.Initialize;
    AApplication.CreateForm(TForm1, Form1);
    pplication.Run;
    end.

    상단에서 압축문제인지 잘 못된것을 수정하고나서
    컴파일하고 실행하면 바코드 스캐너가 스캔을 못합니다.

    메모장에 커서를 놓으면 바코드 리딩이 되는데
    스캔 버튼 클릭하면 작동이 안됩니다.

    메모장이나 에디트박스에서 커서 위치시키고
    스캔해야만 됩니다.
    제가 뭐를 잘못하고 있는건가요

  6. 위 영상을 보시면 아시겠지만, 저는 메모나 에디터에 커서를 놓지 않아도 스캔이 잘 동작합니다.

    자세한 내용은 github 소스코드와 설치한 소스코드를 비교해보시거나, 디버깅(F9 버튼으로 실행)해보시며 찾아보시기 바랍니다.

    질문만 보고는 제가 조언드릴 부분이 없네요.

  7. Blog Icon
    silkroad99

    빠른 답변에 감사드립니다.

    여기에는 파일 업로드를 할수없어
    부득이하게 데브기어 개발자QnA에
    프로젝트를 업로드했습니다.

    가능하시다면 어디가 오류인지 부탁드립니다.

    감사합니다.

  8. http://tech.devgear.co.kr/delphi_qna/432793
    위 글에 답변 달았습니다.

  9. Blog Icon
    silkroad99

    감사합니다.

  10. 안녕하세요.

    델파이 10.1 베를린 사용자 입니다.

    안드로이드가 아닌 윈도우 프로그램으로 자바파일을 호출하여 델파이에서 음성입력과 출력이 가능한 프로젝트를 구성해 보려고 하는데 위의 사항과 동일한 방법을 사용해도 무관할까요?

    음성인식과 음성출력은 자바에서 사용하였습니다.

    혹여 가능하시다면 답변부탁드립니다.

  11. 저도 윈도우 환경에서 자바파일 호출하는 작업을 진행해보지는 않았지만,
    개념은 비슷할 것으로 보입니다.

    다만, 안드로이드 환경에서는 필요한 기능이 유닛과 함수로 준비되어 있지만, 윈도우 환경에서는 필요한 기능들을 안드로이드 코드를 참조하는 등으로 새로 구성해야 할것으로 예상됩니다.

    구글에서 "call java from delphi" 등의 검색어로 검색해 보시면 관련된 자료들을 찾을 수 있으니 참고하시기 바랍니다.

  12. Blog Icon
    파이어버드

    위 소스 실행시키면
    Context := TAndroidHelper.Context;

    < 오류내용 >
    [DCC Error] PointMobileBluetoothChatService.pas(264): E2003 Undeclared identifier: 'TAndroidHelper'
    [DCC Error] PointMobileBluetoothChatService.pas(264): E2066 Missing operator or semicolon
    [DCC Error] PointMobileBluetoothChatService.pas(364): E2250 There is no overloaded version of 'Queue' that can be called with these arguments


    procedure TPointMobileBluetoothChatService.SetupChatService;
    var
    Context: JContext;
    Looper: JLooper;
    begin

    if FBluetoothAdapter.State = TBluetoothAdapterState.Off then
    begin
    Log.d('Turn on bluetooth');
    Exit;
    end;

    FCallback := TCharServiceHandlerCallback.Create;

    FCallback.OnHandleMessage := ChatServiceHandleMessage;

    //이부분이 에러가 납니다.
    Context := TAndroidHelper.Context;

    Looper := TJLooper.JavaClass.getMainLooper;

    FHandler := TJHandler.JavaClass.init(Looper, FCallback);

    FChatService := TJBluetoothChatService.JavaClass.init(Context, FHandler);
    TJSendCommand.JavaClass.SendCommandInit(FChatService, FHandler);

    end;


    xe8로 개발하고 있습니다.

    혹시 아시는 분 있으시면 답변 부탁드립니다.

    감사합니다.

  13. XE8에서는 TAndroidHelper.Context 대신 SharedActivityContext라고 선언하면 됩니다.

    참고로, 모바일 개발 시 되도록 최신버전으로 진행하는 것이 좋습니다.
    최신버전에서 새로운 안드로이드/iOS 플랫폼을 정식지원합니다.

  14. Blog Icon
    파이어버드

    안녕하세요. 험프리님
    xe8에서
    TAndroidHelper.Context -> SharedActivityContext 변경하니 잘 됩니다.
    이것 때문에 몇일동안 고생했는데 너무 감사합니다.

    그리고 silkroad99님도 원격 및 소스도 주시면서 도와 주셔서 감사하고
    두분 언제 저녁 한번 대접하고 싶은데 가능하신지...
    메일 주시면 달려가겠습니다.

    감사합니다.







  15. Blog Icon
    장창원

    안녕하세요. 자료 잘 봤습니다.
    delphi 10. berlin 쓰고 있습니다.
    머 좀 여쭤봐도 될른지요...

    제가 가지고 있던 java 파일을 jar을 만들었고, java2op 를 쓰는데, 여기서 문제가 생겼습니다.
    java2op -jar Recognition.jar -unit Android.JNI.Recognition -classes *
    라고 해서, 맨 뒤에 -classes * 을 붙이지 않으면, no classes selecte 라고 에러가 나더이다.
    여튼 붙여서 작업을 하면 돌아는 갑니다만, 결과로 나온 pas파일이 다 똑같습니다.
    java2op -jar FreeImage.jar -unit Android.JNI.FreeImage -classes * 라고 해서,
    FreeImage.jar 란 파일을 돌려도, 물론 java 파일의 내용은 틀리고, 안에 몇줄 없습니다만, 결과로 나온 pas 가 둘다 똑같습니다. 아무래도 bootclasses.xml 파일을 parsing 한다고 메세지가 나오던데, xml 과 비슷한 내용만 들어가 있습니다. 파일도 엄청 크고요...
    뭐가 문제인지 삽질 중입니다.
    고견 있으시면 댓글 부탁드립니다.
    감사합니다.

  16. 데브기어 테크게시판(tech.devgear.co.kr)의 개발자 Q&A에 해당 jar파일과 함께 다시 등록해 주세요.

    잘될지는 모르겠지만 저도 시도해보도록 하겠습니다.