이 페이지에서

Qt에서 ActiveX 서버 구축

QAxServer 모듈은 ActiveQt 프레임워크의 일부입니다. 이 모듈은 다음 세 가지 클래스로 구성됩니다:

  • QAxFactory COM 객체 생성을 위한 팩토리를 정의합니다.
  • QAxBindable Qt Widgets와 COM 객체 간의 인터페이스를 제공합니다.
  • QAxAggregated 추가적인 COM 인터페이스를 구현하기 위해 서브클래스로 확장할 수 있습니다.

ActiveX 컨트롤 및 COM 객체의 구현 예제가 제공됩니다.

라이브러리 사용법

QAxServer 라이브러리를 사용하여 표준 Qt 애플리케이션을 COM 서버로 전환하려면, .pro 파일의 QT 변수에 axserver 를 추가해야 합니다.

다음과 같은 .pro 파일을 통해 프로세스 외 실행형 서버가 생성됩니다.

TEMPLATE = app
QT  += axserver

RC_FILE  = qaxserver.rc
...

인-프로세스 서버를 빌드하려면 다음과 같은 .pro 파일을 사용하십시오:

TEMPLATE = lib
QT += axserver
CONFIG  += dll

DEF_FILE = qaxserver.def
RC_FILE  = qaxserver.rc
...

qaxserver.rc 및 qaxserver.def 파일은 프레임워크의 일부이며, 일반적인 위치에서 사용하거나( .pro 파일에 경로를 지정) 프로젝트 디렉터리로 복사할 수 있습니다. 타입 라이브러리 항목으로 어떤 파일이라도 포함하는 한 이 파일들을 수정할 수 있습니다. 즉, 버전 정보를 추가하거나 다른 툴박스 아이콘을 지정할 수 있습니다.

axserver 모듈을 사용하면 qmake 도구가 빌드 시스템에 필요한 빌드 단계를 추가합니다:

  • 다음 대신 바이너리를 qaxserver.lib 에 링크하십시오. qtmain.lib
  • idc 도구를 호출하여 COM 서버용 IDL 파일을 생성합니다.
  • MIDL 도구(컴파일러 설치의 일부)를 사용하여 IDL을 타입 라이브러리로 컴파일합니다.
  • 생성된 타입 라이브러리를 서버 바이너리에 바이너리 리소스로 연결합니다(이때도 idc 도구를 사용).
  • 서버를 등록합니다. 이 단계는 관리자 권한이 필요할 수 있으며, ` qaxserver_no_register ` 구성을 설정하여 건너뛸 수 있습니다.

후처리 단계를 건너뛰려면 qaxserver_no_postlink 구성을 설정하십시오.

또한 VERSION 변수를 사용하여 버전 번호를 지정할 수 있습니다. 예:

TEMPLATE = lib
VERSION = 2.5
...

지정된 버전 번호는 등록 시 유형 라이브러리와 서버의 버전으로 사용됩니다.

프로세스 외부 대 프로세스 내부

COM 서버를 독립 실행형 실행 파일로 실행할지, 아니면 클라이언트 프로세스 내의 공유 라이브러리로 실행할지는 주로 서버에서 제공하려는 COM 개체의 유형에 따라 달라집니다.

실행 파일 서버는 독립형 응용 프로그램으로 실행될 수 있다는 장점이 있지만, COM 클라이언트와 COM 개체 간의 통신에 상당한 오버헤드를 추가합니다. 컨트롤에 프로그래밍 오류가 있는 경우, 해당 컨트롤을 실행하는 서버 프로세스만 중단되고 클라이언트 응용 프로그램은 계속 실행될 가능성이 높습니다. 모든 COM 클라이언트가 실행 파일 서버를 지원하는 것은 아닙니다.

인-프로세스 서버는 일반적으로 크기가 더 작고 시작 시간이 더 빠릅니다. 클라이언트와 서버 간의 통신은 가상 함수 호출을 통해 직접 이루어지며, 원격 프로시저 호출에 필요한 오버헤드가 발생하지 않습니다. 그러나 서버가 중단되면 클라이언트 애플리케이션도 함께 중단될 가능성이 높으며, 인-프로세스 서버에서는 모든 기능을 사용할 수 있는 것은 아닙니다(예: COM의 실행 중인 객체 테이블(ROT)에 등록).

두 서버 유형 모두 Qt를 공유 라이브러리로 사용하거나 서버 바이너리에 정적으로 링크하여 사용할 수 있습니다.

빌드 후 단계에서 흔히 발생하는 오류

