gVimを自分のアプリに埋め込む

追記: ソースコードを少し修正。あと、このやりかただと複数起動ができないので何か方法を考える必要がありそう。
追記2: 実行前にgvimをOLEオートメーションサーバとして登録する必要あり。gvim.exe -register。詳しくは help if_ole を。

gVimにはCOMインターフェイスがあるというので、試してみた。

こんな感じになる。といってもこれだけだと普通のgVimと見た目は一緒になってしまうので余り面白味がないんだけど。
f:id:moriyoshi:20081221011000p:image

if_ole.hVimソースコードから拾うかsourceforge.netのSubversionレポジトリから落としてきて、次のコードと同じディレクトリに置いた後、コンパイル&リンクする。

VC++の場合

C>cl vimembed.c user32.lib kernel32.lib uuid.lib ole32.lib oleaut32.lib

gcc (cygwin) の場合

C>gcc -mno-cygwin -mwindows -o vimembed vimembed.c -luuid -lole32 -loleaut32

vimembed.c

#define COBJMACROS
#include <windows.h>
#include <objbase.h>
#include <oleidl.h>
#include <ole2.h>
#include <unknwn.h>
#include <stddef.h>
#include "if_ole.h"

typedef struct _MyWndData {
    HWND hWnd;
    IVim *vim;
    HWND hWndVim;
    PROCESS_INFORMATION vimProcInfo;
} MyWndData;

static const char vimprog[] = "C:\\Program Files\\Vim\\vim70\\gvim.exe";
static const char progname[] = "vimembed";
static LPCOLESTR vimProgID = OLESTR("Vim.Application");

static const IID IID_IVim = {
    0x0F0BFAE2, 0x4C90, 0x11d1, { 0x82,0xD7,0x00,0x04,0xAC,0x36,0x85,0x19 }
};

static LPVOID Alloc(SIZE_T sz)
{
    return HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sz);
}

static void Free(LPVOID p)
{
    HeapFree(GetProcessHeap(), 0, p);
}

static void Error(LPCTSTR lpszFormat, ...)
{
    CHAR buf[2048]; // XXX
    va_list ap;
    va_start(ap, lpszFormat);
    wvsprintf(buf, lpszFormat, ap);
    va_end(ap);
    MessageBox(NULL, buf, "Error", MB_OK | MB_ICONEXCLAMATION);
}

BOOL CALLBACK MyWndData_EnumWndCallback(HWND hWnd, LPARAM lParam)
{
    *((BOOL*)lParam) = TRUE;
    return TRUE;
}

MyWndData* MyWndData_New(HWND hWnd)
{
    MyWndData* retval = Alloc(sizeof(MyWndData));
    retval->vimProcInfo.hProcess = INVALID_HANDLE_VALUE;
    retval->vimProcInfo.hThread = INVALID_HANDLE_VALUE;
    retval->hWnd = hWnd;
    retval->vim = NULL;

    {
        STARTUPINFO si;
        si.cb = sizeof(STARTUPINFO);
        si.lpReserved = NULL;
        si.lpDesktop = NULL;
        si.lpTitle = NULL;
        si.dwFlags = STARTF_USESHOWWINDOW;
        si.wShowWindow = SW_HIDE;
        si.cbReserved2 = 0;
        si.lpReserved2 = NULL;
        if (!CreateProcess(vimprog, NULL, NULL, NULL, FALSE,
                CREATE_DEFAULT_ERROR_MODE,
                NULL, NULL, &si, &retval->vimProcInfo)) {
            Error("Failed to launch %s (%d)", vimprog, GetLastError());
            goto fail;
        }
    }

    {
        BOOL ready = FALSE;
        do {
            EnumThreadWindows(retval->vimProcInfo.dwThreadId,
                MyWndData_EnumWndCallback, (LPARAM)&ready);
            Sleep(10);
        } while (!ready);
    }

    {
        CLSID vimClsid;
        HRESULT err;
        err = CLSIDFromProgID(vimProgID, &vimClsid);
        if (S_OK != err) {
            Error("Failed to retrieve clsid (%08x)", err);
            goto fail;
        }
        err = CoCreateInstance(&vimClsid, NULL, CLSCTX_LOCAL_SERVER,
                &IID_IVim, (void **)&retval->vim);
        if (S_OK != err) {
            Error("Failed to create OLE object (%08x)", err);
            goto fail;
        }
        err = IVim_GetHwnd(retval->vim, (UINT_PTR*)&retval->hWndVim);
        if (S_OK != err) {
            Error("Failed to retrieve window handle (%08x)", err);
            goto fail;
        }
    }

    return retval;
fail:
    if (INVALID_HANDLE_VALUE != retval->vimProcInfo.hThread)
        CloseHandle(retval->vimProcInfo.hThread);
    if (INVALID_HANDLE_VALUE != retval->vimProcInfo.hProcess) {
        TerminateProcess(retval->vimProcInfo.hProcess, 255);
        CloseHandle(retval->vimProcInfo.hProcess);
    }
    if (retval->vim)
        IUnknown_Release(retval->vim);
    Free(retval);
    return NULL;
}

