Delphi
RadStudio
cppBuilder
RADServer

Delphi Advent Calender 2017 21日目の記事に C++Builder のコード例を追記しようと思ったら、そもそもウィザード実行後の状態が違ったので差分を埋めてみる

RAD Server (EMS) のウィザードで C++ 向けに生成される初期コードは微妙に不親切でした……

Delphi Advent Calendar 2017 21日目の記事 Delphi/C++BuilderでREST APIをカンタンに実装できるRAD ServerでJSONを返す方法を3種類作ってみるの初稿では Delphi のことしか説明していなかったので、C++ のコード例も書こうかと思いました。(現在の記事は C++ のコード例も併記済みです)

※2018年3月にリリースされた RAD Studio/C++Builder 10.2 Tokyo Update 3 でC++向けの RAD Server のプロジェクトが作成できない場合は下記のパッチを適用してください。
https://cc.embarcadero.com/item/30832

というわけで、まずは RAD Server 向けプロジェクトにウィザードで C++ 向けプロジェクトを生成して動作確認と思ってそのままビルド、実行してみたら、ブラウザでアクセスしてもHTTPレスポンスのBodyがない。Delphi 向けのプロジェクトをそのままビルド、実行した場合はリソース名等が返されるのですが、C++ 向けの初期コードはそのようなサンプルが含まれておらず、応答が返されません。そこで、まずはその差分を埋めることにしてみます。

初期コードの違いを確かめてみる

いずれもリソース名は EMS にしました。またエンドポイントは下記のとおりです。要するにデフォルトと同じですね。

Endpoint URL
Get localhost:8080/EMS
GetItem localhost:8080/EMS/{item}

では Delphi と C++Builder での初期コードを引用してみましょう。

Delphi の例

unit Unit1;

// EMS Resource Module

interface

uses
  System.SysUtils, System.Classes, System.JSON,
  EMS.Services, EMS.ResourceAPI, EMS.ResourceTypes;

type
  [ResourceName('EMS')]
  TEMSResource1 = class(TDataModule)
  published
    procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    [ResourceSuffix('{item}')]
    procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
  end;

implementation

{%CLASSGROUP 'System.Classes.TPersistent'}

{$R *.dfm}

procedure TEMSResource1.Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
begin
  // Sample code
  AResponse.Body.SetValue(TJSONString.Create('EMS'), True)
end;

procedure TEMSResource1.GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  LItem: string;
begin
  LItem := ARequest.Params.Values['item'];
  // Sample code
  AResponse.Body.SetValue(TJSONString.Create('EMS ' + LItem), True)
end;


procedure Register;
begin
  RegisterResource(TypeInfo(TEMSResource1));
end;

initialization
  Register;
end.

Delphi を良く知らなかったとしても、Get や GetItem の最後にレスポンスを返す処理が書かれているなあ、くらいのことは分かりますよね。

C++Builder の例

//---------------------------------------------------------------------------
#pragma hdrstop

#include "Unit1.h"
#include <memory>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma classgroup "System.Classes.TPersistent"
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
__fastcall TEMSResource1::TEMSResource1(TComponent* Owner)
    : TDataModule(Owner)
{
}

void TEMSResource1::Get(TEndpointContext* AContext, TEndpointRequest* ARequest, TEndpointResponse* AResponse)
{
}

void TEMSResource1::GetItem(TEndpointContext* AContext, TEndpointRequest* ARequest, TEndpointResponse* AResponse)
{
    String item;
    item = ARequest->Params->Values["item"];
}

static void Register()
{
    std::auto_ptr<TEMSResourceAttributes> attributes(new TEMSResourceAttributes());
    attributes->ResourceName = "EMS";
    attributes->ResourceSuffix["GetItem"] = "{item}";
    RegisterResource(__typeinfo(TEMSResource1), attributes.release());
}

#pragma startup Register 32

C++ のコードをよ~く見てみると、Get は何もしておらず、GetItem はパラメータを取得しているだけです。つまり Get, GetItem が呼ばれても実質的な応答が返されないのですね。大したコードではないとはいえ、初期状態は同等のほうが良いなあと思います。

C++Builder 向けのコードを修正して Delphi の初期コードと同じ状態にする

というわけで、同じ状態にするためにこんなふうにしてみました。詳細についてはコードをご覧ください。また、ビルドしたら localhost:8080/EMS や localhost:8080/EMS/test などにアクセスしてみると応答が返って来ますから動いていることが確認できます。

//---------------------------------------------------------------------------
#pragma hdrstop

#include "Unit1.h"
#include <memory>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma classgroup "System.Classes.TPersistent"
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
__fastcall TEMSResource1::TEMSResource1(TComponent* Owner)
    : TDataModule(Owner)
{
}

void TEMSResource1::Get(TEndpointContext* AContext, TEndpointRequest* ARequest, TEndpointResponse* AResponse)
{
    // EMS という固定文字列で JSONString 型を作って SetValue で返す
    AResponse->Body->SetValue(new TJSONString("EMS"), true);
}

void TEMSResource1::GetItem(TEndpointContext* AContext, TEndpointRequest* ARequest, TEndpointResponse* AResponse)
{
    String item;
    item = ARequest->Params->Values["item"];

    // "EMS " + item という可変文字列で JSONString 型を作って SetValue で返す
    AResponse->Body->SetValue(new TJSONString("EMS " + item), true);
}

static void Register()
{
    std::auto_ptr<TEMSResourceAttributes> attributes(new TEMSResourceAttributes());

    // Delphi での [ResourceName('EMS')] に相当する設定
    attributes->ResourceName = "EMS";

    // Delphi での [ResourceSuffix('{item}')] に相当する設定
    attributes->ResourceSuffix["GetItem"] = "{item}";
    RegisterResource(__typeinfo(TEMSResource1), attributes.release());
}

#pragma startup Register 32

これでプロジェクトを新規に開いた場合の初期コードが同じ状態になりました。

See also

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/EMS_リソースの概要