ActiveQt 고유의 후처리 단계가 정상적으로 작동하려면 서버가 다음 요구 사항을 충족해야 합니다:

  • 노출된 모든 컨트롤은 ` QApplication ` 인스턴스만 존재하더라도 생성될 수 있어야 합니다
  • 서버의 초기 링크 과정에 임시 타입 라이브러리 리소스가 포함되어야 합니다.
  • 서버 실행에 필요한 모든 종속성이 시스템 경로(또는 호출 환경에서 사용하는 경로)에 있어야 합니다(Visual Studio에는 [도구] > [옵션] > [디렉터리] 대화 상자에 나열된 고유한 환경 변수 세트가 있다는 점에 유의하십시오).

이러한 요구 사항이 충족되지 않으면 다음 오류 중 하나 이상이 발생할 가능성이 높습니다:

서버 실행 파일이 중단됩니다.

IDL을 생성하려면 ActiveX 컨트롤로 노출된 위젯을 인스턴스화(생성자 호출)해야 합니다. 이 시점에서는 QApplication 객체 외에는 아무것도 존재하지 않습니다. 위젯 생성자는 다른 객체가 생성될 것을 전제로 해서는 안 되며, 예를 들어 null 포인터를 확인해야 합니다.

서버를 디버그하려면 -dumpidl 출력파일 옵션을 사용하여 실행하고, 어디서 충돌이 발생하는지 확인하십시오.

이때 컨트롤의 어떤 함수도 호출되지 않는다는 점에 유의하십시오.

서버 실행 파일이 유효한 Win32 응용 프로그램이 아닙니다

타입 라이브러리를 연결하는 과정에서 서버 바이너리가 손상되었습니다. 이는 Windows의 버그이며 릴리스 빌드에서만 발생합니다.

첫 번째 링크 단계에서는 나중에 idc로 대체할 수 있는 더미 타입 라이브러리를 실행 파일에 링크해야 합니다. 예제에서 보여준 대로 타입 라이브러리가 포함된 리소스 파일을 프로젝트에 추가하십시오.

"DLL을 찾을 수 없음"

빌드 시스템은 인터페이스 정의를 생성하고 서버를 등록하기 위해 서버 실행 파일을 실행해야 합니다. 서버가 링크하는 동적 링크 라이브러리가 경로에 없으면 이 작업이 실패할 수 있습니다(예: Visual Studio는 "디렉터리" 옵션에 지정된 환경 설정을 사용하여 서버를 호출합니다). 서버에 필요한 모든 DLL 및 플러그인이 오류 메시지 상자에 표시된 경로에 포함된 디렉터리 내에 있는지 확인하십시오( Windows 배포 도구도 참조하십시오).

"파일을 열 수 없습니다..."

마지막 클라이언트가 사용을 중단했을 때 ActiveX 서버가 정상적으로 종료되지 않았을 수 있습니다. 일반적으로 응용 프로그램이 종료되는 데 약 2초가 소요되지만, 작업 관리자를 사용하여 프로세스를 강제 종료해야 할 수도 있습니다(예: 클라이언트가 컨트롤을 제대로 해제하지 않은 경우).

컨트롤을 인스턴스화할 수 없음

이 경우, 관리자 권한으로 서버를 등록하면 문제가 해결될 수 있습니다.

컨트롤 구현

Qt로 COM 객체를 구현하려면 QObject 또는 기존 QObject 의 하위 클래스를 생성하십시오. 해당 클래스가 QWidget 의 하위 클래스인 경우, COM 객체는 ActiveX 컨트롤이 됩니다.

#include <QWidget>

class MyActiveX : public QWidget
{
    Q_OBJECT

Q_OBJECT 매크로는 위젯에 대한 메타 객체 정보를 ActiveQt 프레임워크에 제공하기 위해 필요합니다.

Q_CLASSINFO("ClassID", "{1D9928BD-4453-4bdd-903D-E525ED17FDE5}")
Q_CLASSINFO("InterfaceID", "{99F6860E-2C5A-42ec-87F2-43396F4BE389}")
Q_CLASSINFO("EventsID", "{0A3E9F27-E4F1-45bb-9E47-63099BCCD0E3}")

Q_CLASSINFO() 매크로를 사용하여 COM 객체의 COM 식별자를 지정하십시오. ClassID 및 InterfaceID 는 필수이며, EventsID 는 객체에 시그널이 있는 경우에만 필요합니다. 이러한 식별자를 생성하려면 uuidgen 또는 guidgen 와 같은 시스템 도구를 사용하십시오.

각 클래스에 대해 추가 속성을 지정할 수 있습니다. 자세한 내용은 ‘클래스 정보 및 튜닝’을 참조하십시오.

Q_PROPERTY(int value READ value WRITE setValue)

Q_PROPERTY() 매크로를 사용하여 ActiveX 컨트롤의 속성을 선언하십시오.

QObject 의 하위 클래스와 마찬가지로 부모 객체를 인수로 받는 표준 생성자와 함수, 시그널 및 슬롯을 선언하십시오.

public:
    MyActiveX(QWidget *parent = 0)
    ...

    int value() const;

public slots:
    void setValue(int v);
    ...

signals:
    void valueChange(int v);
    ...

};

