인프런 커뮤니티 질문&답변

김성락님의 프로필 이미지
김성락

작성한 질문수

직장인에게 꼭 필요한 파이썬-아래아한글 자동화 레시피

녹화된 스크립트매크로를 파이썬에서 활용하는 방법

배포 파일 작성방법 문의

해결된 질문

작성

·

76

1

현재 엑셀에서 값을 가져와서 필드값으로 입력하는 것으로 배포파일을 만들어 부서에 테스트 해보려고 하는데요,

혹시 배포파일을 만드는 방법도 강의 커리큘럼에 있을까요?

알려주시면 바로 수강하고자 합니다

답변 1

2

일코님의 프로필 이미지
일코
지식공유자

성락님 안녕하세요?

배포파일을 만드는 것은 너무너무 간단한 작업이기 때문에,

그냥 답변으로도 알려드릴 수는 있습니다.

 

가장 기본적인 컴파일 방법

우선 (가상환경을 사용하신다는 전제하에)

pyinstaller라는 모듈을 pip install pyinstaller로 설치하셔야 합니다.

그리고 (.ipynb 파일이 아닌) .py 확장자로 코드를 완성하신 후에

터미널에서 pyinstaller -F -w 소스코드.py 라고 입력하고 실행하시면 됩니다.

이 때 -F 는 --onefile과 같은 의미로, 하나의 exe 파일로 만들겠다는 의미이고,

-w는 콘솔창을 유지하지 않겠다는 의미입니다. (GUI가 없다면 백그라운드에서 실행되는 겁니다.)

 

보안모듈 포함 컴파일하는 방법

보안모듈은 배포되는 PC의 레지스트리에디터를 건드려야 하므로

그냥 "번거로우시겠지만 '모두허용' 버튼을 한 번 클릭해주시라~" 고 안내를 드리는 방법도 있지만,

win32com 대신 pyhwpx 모듈을 이용하시는 경우라면

FilePathCheckerModule.dll 파일을 소스코드와 동일한 폴더에 넣으신 후,

pyinstaller -F -w --add-binary="FilePathCheckerModule.dll:." 소스코드.py

라고 실행하시면 배포된 PC에서 보안모듈이 자동등록되면서 팝업창이 뜨지 않게 됩니다.

(가급적 배포시에 FilePathCheckerModule.dll 파일도 실행파일과 같이 배포하시는 걸 추천드림)

 

용량이 너무 큰 경우

이 때 가상환경에 외부모듈이 많이 설치되어 있으면

컴파일할 때 실행파일 용량이 어마어마하게 커지는 경우가 있습니다.

여러가지 최적화 방법이 있지만,

제일 무난한 방법은, 개발용 말고 컴파일용 가상환경을 새로 하나 만들어버리는 겁니다.

그리고 딱 pyhwpx, pyinstaller 두 개만 설치한 상태에서 컴파일을 하면

40~70메가바이트 용량 정도로 만들어지는 듯 합니다.

 

테스트해보시고,

추가로 궁금한 점 있으면 댓글 남겨주세요!

 

김성락님의 프로필 이미지
김성락
질문자

안녕하세요! 테스트 해보았는데 잘 작동합니다.

다만, 보안모듈을 함께 넣어 배포해야할 것 같은데 자세한 내용을 설명 가능할까요..?

그냥 pyhwpx만 import하고 위의 내용대로 진행해도 동일하게 작동할까요?

일코님의 프로필 이미지
일코
지식공유자

  1. 컴파일한 exe파일과 FilePathCheckerModule.dll 파일을 같이 전송하시면 됩니다^^ 같은 폴더에 있으면 확실히 되어야합니다;;; (근데 안 된다는 경우가 있더라고요...)

  2. 제대로 컴파일이 되었으면 dll파일은 빼고 보내도 되는데, (또 안된다는 경우가 있다고 하더라고요..)

  3. 그리고 혹시 진짜 혹시 같이 보내도 안 된다고 하면

    dll파일을 사용자 폴더(C:\Users\사용자이름)에다 넣으시면 확실하게 됩니다.

사실 제가 갖고 있는 모든 PC에서는 세 가지 방법 다 잘 되는데...

저도 컴파일해서 나눠드리면 간혹 안된다는 분들이 있어서 안심이 안 되네요...ㅜㅜㅜ

 

김성락님의 프로필 이미지
김성락
질문자

일코님 자꾸 질문드려 죄송합니다..

pyhwpx라이브러리에서 보안모듈 기능만 import할 수 있을까요?

함께 import해서 배포파일을 만들어보니 용량이 꽤 많이 커져서 여쭤봅니다

일코님의 프로필 이미지
일코
지식공유자

맞아요...

pyhwpx의 일부 메서드에서 numpy와 pandas를 쓰고 있기 때문입니다ㅠ

 

아래는 pyhwpx에서 보안모듈 자동등록 부분만 잘라낸 코드입니다.

기존 win32com으로 작성하시는 코드 상단에 붙여넣으시고,

hwp 객체 생성 직후에, 커스텀함수인 register_module(hwp)를 실행하시면 됩니다.

 

register_module 함수의 동작은 아래와 같습니다.

 

① 레지스트리에 보안모듈이 등록되어 있는지 체크

-> 보안모듈 등록되어 있으면 : 레지스트리 경로값에 DLL파일이 존재하는지 체크하고,

DLL파일이 해당 경로에 존재하면 바로 이어서 hwp.RegisterModule() 실행. 끝.

-> 보안모듈이 등록되어 있지 않으면 : ②번 과정으로.

 

② 실행폴더 또는 사용자폴더에 FilePathCheckerModule.dll 파일이 있는지 체크

