トップC++入門 > DLLの作成と使用
DLLの作成と使用

1.DLLの作成と使用

先に作ったGPSセンサプログラムはC#で開発している地図システムで使用する ため、DLL化する。

このプログラムで公開する関数は bool GPSMonitor() である。 __declspec(dllexport) bool GPSMonitor() とするだけでよい。 コンパイルするとき、オプション /LD を付けるだけでよい。

// cl /LD /Fe./bin\gpsmonitor.dll /EHsc gps\gpsmonitor.cpp
#include <atlbase.h>
#define INITGUID 1
#include "Guiddef.h"
#include "SensorsApi.h"
#include "Sensors.h"

class CSensorEventSink : public ISensorEvents { 
  public: 
    // ISensorEvents で定義されているイベント 
    STDMETHOD (OnStateChanged)(ISensor* pSensor, SensorState state); 
    STDMETHOD (OnLeave)(REFSENSOR_ID sensorID); 
    STDMETHOD (OnDataUpdated)(ISensor* pSensor, ISensorDataReport* pNewData); 
    STDMETHOD (OnEvent)(ISensor* pSensor, REFGUID eventId, IPortableDeviceValues* pEventData);
 
    // 以下は、COMのおまじない 
    CSensorEventSink() : m_lRefCount(0) {} 

    STDMETHODIMP QueryInterface(REFIID riid, void **ppObject ) { 
        *ppObject = 0 ; 
        if (riid == __uuidof(ISensorEvents)) { 
            *ppObject = reinterpret_cast<ISensorEvents*>(this); 
        } else if (riid == IID_IUnknown) { 
            *ppObject = reinterpret_cast<IUnknown*>(this); 
        } else { 
            return E_NOINTERFACE; 
        } 
        (reinterpret_cast<IUnknown*>(*ppObject))->AddRef(); 
        return S_OK; 
    } 

    ULONG _stdcall AddRef() { return ++m_lRefCount; } 

    ULONG _stdcall Release() { 
        ULONG lRet = --m_lRefCount; 
        if (m_lRefCount == 0) delete this; 
        return lRet; 
    } 
 
private: 
    long m_lRefCount; 
};

STDMETHODIMP CSensorEventSink::OnDataUpdated(ISensor *pS, ISensorDataReport *pData) { 
    SYSTEMTIME st; 
    PROPVARIANT lonValue, latValue, altValue; 
    if (pData == NULL || pS == NULL) return E_INVALIDARG;
    HRESULT hr = pData->GetTimestamp(&st); 
    hr = pData->GetSensorValue(SENSOR_DATA_TYPE_LONGITUDE_DEGREES, &lonValue); 
    hr = pData->GetSensorValue(SENSOR_DATA_TYPE_LATITUDE_DEGREES, &latValue); 
    hr = pData->GetSensorValue(SENSOR_DATA_TYPE_ALTITUDE_ANTENNA_SEALEVEL_METERS, &altValue); 
    if (SUCCEEDED(hr)){ 
        DOUBLE lonvalue = lonValue.dblVal; 
        DOUBLE latvalue = latValue.dblVal; 
        DOUBLE altvalue = altValue.dblVal; 
        printf("%d/%02d/%02d %02d:%02d:%02d ", st.wYear, 
                st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
        printf("%.6f %.6f %.1f\n", lonvalue, latvalue, altvalue);
    } 
    return S_OK; 
}

STDMETHODIMP CSensorEventSink::OnStateChanged(ISensor* pSensor, SensorState state) {
    return S_OK; 
}
     
STDMETHODIMP CSensorEventSink::OnLeave(REFSENSOR_ID sensorID) {
    return S_OK; 
}

STDMETHODIMP CSensorEventSink::OnEvent(ISensor* pSensor, REFGUID eventId, 
                                        IPortableDeviceValues* pEventData) {
    return S_OK; 
}

__declspec(dllexport)
bool GPSMonitor() {
    CoInitializeEx(NULL, COINIT_MULTITHREADED);

    CComPtr<ISensorManager> pSensorManager = NULL;  // センサーマネージャを格納する変数
    CComPtr<ISensor> pGPSSensor = NULL;             // 取得したセンサーを格納する変数 
    IPortableDeviceValues* pPropsToSet = NULL;      // Input
    IPortableDeviceValues* pPropsReturn = NULL;     // Output
    ULONG ulReportInterval;

    // センサーマネージャの取得 
    HRESULT hr = CoCreateInstance(__uuidof(SensorManager), NULL, CLSCTX_INPROC_SERVER, 
                                  __uuidof(ISensorManager), (LPVOID*)&pSensorManager); 
 
    CComPtr<ISensorCollection> pGPSSensors;         // センサーコレクション変数 

    // GPSセンサーを取り出す 
    hr = pSensorManager->GetSensorsByType(SENSOR_TYPE_LOCATION_GPS, &pGPSSensors); 
    if (!SUCCEEDED(hr)) { 
        printf("GPSセンサの取得に失敗しました");
        return false;
    }
    CComPtr<ISensor> pSensor; 
    hr = pGPSSensors->GetAt(0, &pSensor);   // 先頭のセンサを取得する
    CSensorEventSink* pEventSink = new CSensorEventSink(); 
    pSensor->SetEventSink(pEventSink);
    return true;
}

このDLL関数を使うプログラムを下に示す。プロトタイプ宣言に __declspec(dllimport) を付けるだけでよい。 コンパイルするとき、gpsmonitor.dll を作成したとき同時に作成される gpsmonitor.lib を参照する。

// cl /Fe./bin\gps.exe /EHsc gps\gps.cpp bin\gpsmonitor.lib
#include <windows.h>

__declspec(dllimport) bool GPSMonitor(void);

int main() {
    if (!GPSMonitor()) return 1;
    Sleep(100000);
    return 0;
}

実際に使うためには、GPSMonitor関数に適切な引数を追加する必要がある。 GPSセンサから得るデータは 時刻、経度、緯度、高度(海抜) である。

現在の地図システムでは、位置情報を次のクラス Loc に格納している。

public class Loc {
    public DateTime datetime;
    public double lon, lat, alt;
	// 後略
}

ファイルには次のようなCSV形式で出力している。日付別のファイルのため、時:分:秒 のみを記録している。

10:49:36,139.2750303,34.5467752,65.2

そこで、GPSMonitor関数の引数を次のようにしてみる。

bool GPSMonitor(GPSData *pGPSData, int *pPos);

使う側でGPSセンサのデータを格納する循環バッファを確保して、 このアドレスとサイズを格納した変数Posのアドレスを引数とする。

int Pos = 100;
GPSData *pGPSData = calloc(Pos, sizeof(GPSData));
GPSMonitor(pGPSData, &Pos);
// 定期的に Pos の値をチェックして、前回からの登録分を取り込む

GPSMonitorでは、Posの値を最初に読み取った後、循環バッファのどこまでデータを格納したかを Pos に設定する。

typedef struct {
    SYSTEMTIME time;
    double lon, lat, alt;
} GPSData;

private struct SYSTEMTIME {
    public ushort wYear, wMonth, wDayOfWeek, wDay; 
    public ushort wHour, wMinute, wSecond, wMilliseconds; 
}

private struct GPSData {
    SYSTEMTIME time;
    double lon, lat, alt;
}

[DllImport("gpsmonitor.dll")]
private extern static bool GPSMonitor(ref GPSData lpGPSData, ref int pos);

A.リファレンス

[1]