ActiveQt 프레임워크는 속성과 공용 슬롯을 ActiveX 속성 및 메서드로, 신호를 ActiveX 이벤트로 노출하고, Qt 데이터 유형과 이에 상응하는 COM 데이터 유형 간에 변환합니다.

데이터 유형

속성에 대해 지원되는 Qt 데이터 유형은 다음과 같습니다.

Qt 데이터 유형COM 속성
boolVARIANT_BOOL
QStringBSTR
intint
uintunsigned int
doubledouble
qlonglongCY
qulonglongCY
QColorOLE_COLOR
QDateDATE
QDateTimeDATE
QTimeDATE
QFontIFontDisp*
QPixmapIPictureDisp*
QVariantVARIANT
QVariantList ( QList<QVariant>와 동일)SAFEARRAY(VARIANT)
QStringListSAFEARRAY(BSTR)
QByteArraySAFEARRAY(BYTE)
QRect사용자 정의 유형
QSize사용자 정의 유형
QPoint사용자 정의 유형

신호 및 슬롯의 매개변수로 지원되는 Qt 데이터 유형은 다음과 같습니다:

Qt 데이터 유형COM 매개변수
bool[in] VARIANT_BOOL
bool&[in, out] VARIANT_BOOL*
QString, const QString&[in] BSTR
QString&[in, out] BSTR*
QString&[in, out] BSTR*
int[in] int
int&[in, out] int
uint[in] unsigned int
uint&[in, out] 부호 없는 정수*
double[in] double
double&[in, out] double*
QColor, const QColor&[in] OLE_COLOR
QColor&[in, out] OLE_COLOR*
QDate, const QDate&[in] DATE
QDate&[in, out] DATE*
QDateTime, const QDateTime&[in] DATE
QDateTime&[in, out] DATE*
QFont, const QFont&[in] IFontDisp*
QFont&[in, out] IFontDisp**
QPixmap, const QPixmap&[in] IPictureDisp*
QPixmap&[in, out] IPictureDisp**
QList<QVariant>, const QList<QVariant>&[in] SAFEARRAY(VARIANT)
QList<QVariant>&[in, out] SAFEARRAY(VARIANT)*
QStringList, const QStringList&[in] SAFEARRAY(BSTR)
QStringList&[in, out] SAFEARRAY(BSTR)*
QByteArray, const QByteArray&[in] SAFEARRAY(BYTE)
QByteArray&[in, out] SAFEARRAY(BYTE)*
QObject*[in] IDispatch*
QRect& [in, out] struct QRect (사용자 정의)
QSize&[in, out] struct QSize (사용자 정의)
QPoint&[in, out] struct QPoint (사용자 정의)

내보내진 열거형 및 플래그도 지원됩니다( Q_ENUM() 및 Q_FLAG() 참조). 입력 매개변수 유형은 반환값으로도 지원됩니다.

다른 데이터 유형을 사용하는 매개변수를 가진 속성 및 시그널/슬롯은 ActiveQt 프레임워크에서 무시됩니다.

하위 객체

COM 객체는 COM 객체의 하위 요소를 나타낼 수 있는 여러 하위 객체를 가질 수 있습니다. 예를 들어, 다중 문서 스프레드시트 애플리케이션을 나타내는 COM 객체는 각 스프레드시트마다 하나의 하위 객체를 제공할 수 있습니다.

QAxFactory 에 등록되어 있는 한, 모든 QObject 하위 클래스는 ActiveX에서 하위 객체의 유형으로 사용될 수 있습니다. 그러면 해당 유형을 속성에서 사용하거나 슬롯의 반환 유형 또는 매개변수로 사용할 수 있습니다.

속성 알림

ActiveX 클라이언트가 속성에 바인딩할 수 있도록 하려면, ` QAxBindable ` 클래스로부터 다중 상속을 사용하십시오:

#include <QAxBindable>
#include <QWidget>