void MyWndData_Delete(MyWndData *data)
{
    CloseHandle(data->vimProcInfo.hProcess);
    CloseHandle(data->vimProcInfo.hThread);
    IUnknown_Release(data->vim);
    Free(data); 
}

static LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
    case WM_CREATE:
        {
            MyWndData *data = MyWndData_New(hWnd);
            if (!data)
                return -1;
            SetWindowLong(hWnd, 0, (LONG)data);
            {
                BSTR str = SysAllocString(OLESTR("\x1b:set guioptions-=T\n"));
                if (str) {
                    IVim_SendKeys(data->vim, str);
                    SysFreeString(str);
                }
            }
            SetWindowLong(data->hWndVim, GWL_STYLE, WS_CHILD | WS_VISIBLE);
            if (!SetParent(data->hWndVim, hWnd)) {
                Error("Failed to change the parent window (%d)", GetLastError());
                return -1;
            }
        }
        break;

    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;

    case WM_ERASEBKGND:
        return 0;

    case WM_SIZE:
        {
            MyWndData *data = (MyWndData*)GetWindowLong(hWnd, 0);
            RECT rc;
            GetClientRect(hWnd, &rc);
            MoveWindow(data->hWndVim,
                rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
                TRUE);
        }
        return 0;

    case WM_DESTROY:
        {
            MyWndData *data = (MyWndData*)GetWindowLong(hWnd, 0);
            if (data)
                MyWndData_Delete(data);
        }
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return 0;
}

INT APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hInstPrev, LPSTR lszCmdLine, INT nCmdShow)
{
    //HINSTANCE hInstance = 0, hInstPrev = 0;
    int err = 0;
    ATOM aWndClass = (ATOM)NULL;
    HWND hWnd = (HWND)NULL;

    CoInitialize(0);

    if (hInstPrev != (HINSTANCE)NULL)
        return 2;

    {
        WNDCLASSEX wndClassDesc;
        wndClassDesc.cbSize = sizeof(wndClassDesc);
        wndClassDesc.style = 0;
        wndClassDesc.lpfnWndProc = MyWndProc;
        wndClassDesc.cbClsExtra = 0;
        wndClassDesc.cbWndExtra = sizeof(MyWndData*);
        wndClassDesc.hInstance = hInstance;
        wndClassDesc.hIcon = NULL;
        wndClassDesc.hIconSm = NULL; 
        wndClassDesc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndClassDesc.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
        wndClassDesc.lpszMenuName = NULL;
        wndClassDesc.lpszClassName = progname;
        aWndClass = RegisterClassEx(&wndClassDesc);
        if (aWndClass == (ATOM)NULL) {
            err = 1;
            goto out;
        }
    }

    hWnd = CreateWindowEx(WS_EX_APPWINDOW, (LPCTSTR)(DWORD)aWndClass, progname, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, (HWND)NULL, (HMENU)NULL, hInstance, NULL);
    if (hWnd == NULL) {
        err = 1;
        goto out;
    }

    {
        MSG msg;
        int i;
        while ((i = GetMessage(&msg, NULL, 0, 0)) != 0) {
            if (i == -1) {
                err = 1;
                goto out;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        err = msg.wParam;
    } 

out:
    if (aWndClass != (ATOM)NULL)
        UnregisterClass((LPCTSTR)(DWORD)aWndClass, hInstance);
    return err;
}