examples/activex_cpp/hello_world/hello_world.cpp
116 lines
// ============================================================================
// hello_world -- C++ ActiveX (COM) port.
//
// WHY THIS TREE EXISTS, AND WHY IT IS NOT examples/activex/
// ---------------------------------------------------------
// examples/activex/ already holds this example as VBScript. Both the .vbs and
// a .cpp beside it would write the SAME out.pdf in the SAME directory, so
// whichever ran last would be the one graded, and the comparer -- which keys
// on the path relative to its language root -- could not tell the two ports
// apart. A sibling language tree is what tools/compare_lang_outputs.py already
// understands: it walks examples/<lang>/** and diffs each artifact against
// examples/delphi/**, so this tree is gradeable with no change to that script.
//
// WHAT IS DIFFERENT FROM examples/cpp/
// ------------------------------------
// examples/cpp/hello_world calls the FLAT C API: pdfSetFontA(pdf, ...) with an
// explicit PPDF handle. This calls the SAME engine through the COM object the
// VBScript port uses -- CreateObject("LumasPdf.PDF") -- so the handle becomes
// the `this` of an interface pointer and the pdf* prefix disappears. That is
// the whole point: it exercises wrappers/activex, which until now no compiled
// language exercised at all.
//
// #import, NOT late-bound IDispatch. The type library is right there
// (wrappers/activex/LumasPdfAX.tlb) and #import turns it into typed smart
// pointers, so a wrong argument count or type is a COMPILE error. A late-bound
// Invoke() harness would push every one of those into a runtime E_INVALIDARG
// with no indication of which call was wrong -- across 80+ generated files
// that is the difference between a build log and an afternoon.
//
// NOTE ON RaiseExceptions: the .vbs sets it so engine errors surface as script
// errors. #import's generated wrappers already throw _com_error on a failed
// HRESULT, so the same setting gives the same behaviour here, and the catch
// below reports it rather than letting a failure look like a clean exit.
// ============================================================================
#import "..\\..\\..\\wrappers\\activex\\LumasPdfAX.tlb" no_namespace named_guids
#include <windows.h>
#include <cstdio>
#include <string>
#include <ctime>
// The output goes beside the EXECUTABLE, exactly as every other port does it,
// so the comparer finds it at the same relative path as delphi's.
static std::string exeDir() {
char buf[MAX_PATH];
GetModuleFileNameA(nullptr, buf, MAX_PATH);
std::string s(buf);
size_t p = s.find_last_of("\\/");
return p == std::string::npos ? std::string(".") : s.substr(0, p);
}
// The Delphi original writes DateTimeToStr(Date + Time) and the .vbs writes
// CStr(Now); both render with the SYSTEM short-date + long-time patterns. Read
// the same two locale values rather than inventing a strftime layout -- the
// comparer diffs whitespace-separated word counts, so dropping the AM/PM
// designator would report CONTENT_DIFF for a reason unrelated to the engine.
// (Same finding, same fix, as examples/cpp/hello_world/hello_world.cpp.)
static std::string nowLikeDelphi() {
char d[128] = {0}, t[128] = {0};
GetDateFormatA(LOCALE_USER_DEFAULT, DATE_SHORTDATE, nullptr, nullptr, d, sizeof(d));
GetTimeFormatA(LOCALE_USER_DEFAULT, 0, nullptr, nullptr, t, sizeof(t));
return std::string(d) + " " + t;
}
int main() {
HRESULT hr = CoInitialize(nullptr);
if (FAILED(hr)) { std::fprintf(stderr, "CoInitialize failed: 0x%08lx\n", (unsigned long)hr); return 1; }
int rc = 0;
try {
const std::string outFile = exeDir() + "\\out.pdf";
// ILumasPDF, not IPDF: the type library names the default interface
// ILumasPDF (see the generated LumasPdfAX.tlh), and #import derives the
// smart pointer name from that. The .tlh also declares
// ILumasPDFContentParser / ILumasPDFReport / ILumasPDFReportJob -- so
// the psr* and rpt* examples are NOT unportable as a first scan of the
// .ridl suggested; they simply hang off secondary interfaces rather
// than off this one.
ILumasPDFPtr pdf;
hr = pdf.CreateInstance(L"LumasPdf.PDF");
if (FAILED(hr)) _com_issue_error(hr);
pdf->RaiseExceptions = VARIANT_TRUE;
pdf->CreateNewPDFA(""); // output opened later
pdf->SetDocInfoA(1, "Delphi Example project"); // diCreator
pdf->SetDocInfoA(5, "My first PDF output"); // diTitle
pdf->Append();
pdf->SetFontA("Arial", 1, 30, VARIANT_TRUE, 2); // fsItalic, cp1252
const std::string txt = "My first PDF output...\r\r" + nowLikeDelphi();
pdf->WriteFTextA(1, txt.c_str()); // taCenter
pdf->EndPage();
if (pdf->HaveOpenDoc() != 0)
pdf->OpenOutputFileA(outFile.c_str());
if (pdf->CloseFile() != 0)
std::printf("OK: %s\n", outFile.c_str());
} catch (const _com_error& e) {
// ErrorMessage() returns TCHAR*, which is char* in this (non-UNICODE)
// build -- mixing it with %ws is a type error, not a formatting nicety.
// Take the BSTR description when there is one and fall back to the
// narrow system message otherwise.
const _bstr_t d = e.Description();
if (d.length())
std::fprintf(stderr, "COM error 0x%08lx: %ws\n",
(unsigned long)e.Error(), (const wchar_t*)d);
else
std::fprintf(stderr, "COM error 0x%08lx: %s\n",
(unsigned long)e.Error(), e.ErrorMessage());
rc = 1;
}
CoUninitialize();
return rc;
}
This file is in the SDK at examples/activex_cpp/hello_world/hello_world.cpp.
The build fails if this page and that file ever differ.