class MyActiveX : public QWidget, public QAxBindable
{
    Q_OBJECT

속성 쓰기 함수를 구현할 때는 QAxBindable 클래스의 requestPropertyChange() 및 propertyChanged() 함수를 사용하여 ActiveX 클라이언트가 컨트롤 속성에 바인딩할 수 있도록 하십시오.

컨트롤 제공

COM 시스템에서 COM 서버를 사용할 수 있게 하려면, 5개의 고유 식별자를 사용하여 시스템 레지스트리에 서버를 등록해야 합니다. 이러한 식별자는 guidgen 또는 uuidgen 과 같은 도구에서 제공됩니다. 등록 정보를 통해 COM은 요청된 ActiveX 컨트롤을 제공하는 바이너리를 찾아내고, 컨트롤에 대한 원격 프로시저 호출(RPC)을 마샬링하며, 컨트롤이 노출하는 메서드 및 속성에 대한 유형 정보를 읽을 수 있습니다.

클라이언트가 요청할 때 COM 개체를 생성하려면 서버는 QAxFactory 의 구현을 내보내야 합니다. 이를 수행하는 가장 쉬운 방법은 다음 매크로 세트를 사용하는 것입니다:

QAXFACTORY_BEGIN("{ad90301a-849e-4e8b-9a91-0a6dc5f6461f}",
                 "{a8f21901-7ff7-4f6a-b939-789620c03d83}")
    QAXCLASS(MyWidget)
    QAXCLASS(MyWidget2)
    QAXTYPE(MySubType)
QAXFACTORY_END()

이렇게 하면 ` MyWidget ` 및 ` MyWidget2 `가 COM 클라이언트가 생성할 수 있는 COM 객체로 내보내지며, ` MySubType `는 ` MyWidget ` 및 ` MyWidget2`의 속성과 매개변수에서 사용할 수 있는 유형으로 등록됩니다.

QAxFactory class documentation 에는 이 매크로의 사용 방법과 사용자 정의 팩토리의 구현 및 사용 방법이 설명되어 있습니다.

프로세스 외 실행형 서버의 경우, main() 함수를 구현하여 QApplication 객체를 인스턴스화하고 일반 Qt 애플리케이션과 마찬가지로 이벤트 루프에 진입할 수 있습니다. 기본적으로 애플리케이션은 표준 Qt 애플리케이션으로 시작되지만, 명령줄에서 ` -activex `를 전달하면 ActiveX 서버로 시작됩니다. ` QAxFactory::isServer()`를 사용하여 표준 애플리케이션 인터페이스를 생성 및 실행하거나, 독립 실행형 실행을 방지할 수 있습니다:

#include <QApplication>
#include <QAxFactory>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    if (!QAxFactory::isServer()) {
        // create and show main window
    }
    return app.exec();
}

그러나 ActiveQt는 main 함수의 기본 구현을 제공하므로 이 작업은 반드시 필요한 것은 아닙니다. 기본 구현은 QAxFactory::startServer()를 호출하여 QApplication 인스턴스를 생성한 다음 exec()를 호출합니다.

ActiveX 서버 실행 파일을 빌드하려면 qmake 를 실행하여 makefile을 생성하고, 다른 Qt 애플리케이션과 마찬가지로 컴파일러의 make 도구를 사용하십시오. make 프로세스는 생성된 실행 파일을 -regserver 명령줄 옵션과 함께 호출하여 시스템 레지스트리에 컨트롤을 등록합니다.

ActiveX 서버가 실행 파일인 경우, 다음 명령줄 옵션이 지원됩니다:

옵션결과
-regserver서버를 시스템 레지스트리에 등록합니다
-regserverperuser현재 사용자에 대해 서버를 시스템 레지스트리에 등록합니다(5.14부터)
-unregserver시스템 레지스트리에서 서버 등록을 해제합니다
-unregserverperuser현재 사용자의 시스템 레지스트리에서 서버 등록을 해제합니다(5.14부터).
-activex응용 프로그램을 ActiveX 서버로 시작합니다
-dumpidl <file> -version x.y서버의 IDL을 지정된 파일에 기록합니다. 타입 라이브러리의 버전은 x.y가 됩니다

인-프로세스 서버는 모든 Windows 시스템에서 사용할 수 있는 ‘ regsvr32 ’ 도구를 사용하여 등록할 수 있습니다.

일반적인 컴파일 시 문제

나열된 컴파일러/링커 오류는 Microsoft Visual C++ 6.0 컴파일러에서 발생하는 오류를 기준으로 합니다.

"2개의 매개변수를 받는 오버로드된 함수가 없습니다"

QAXCLASS() 또는 QAXFACTORY_DEFAULT() 매크로를 사용하는 코드에서 이 오류가 발생하면, 위젯 클래스에 기본 팩토리가 사용할 수 있는 생성자가 없는 것입니다. 표준 위젯 생성자를 추가하거나, 생성자가 필요 없는 사용자 지정 팩토리를 구현하십시오.

QAXFACTORY_EXPORT() 매크로를 사용하는 코드에서 이 오류가 발생하면, QAxFactory 의 서브클래스에 적절한 생성자가 없는 것입니다. 팩토리 클래스에 다음과 같은 공용 클래스 생성자를 제공하십시오.

MyFactory(const QUuid &, const QUuid &);

와 같은 공용 클래스 생성자를 팩토리 클래스에 제공하십시오.

"구문 오류: 숫자의 접미사가 잘못되었습니다"

고유 식별자가 QAXFACTORY_EXPORT(), QAXFACTORY_BEGIN() 또는 QAXFACTORY_DEFAULT() 매크로에 문자열로 전달되지 않았습니다.

"해결되지 않은 외부 심볼 _ucm_instantiate"

