ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

com_error at / (-2147221008, 'CoInitialize は呼び出されていません。', None, None)

エラーの対処法

Djangoで作ったWebアプリケーションで表題のエラーが発生しました。

このWebアプリは、Web画面でファイルをアップロードし、ファイルをサーバーに渡し、サーバー側でExcelを起動しファイルを処理する。というようなものです。Excelを起動するときにエラーが発生しているようです。

Internal Server Error: /
Traceback (most recent call last):
  File "C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\myproject\lib\site-packages\win32com\client\dynamic.py", line 89, in _GetGoodDispatch
    IDispatch = pythoncom.connect(IDispatch)
pywintypes.com_error: (-2147221008, 'CoInitialize は呼び出されていません。', None, None)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\myproject\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
    response = get_response(request)
  File "C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\myproject\lib\site-packages\django\core\handlers\base.py", line 126, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\myproject\lib\site-packages\django\core\handlers\base.py", line 124, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\XXXX\workspace\myproject\csv2excel\views.py", line 73, in config_table_maker
    feat_names = handle_uploaded_file(request.FILES["file"])
  File "C:\Users\XXXX\workspace\myproject\csv2excel\views.py", line 61, in handle_uploaded_file
    feat_names = sample(file_name)
  File "C:\Users\XXXX\workspace\myproject\csv2excel\views.py", line 20, in sample
    excel = Excel()
  File "C:\Users\XXXX\workspace\myproject\src\mods\Excel.py", line 17, in __init__
    self._app = win32com.client.Dispatch("Excel.Application")
  File "C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\myproject\lib\site-packages\win32com\client\__init__.py", line 95, in Dispatch
    dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
  File "C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\myproject\lib\site-packages\win32com\client\dynamic.py", line 114, in _GetGoodDispatchAndUserName
    return (_GetGoodDispatch(IDispatch, clsctx), userName)
  File "C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\myproject\lib\site-packages\win32com\client\dynamic.py", line 91, in _GetGoodDispatch
    IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
pywintypes.com_error: (-2147221008, 'CoInitialize は呼び出されていません。', None, None)

サーバー側でログインしてコンソールでプログラムを動かすことはできていて、Webアプリを介して(ブラウザからリクエストして)プログラムを動かそうとしたらこのエラーが発生します。

Apacheでアプリを作った時に、Apacheユーザ(www-data)にsudoersで権限を渡さないとブラウザからのリクエストでサーバ側のプログラムを走らせられないという現象に似ているので、それと同様のエラーでしょうか。

ということはDjangoに何かしらの権限を渡せば動きそうですが、そういう設定はどこで書けばいいのかちょっとわかりません。

色々調べてたら、下記の記事に同様の現象について書いてありました。

[Delphi] CoInitializeが呼び出されていません - くろねこ研究所

このため、CUI アプリケーションでこのエラーメッセージを回避するには、CoInitialize を呼び出すなどの対策が必要になる。また、CoInitialize と対で CoUninitialize を呼び出さないといけない。

この方は私とは逆に、CUIだとエラーが発生するそうです。

COM を使うとその部分は、結構長くなることが多いと思うので、クラス化して、その Create と Destroy に それぞれ CoInitialize と CoUninitialize を追加するのがオススメである。

ほかの記事も色々見たのですが、Excelを起動する前にCoInitialize()を呼び出し、Excelを終了した後にCoUninitialize()を呼び出すとOKっぽいことがわかります。クラスで書くとこんな感じでしょうか。

# -*- coding:utf-8 -*-
from win32com.client import gencache, Dispatch
import pythoncom

class Excel():

    def __init__(self, visible=False):
        """ Excel機能を提供するクラス """
        pythoncom.CoInitialize()  # Excelを起動する前にこれを呼び出す
        self._app = Dispatch("Excel.Application")
        self._app.Visible = visible

    @property
    def app(self):
        return self._app

    def quit(self):
        """ Excelを終了させる """
        self.app.Quit()
        pythoncom.CoUninitialize()  # Excelを終了した後はこれを呼び出す

で、書き換えたら動きました。ただ、このCoInitialize()が何なのか正直よくわからない。

参考URL

Coinitializeとは?

わからないままにしとくのはちょっと怖かったので、少し調べます。

[連載! とことん VC++] 第 1 回 COM 再入門 ~ COM オブジェクトの基本的利用 (COM クライアントの実装) ~ in C++

  1. CoInitialize の初期化が意味すること ~ アパートメントの準備 ~ ここで、[3] の初期化の意味をもう少しく詳しく確認しましょう。

CoInitialize 関数は COM オブジェクトの実行環境ともいえる「アパートメント」を必要に応じて 1 つ作成する役割を持ちます。この関数呼び出しによって、アパートメントを準備しなければ、COM オブジェクトを実行できません。

このアパートメントには 2 つの種類があります。それは、「STA (Single Thread Apartment)」と「MTA (Multi Thread Apartment)」です。実は、[3] の CoInitialize 関数は、STA のアパートメントを作成する作用があり、次のように CoInitializeEx 関数を使用する場合と同じです。

CoInitializeはCOMの初期化をする関数で、STAかMTAで初期化するそう。

そもそもCOMというものを明確に理解していないのでググります。

COM(Component Object Model)とは - IT用語辞典 e-Words

COMとは、マイクロソフトMicrosoft)社が提唱していた、ソフトウェアの機能を部品化して外部から呼び出して利用する仕組みを定めた技術仕様の一つ。主に同社のWindowsシリーズのOSやその上で動作するソフトウェアで利用された。

プログラムの機能を外部から呼び出して利用するための手順やデータ形式などの標準を定めており、COMの仕様に則って開発されたソフトウェア間は容易に連携して動作させることができる。特定のOSやプログラミング言語に依存しない仕様になっており、異なる言語やOSで開発されたプログラムを連携させることができる。

まあつまり、「外部アプリケーションであるExcelpythonで呼び出すにはCOMという技術を使うので、COMの初期化が必要。」というのはわかりました。でも結局、どうしてWebアプリから実行したときだけこの初期化が必要になったのかはよくわかりませんでしたね(眼鏡クイッ)。

com_errorのtry except

from pywintypes import com_error

try:
    # do something
except com_error:
    # do something when com_error occurred.