-> 둘 중 하나에 DLL 파일이 있으면 : 레지스트리 경로값 수정 또는 레지스트리 등록

-> DLL 파일이 없으면 사용자 폴더에 DLL 파일 다운로드 후 레지스트리 등록

등록을 마친 후 hwp.RegisterModule() 실행. 끝.

 

이런 과정을 거칩니다.

코드는 아래와 같습니다.

 

"""
아래아한글 보안모듈(FilePathCheckerModule.DLL) 자동등록 코드.
`if __name__ == '__main__':` 라인 밑으로 코드 작성하시면 됩니다.
"""

import os
import win32com.client as win32
from winreg import ConnectRegistry, HKEY_CURRENT_USER, OpenKey, KEY_WRITE, SetValueEx, REG_SZ, CloseKey, KEY_READ, QueryValueEx


def check_registry_key():
    """
    보안모듈이 등록되어 있는 경우, 레지스트리 수정하지 않기!
    """
    winup_path = r"Software\HNC\HwpAutomation\Modules"
    alt_winup_path = r"Software\Hnc\HwpUserAction\Modules"
    reg_handle = ConnectRegistry(None, HKEY_CURRENT_USER)

    for path in [winup_path, alt_winup_path]:
        try:
            key = OpenKey(reg_handle, path, 0, KEY_READ)
            try:
                value, regtype = QueryValueEx(key, "FilePathCheckerModule")
                if value and os.path.exists(value):
                    print("Security Module already registered.")
                    CloseKey(key)
                    return True
                else:
                    print("Security Module DLL doesn't exist in registered path.")
                    CloseKey(key)
                    return False
            except FileNotFoundError:
                pass
            CloseKey(key)
        except FileNotFoundError:
            pass
    return False


def register_module(hwp, module_type="FilePathCheckDLL", module_data="FilePathCheckerModule"):
    """
    레지스트리 체크해서 보안모듈 등록되어 있지 않으면 등록하고 보안모듈 실행
    """
    if not check_registry_key():
        register_regedit()
    return hwp.RegisterModule(ModuleType=module_type, ModuleData=module_data)


def register_regedit():
    """
    레지스트리에 보안모듈 등록하기
    """
    # 1. 현재 폴더에 보안모듈 파일이 있는지
    location = os.environ["USERPROFILE"]
    if "FilePathCheckerModule.dll".lower() in [i.lower() for i in os.listdir(os.getcwd())]:
        print("Found Security Module DLL in Current Path.")
        location = os.getcwd()
    # 2. 사용자 폴더에 보안모듈 파일이 있는지
    elif "FilePathCheckerModule.dll".lower in [i.lower() for i in os.listdir(os.path.join(os.environ["USERPROFILE"]))]:
        print("Found Security Module DLL in USERPROFILE Path.")
        pass
    else:
        # 3. 두 군데 다 없으면 직접 다운받아버리기
        from urllib import request
        from urllib.error import URLError
        from zipfile import ZipFile

        print("downloading FilePathCheckerModule.dll to USERPROFILE path")
        try:
            f = request.urlretrieve(
                "https://github.com/hancom-io/devcenter-archive/raw/main/hwp-automation/%EB%B3%B4%EC%95%88%EB%AA%A8%EB%93%88(Automation).zip",
                filename=os.path.join(os.environ["USERPROFILE"], "FilePathCheckerModule.zip"))
            with ZipFile(f[0]) as zf:
                zf.extract(
                    "FilePathCheckerModuleExample.dll",
                    os.path.join(os.environ["USERPROFILE"]))
            os.remove(os.path.join(os.environ["USERPROFILE"], "FilePathCheckerModule.zip"))
            if not os.path.exists(os.path.join(os.environ["USERPROFILE"], "FilePathCheckerModule.dll")):
                os.rename(os.path.join(os.environ["USERPROFILE"], "FilePathCheckerModuleExample.dll"),
                          os.path.join(os.environ["USERPROFILE"], "FilePathCheckerModule.dll"))
            location = os.environ["USERPROFILE"]
        except URLError as e:
            print(f"아래와 같은 이유로 URL 에러가 발생하여 보안모듈 다운로드에 실패했습니다: \n{e.reason}")
        except Exception as e:
            print(f"예기치 못한 오류가 발생했습니다. 아래 오류를 복사하여 개발자 이메일로 전달 부탁드립니다: \n{str(e)}\n\nto `martinii.fun@gmail.com`")

    winup_path = r"Software\HNC\HwpAutomation\Modules"
    reg_handle = ConnectRegistry(None, HKEY_CURRENT_USER)

    try:
        key = OpenKey(reg_handle, winup_path, 0, KEY_WRITE)
    except FileNotFoundError as e:
        winup_path = r"Software\Hnc\HwpUserAction\Modules"
        key = OpenKey(reg_handle, winup_path, 0, KEY_WRITE)
    SetValueEx(key, "FilePathCheckerModule", 0, REG_SZ, os.path.join(location, "FilePathCheckerModule.dll"))
    CloseKey(key)


if __name__ == '__main__':
    hwp = win32.gencache.EnsureDispatch("hwpframe.hwpobject")
    register_module(hwp)  # <--- 사전작업 및 hwp.RegisterModule()까지 실행하는 커스텀 함수  
    hwp.XHwpWindows.Item(0).Visible = True
    # 아래에 이어서 코드 작성하시면 됩니다~~~
    hwp.Open(r"C:\Users\Administrator\Documents\바탕화면에 있던 것들\수능수리\2024학년도 수능 수리(나형).hwp")

 

도움이 되길 바랍니다.

행복한 하루 되세요^^

김성락님의 프로필 이미지
김성락

작성한 질문수

질문하기