서버가 QAxFactory 의 구현을 내보내지 않습니다. 프로젝트의 구현 파일 중 하나에서 QAXFACTORY_EXPORT() 매크로를 사용하여 팩토리를 인스턴스화하고 내보내거나, QAXCLASS() 또는 QAXFACTORY_DEFAULT() 매크로를 사용하여 기본 팩토리를 사용하십시오.

"_ucm_initialize가 이미 ...에 정의되어 있습니다."

서버가 QAxFactory 의 구현을 두 개 이상 내보내거나, 동일한 구현을 두 번 내보내는 경우입니다. 기본 팩토리를 사용하는 경우, QAXFACTORY_BEGIN() 또는 QAXFACTORY_DEFAULT() 매크로는 프로젝트 내에서 한 번만 사용해야 합니다. 서버가 여러 ActiveX 컨트롤을 제공하는 경우, 사용자 정의 QAxFactory 구현과 QAXFACTORY_EXPORT() 매크로를 사용하십시오.

QAxServer 바이너리 배포

Qt로 작성된 ActiveX 서버는 Qt를 공유 라이브러리로 사용하거나, Qt를 바이너리에 정적으로 링크할 수 있습니다. 두 방법 모두 상당히 큰 패키지가 생성됩니다(서버 바이너리 자체가 커지거나, Qt DLL을 함께 제공해야 하기 때문입니다).

독립 실행형 서버 설치

ActiveX 서버가 독립 실행형 애플리케이션으로도 실행될 수 있는 경우, 대상 시스템에 서버 실행 파일을 설치한 후 -regserver 명령줄 매개변수와 함께 서버 실행 파일을 실행하십시오. 그러면 서버에서 제공하는 컨트롤을 ActiveX 클라이언트에서 사용할 수 있게 됩니다.

인-프로세스 서버 설치

ActiveX 서버가 설치 패키지의 일부인 경우, Microsoft에서 제공하는 regsvr32 도구를 사용하여 대상 시스템에 컨트롤을 등록하십시오. 이 도구가 없는 경우, DLL을 설치 프로그램 프로세스에 로드하고 DllRegisterServer 심볼을 해결한 후 다음 함수를 호출하십시오:

HMODULE dll = LoadLibrary("myserver.dll");
typedef HRESULT(__stdcall *DllRegisterServerProc)();
DllRegisterServerProc DllRegisterServer =
    (DllRegisterServerProc)GetProcAddress(dll, "DllRegisterServer");

HRESULT res = E_FAIL;
if (DllRegisterServer)
    res = DllRegisterServer();
if (res != S_OK)
    // error handling

인터넷을 통한 서버 배포

웹 페이지에서 서버의 컨트롤을 사용하려면, 페이지를 보는 데 사용되는 브라우저가 해당 서버에 접근할 수 있도록 해야 하며, 페이지 내에 서버 패키지의 위치를 명시해야 합니다.

서버의 위치를 지정하려면 웹 사이트의 OBJECT 태그에서 CODEBASE 속성을 사용하십시오. 이 속성의 값은 서버 파일 자체, 서버에 필요한 다른 파일(예: Qt DLL)을 나열한 INF 파일, 또는 압축된 CAB 아카이브를 가리킬 수 있습니다.

INF 및 CAB 파일에 대한 설명은 ActiveX 및 COM 프로그래밍에 관한 거의 모든 서적은 물론, MSDN 라이브러리 및 다양한 온라인 리소스에서도 확인할 수 있습니다. 다음 예제에는 CAB 아카이브를 생성하는 데 사용할 수 있는 INF 파일이 포함되어 있습니다:

[version]
    signature="$CHICAGO$"
    AdvancedINF=2.0
 [Add.Code]
    simpleax.exe=simpleax.exe
 [simpleax.exe]
    file-win32-x86=thiscab
    clsid={DF16845C-92CD-4AAB-A982-EB9840E74669}
    RegisterServer=yes

Microsoft의 CABARC 도구를 사용하면 CAB 아카이브를 쉽게 생성할 수 있습니다:

cabarc N simpleax.cab simpleax.exe simple.inf

이 INF 파일들은 Qt의 정적 빌드를 전제로 하므로, INF 파일에는 다른 DLL에 대한 종속성이 나열되어 있지 않습니다. DLL에 의존하는 ActiveX 서버를 배포하려면 종속성을 추가하고, 아카이브와 함께 라이브러리 파일을 제공해야 합니다.

컨트롤 사용

ActiveX 컨트롤을 사용하려면(예: 웹 페이지에 삽입하기 위해) ` <object> ` HTML 태그를 사용하십시오.

<object ID="MyActiveX1" CLASSID="CLSID:ad90301a-849e-4e8b-9a91-0a6dc5f6461f">
   ...
<\object>

컨트롤의 속성을 초기화하려면 다음을 사용하십시오.

<object ID=...>
    <param name="name" value="value">
<\object>

웹 브라우저가 스크립트를 지원하는 경우, JavaScript, VBScript 및 양식을 사용하여 컨트롤을 제어할 수 있습니다. ActiveQt 예제에는 예제 컨트롤에 대한 데모 HTML 페이지가 포함되어 있습니다.

지원 및 미지원 ActiveX 클라이언트

다음 내용은 주로 ActiveX 컨트롤 및 클라이언트 애플리케이션에 대한 당사의 실험 결과를 바탕으로 한 것이며, 결코 완전한 목록은 아닙니다.

지원되는 클라이언트

다음 표준 애플리케이션들은 ActiveQt로 개발된 ActiveX 컨트롤과 호환됩니다. 일부 클라이언트는 인프로세스 컨트롤만 지원한다는 점에 유의하십시오.

  • 인터넷 익스플로러
  • Microsoft ActiveX 컨트롤 테스트 컨테이너
  • Microsoft Visual Studio 6.0
  • Microsoft Visual Studio.NET/2003
  • Microsoft Visual Basic 6.0
  • MFC 및 ATL 기반 컨테이너
  • Sybase PowerBuilder
  • ActiveQt 기반 컨테이너

Microsoft Office 응용 프로그램은 지원되지만, 컨트롤을 "삽입 가능(Insertable)" 개체로 등록해야 합니다. COM 클래스에 이 속성을 추가하려면 ` QAxFactory::registerClass `을 재구현하거나, ` Q_CLASSINFO ` 매크로를 사용하여 해당 클래스의 "삽입 가능" 클래스 정보를 "yes"로 설정하십시오.

지원되지 않는 클라이언트

다음 클라이언트 애플리케이션에서는 ActiveQt 기반 COM 개체가 작동하도록 구현하지 못했습니다.

  • Borland C++ Builder (버전 5 및 6)
  • Borland Delphi

일반적인 런타임 오류

서버가 응답하지 않음

시스템에서 서버를 시작할 수 없는 경우(작업 관리자를 통해 서버 프로세스가 실행 중인지 확인하십시오), 시스템 경로에 서버가 의존하는 DLL(예: Qt DLL!)이 누락되지 않았는지 확인하십시오. Dependency Walker를 사용하여 서버 바이너리의 모든 종속성을 확인하십시오.

서버가 실행 중이라면(예: 작업 관리자에 프로세스가 표시됨), 서버 디버깅에 대한 정보는 다음 섹션을 참조하십시오.

객체를 생성할 수 없음

빌드 과정에서 서버를 올바르게 빌드하고 등록할 수 있었으나, OLE/COM 객체 뷰어 애플리케이션 등을 통해 객체를 초기화할 수 없는 경우, 서버가 의존하는 DLL(예: Qt DLL)이 시스템 경로에 누락되지 않았는지 확인하십시오. 의존성 워커를 사용하여 서버 바이너리의 모든 의존성을 확인하십시오.

서버가 실행되는 경우, 서버 디버깅에 대한 정보는 다음 섹션을 참조하십시오.

COM 서버를 언로드하고 다시 로드할 때 충돌 발생

Active Qt COM 서버가 Qt Base에 포함된 모듈 이외의 Qt 모듈을 사용하는 경우, COM 서버를 프로세스 외부 COM 서버로 활성화해야 합니다. Qt Quick 과 같은 모듈을 포함하는 프로세스 내 COM 서버를 활성화하려고 하면, COM 서버를 언로드한 후 충돌이 발생할 수 있습니다.

COM 발신 호출 중 충돌 또는 예기치 않은 동작

프로세스 외 COM 서버는 클라이언트에 대한 발신 호출을 수행하는 동안에도 메시지 큐를 처리한다는 점에 유의하십시오. 클라이언트가 동시에 서버를 호출하는 경우, 이로 인해 예기치 않은 동작이나 충돌이 발생할 수 있습니다. 이러한 상황에서는 발신 호출이 반환되기 전에 수신 호출이 서버에서 먼저 실행됩니다. 특히, ActiveX 컨트롤이 클라이언트로 다시 호출을 수행하는 중에 클라이언트가 해당 컨트롤을 닫을 경우, 이로 인해 프로그램이 종료될 수 있습니다. 이러한 재진입 문제는 메시지 필터(IMessageFilter 및 CoRegisterMessageFilter)를 사용하여 완화할 수 있습니다.

런타임 오류 디버깅

Visual Studio에서 인-프로세스 서버를 디버그하려면 서버 프로젝트를 활성 프로젝트로 설정하고, 프로젝트 설정에서 "디버그 세션용 클라이언트 실행 파일"을 지정하십시오(예: ActiveX 테스트 컨테이너 사용). 코드에 중단점을 설정할 수 있으며, 디버그 버전을 설치한 경우 ActiveQt 및 Qt 코드를 단계별로 실행할 수도 있습니다.

실행 가능한 서버를 디버그하려면 디버거에서 애플리케이션을 실행하고 명령줄 매개변수 ` -activex`를 사용하여 시작하십시오. 그런 다음 클라이언트를 시작하고 ActiveX 컨트롤의 인스턴스를 생성하십시오. COM은 ActiveX 컨트롤을 생성하려는 다음 클라이언트에 대해 기존 프로세스를 사용합니다.

클래스 정보 및 튜닝

각 COM 클래스에 속성을 제공하려면 Qt의 메타 객체 시스템의 일부인 Q_CLASSINFO 매크로를 사용하십시오.

키값의 의미
버전클래스의 버전(기본값은 1.0)
설명클래스를 설명하는 문자열입니다.
ClassID클래스 ID입니다. 지정되지 않은 경우 ` QAxFactory::classID `를 재구현해야 합니다.
InterfaceID인터페이스 ID입니다. 지정되지 않은 경우 QAxFactory::interfaceID 를 재구현해야 합니다.
EventsID이벤트 인터페이스 ID입니다. 지정되지 않은 경우 COM 이벤트로 노출되는 신호가 없습니다.
DefaultProperty지정된 속성은 이 클래스의 기본 속성을 나타냅니다. 예를 들어, 푸시 버튼의 기본 속성은 "text"가 됩니다.
DefaultSignal지정된 신호는 이 클래스의 기본 신호를 나타냅니다. 예를 들어, 푸시 버튼의 기본 신호는 "clicked"입니다.
LicenseKey객체를 생성하려면 지정된 라이선스 키가 필요합니다. 라이선스가 부여된 컴퓨터에서만 사용할 수 있도록 하려면 키를 비워둘 수 있습니다. 기본적으로 클래스에는 라이선스가 부여되지 않습니다. 다음 섹션도 참조하십시오.
StockEvents값이 "yes"인 경우 객체는 기본 이벤트를 노출합니다. QAxFactory::hasStockEvents()를 참조하십시오.
ToSuperClass객체는 value에 지정된 클래스 이름을 포함하여 그 상위 모든 클래스의 기능을 노출합니다. QAxFactory::exposeToSuperClass()을 참조하십시오.
Insertable값이 "yes"인 경우, 해당 클래스는 "Insertable"로 등록되며 OLE 2 컨테이너(예: Microsoft Office)에 나열됩니다. 이 속성은 기본적으로 설정되어 있지 않습니다.
Aggregatable값이 "no"인 경우, 해당 클래스는 집합 처리를 지원하지 않습니다. 기본적으로 집합 처리는 지원됩니다.
생성 가능값이 "no"인 경우, 클라이언트는 해당 클래스를 생성할 수 없으며 다른 클래스의 API를 통해서만 사용할 수 있습니다(즉, 해당 클래스는 하위 유형입니다).
RegisterObject값이 "yes"인 경우, 이 클래스의 객체는 OLE에 등록되며 실행 중인 객체 테이블을 통해 액세스할 수 있습니다(즉, 클라이언트는 이 클래스의 이미 실행 중인 인스턴스에 연결할 수 있습니다). 이 속성은 프로세스 외부 서버에서만 지원됩니다.
MIME이 객체는 값에 지정된 형식의 데이터와 파일을 처리할 수 있습니다. 값의 형식은 mime:확장자:설명입니다. 여러 형식은 세미콜론으로 구분됩니다.
CoClassAlias생성된 IDL 및 레지스트리에서 사용되는 클래스 이름입니다. 이는 네임스페이스에 속한 C++ 클래스의 경우 특히 유용합니다. 기본적으로 ActiveQt는 IDL이 컴파일되도록 "::"를 제거합니다.
구현된 카테고리쉼표로 구분된 카테고리 ID(CATID) UUID 목록입니다. "control", "insertable" 등과 더불어 추가적인 컨테이너 기능을 지정하기 위한 일반적인 메커니즘입니다. 일반적인 CATID로는 CATID_InternetAware ("{0DE86A58-2BAA-11CF-A229-00AA003D7352}"), CATID_SafeForScripting ("{7DD95801-9882-11CF-9FA9-00AA006C42C4}") 및 사용자 정의 CATID 값이 있습니다.

키와 값 모두 대소문자를 구분한다는 점에 유의하십시오.

다음은 자체 API만 노출하고 Microsoft Office 응용 프로그램의 "개체 삽입" 대화 상자에서 사용할 수 있는 클래스의 버전 2.0을 선언한 것입니다.

class MyActiveX : public QWidget
{
    Q_OBJECT
    Q_CLASSINFO("Version", "2.0")
    Q_CLASSINFO("ClassID", "{7a4cffd8-cbcd-4ae9-ae7e-343e1e5710df}")
    Q_CLASSINFO("InterfaceID", "{6fb035bf-8019-48d8-be51-ef05427d8994}")
    Q_CLASSINFO("EventsID", "{c42fffdf-6557-47c9-817a-2da2228bc29c}")
    Q_CLASSINFO("Insertable", "yes")
    Q_CLASSINFO("ToSuperClass", "MyActiveX")
    Q_PROPERTY(...)

public:
    MyActiveX(QWidget *parent = 0);

    ...
};

라이선스가 부여된 컴포넌트 개발

컴포넌트를 개발하는 경우, 해당 컴포넌트를 인스턴스화할 수 있는 사용자를 제어하고 싶을 수 있습니다. 서버 바이너리는 모든 클라이언트 컴퓨터로 전송되어 등록될 수 있으므로, 누구나 자신의 소프트웨어에서 해당 컴포넌트를 사용할 수 있습니다.

컴포넌트 라이선싱은 다양한 기법을 사용하여 수행할 수 있습니다. 예를 들어, 컨트롤을 생성하는 코드가 라이선스 키를 제공하거나, 컨트롤이 실행될 컴퓨터에 라이선스가 부여되어야 할 수 있습니다.

Qt 클래스를 라이선스가 적용된 것으로 표시하려면 Q_CLASSINFO() 매크로를 사용하여 "LicenseKey"를 지정하십시오.

class MyLicensedControl : public QWidget
{
    Q_OBJECT
    Q_CLASSINFO("LicenseKey", "<key string>")
    ...
};

이 키는 라이선스가 부여되지 않은 컴퓨터에서 MyLicensedControl 의 인스턴스를 생성하기 위해 필요합니다. 이제 라이선스를 보유한 개발자는 자신의 애플리케이션과 함께 서버 바이너리를 재배포할 수 있으며, 이 바이너리는 "LicenseKey"의 값을 사용하여 컨트롤을 생성합니다. 반면, 애플리케이션 사용자는 라이선스 키 없이는 컨트롤을 생성할 수 없습니다.

컨트롤에 대한 단일 라이선스 키로 충분하지 않은 경우(예: 서로 다른 개발자에게 각기 다른 라이선스 키를 부여하려는 경우), 컨트롤에 라이선스가 필요함을 나타내기 위해 빈 키를 지정하고, QAxFactory::validateLicenseKey() 메서드를 재구현하여 시스템에 라이선스가 존재하는지(예: 라이선스 파일을 통해) 확인할 수 있습니다.

추가 인터페이스

ActiveQt 서버에서 제공하는 ActiveX 컨트롤은 OLE 사양을 구현하기 위한 최소한의 COM 인터페이스 세트를 지원합니다. ActiveX 클래스가 ` QAxBindable ` 클래스를 상속받을 경우, 추가적인 COM 인터페이스를 구현할 수도 있습니다.

QAxAggregated 의 새로운 하위 클래스를 생성하고, 다중 상속을 사용하여 추가적인 COM 인터페이스 클래스를 상속받도록 하십시오.

class AxImpl : public QAxAggregated, public ISomeCOMInterface
{
public:
    AxImpl() {}

    long queryInterface(const QUuid &iid, void **iface);

    // IUnknown
    QAXAGG_IUNKNOWN

    // ISomeCOMInterface
    ...
}

추가 COM 인터페이스를 지원하려면 QAxAggregated::queryInterface() 함수를 재구현하십시오.

long AxImpl::queryInterface(const QUuid &iid, void **iface)
{
    *iface = 0;
    if (iid == IID_ISomeCOMInterface)
        *iface = (ISomeCOMInterface *)this;
    else
        return E_NOINTERFACE;

    AddRef();
    return S_OK;
}

ISomeCOMInterface 는 IUnknown 의 하위 클래스이므로 QueryInterface(), AddRef() 및 Release() 함수를 구현해야 합니다. 이를 위해 클래스 정의에서 QAXAGG_IUNKNOWN 매크로를 사용하십시오. IUnknown 함수를 수동으로 구현하는 경우, QAxAggregated::controllingUnknown() 함수가 반환하는 인터페이스 포인터로 호출을 위임하십시오. 예:

HRESULT AxImpl::QueryInterface(REFIID iid, void **iface)
{
    return controllingUnknown()->QueryInterface(iid, iface);
}

queryInterface() 구현에서는 IUnknown 인터페이스 자체를 지원하지 마십시오.

COM 인터페이스의 메서드를 구현하고, 컨트롤을 구현하는 QObject 하위 클래스를 호출해야 하는 경우 QAxAggregated::object()을 사용하십시오.

QAxBindable 하위 클래스에서는 QAxBindable::createAggregate()을 구현하여 QAxAggregated 하위 클래스의 새 객체를 반환하십시오.

class MyActiveX : public QWidget, public QAxBindable
{
    Q_OBJECT

public:
    MyActiveX(QWidget *parent);

    QAxAggregated *createAggregate()
    {
        return new AxImpl();
    }
};

ActiveQt 프레임워크도 참조하십시오 .

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.