Ivyware TargetCore Facade & COM layer C++ · COM · PowerShell · VBScript · C# · Java Melbourne, AU
Ivyware

Ivyware/TargetCore/Facade

TargetFacade — the kernel without the macros

One header.
One init.
Any language.

The kernel's native surface is MFC classes, factories and BEGIN_*_MAP macros — right for the kernel, wrong for a caller who just wants a hub. TargetFacade wraps it in a flat, macro-free ABI: one header, one import library, lambdas instead of message maps, and a single Listen/Connect pair for every transport. TargetCom then puts the same hubs one line away from PowerShell, VBScript, VBA, C# and anything else that can say CreateObject.

0 macros in client code 1 Listen / Connect pair, 4 transports 1 line of binding from a script HRESULT in, exception out 4 client tiers, all 12 harnesses green

Same program, four languages

Two hubs, one wire, one message. Pick a language tab and watch the same seven calls light up the same diagram — the facade is what makes the four listings line-for-line the same shape.

P2PF::NETWORK one init object — StartupP2Pmsg, WSAStartup and hub lifetime all inside ABI 11 HUB Demo onTopic("chat", λ) HANDLER · NO MESSAGE MAP LISTEN tcp://:7788 OnMessage(Demo.Client, chat, "hello") ON THE HUB'S OWN PUMP THREAD BEGIN_P2PeerMsg_MAP(CMyHub, P2PeerHub) ON_P2PeerMsg("chat", OnChat) END_P2PeerMsg_MAP() WSAStartup · StartupP2Pmsg · SpawnHub ConWsa::ServiceFactory · PostP2PeerCon WHAT YOU NO LONGER WRITE TCP 7788 chat HUB Demo.Client CONNECT tcp://127.0.0.1:7788 sendText("Demo", "chat", "hello") THE DIALLING SIDE SPEAKS FIRST
p2pf::Network net;

Caller code


        

What the facade does for you

What the facade removes

Before and after

The kernel's native surface is a set of MFC classes wired together with map macros. That surface is right for the kernel, which needs the full state machine, and wrong for a caller who wants a hub, a handler and a wire. The facade is the difference, written once.

Kernel API — what a derived hub looks like

class CChatHub : public P2PeerHub {
    MapResult OnChat(P2PeerMsg* pMsg);
    DECLARE_P2PeerMsg_MAP()
};
BEGIN_P2PeerMsg_MAP(CChatHub, P2PeerHub)
    ON_P2PeerMsg("chat", OnChat)
END_P2PeerMsg_MAP()

StartupP2Pmsg(); WSAStartup(MAKEWORD(2,2), &wsa);
CChatHub hub;
hub.CreateHub(L"Demo", 2);
hub.SpawnHub();
P2PeerConWsa* pSvc =
    P2PeerConWsa::ServiceFactory(L"Demo.Client", 7788);
hub.PostP2PeerCon(pSvc);
// … and the same again, in reverse, for the client

TargetFacade — the same hub

#include "TargetFacadeFn.hpp"

p2pf::Network net;
p2pf::Hub hub = net.createHub(L"Demo");

hub.onTopic(L"chat", [](const p2pf::Message& m) {
    wprintf(L"%s: %s\n", m.source, m.text());
});

hub.listen(L"Demo.Client", L"tcp://:7788");

// nothing else. no derived class, no macro,
// no WinSock, no factory, no PostP2PeerCon.
// destructors tear it down in the right order.

Surface

Pure vtables, HRESULTs, flat parameters

The public ABI is a handful of interfaces reached through one exported factory, P2PF_CreateNetwork. No MFC in the header, no kernel type leaks through, and a client that includes only the facade's headers fails to compile if that ever changes. The optional TargetFacadeFn.hpp adds std::function handlers and RAII on top.

Append-only

Eleven ABI versions, one hard cut

Every version since the first appended slots rather than moving them, so a client built against ABI 3 still binds. The one exception was deliberate: eight typed ListenPipe/ConnectDmx-style verbs collapsed into one pair, and the old names were removed rather than kept as ceremony.

Refusals, not surprises

Wrong things fail at the call

A malformed endpoint is P2PF_E_ENDPOINT before anything is manufactured. A reserved P2Pmsg_* topic is P2PF_E_RESERVED_TOPIC. Pinging from inside a timer handler would deadlock the pump, so it is P2PF_E_PUMP_THREAD instead of a 3am mystery.

One pair of verbs, every transport

Endpoints

The transport is not a method name. It is the scheme inside the endpoint string, parsed in one place. A fifth transport is a parser entry, not two more vtable slots, two more dispids and two more wrappers per language.

Endpoint grammar
SchemeListenConnectKernel classRetries
tcptcp://:7788tcp://127.0.0.1:7788P2PeerConWsaYes
pipepipe://P2PmsgDemopipe://P2PmsgDemoP2PeerConPipeYes
dmxdmx://DemoServicedmx://DemoServiceP2PeerConDmxNo, so arm the listener first
serialserial://COM5serial://COM5P2PeerCon232No
omitted""""Resolved from the deployment map, else in-process DmxYes, on a budget

A whole topology from text — no endpoint in the code at all

// the map is a value: an ini section, a registry key, a config store
net.setEndpointMap(L"Demo        = tcp://:7788\n"
                   L"Demo.Client = tcp://10.0.0.7:7788\n");

net.link(L"Demo", L"Demo.Client");   // arms BOTH ends, listener first, in the order that works

The COM layer — the same API, one line of binding

TargetCom.dll

TargetCom is an in-process ATL server over the facade: dual interfaces, so early-bound clients get a vtable and late-bound ones get IDispatch, plus a connection-point event source. The mapping from the C++ ABI is mechanical, which is what keeps the two tiers the same shape.

Mapping

Mechanical, by rule

FacadeCOM
const wchar_t*BSTR
void* + sizeVARIANT holding SAFEARRAY(VT_UI1), or a string
BOOLVARIANT_BOOL
IP2PHubEvents sink_IP2PHubEvents dispinterface, connection point
HRESULTStraight through, with ISupportErrorInfo text

Objects

Three coclasses

ProgID / classRole
TargetCom.P2PNetworkCreatable. CreateHub, CreateSecureHub, Link, SetEndpointMap, CreateMessage, SetSecurityDir.
P2PHubReturned by CreateHub. Listen, Connect, Send, SendText, Broadcast, IsPeerUp, Ping, SetTimer, Disconnect, the read side, SecurityInfo.
P2PMessageA message with named fields: SetFieldText, SetField, GetField, then SendMsg.

Two deliberate departures from a blind copy

  • Broadcast returns a Boolean. The flat facade signals "no peer was up" with S_FALSE, and automation clients never see a success code. So the answer is a [retval]: True only if at least one peer took a copy, which is also the cheapest proof a script has that traffic actually moved.
  • Address, VersionString and MaxPayload are properties. That is what an automation client expects of a read-only value, and it is why $net.MaxPayload reads as a number in PowerShell without a call.

What a script gets that a C++ caller pays for

  • No lifetime management. No AddRef, no Release, no VariantClear. Objects go away when the variables do.
  • Errors with sentences. Every facade HRESULT arrives as a trappable error whose number is the code and whose description names the call, echoes the argument and says what would have been accepted.
  • Records without a wire format. CreateMessage, SetFieldText, SendMsg: three named fields cross to a C++ peer with no encoding agreed anywhere and no parser written on either side.
  • Secure hubs from one verb. CreateSecureHub does the key files, allow-list and revocation list a script could never express, and Link then exchanges the keys between two hubs of the same process as a memcpy.

The honest limitations, measured rather than assumed

  • Late-bound .NET hosts cannot sink COM events. PowerShell binds events only through an interop assembly for the coclass. C#, VB6 and C++ attach to the same connection point and get OnMessage, OnPeerUp, OnPeerDown, OnError, OnEvent and OnTimer. A script proves delivery with IsPeerUp and Broadcast.
  • A managed sink is apartment-agile. In C# the handlers run concurrently on the DLL's per-hub dispatch threads, not marshalled onto one thread. Counters need Interlocked; console output needs a lock.
  • The CLR rewrites part of the error contract. E_INVALIDARG arrives as ArgumentException, not COMException. Catch Exception and read Marshal.GetHRForException.

Secure hubs, from any tier

ABI 11

Authentication is a property of a hub, not of a link, so it is said once, when the hub is made. One flag in C++, one verb from a script. Everything the flag arranges is key files, an allow-list and a revocation list, and none of it appears in the header.

C++

p2pf::Hub a = net.createHub(L"Demo",        p2pf::P2PF_HUB_SECURE);
p2pf::Hub b = net.createHub(L"Demo.Client", p2pf::P2PF_HUB_SECURE);
net.link(L"Demo", L"Demo.Client");        // and that is the whole of it

a.securityFlags();        // REQUIRED | ARMED | CAN_SIGN | CAN_OPEN | REVOCATION
a.securityFingerprint();  // "4E3A-2E33-D5B5-EAB1-F13A-4175-0597-F01A"

VBScript — the same thing

Set a = net.CreateSecureHub("Demo")
Set b = net.CreateSecureHub("Demo.Client")
net.Link "Demo", "Demo.Client"

WScript.Echo a.SecurityInfo   ' flags and fingerprint, as text

' a secure hub and a plain one cannot link: P2PF_E_SECURITY, nothing armed

Msgcore has the same shape

MsgFacade · MsgcoreCom

The store underneath the kernel gets the same treatment: MsgFacade over the Msgcore object model, and MsgcoreCom over that. The one design rule that carries it is that a node is a path, not a pointer, so the kernel's "no handle survives a mutation" rule disappears for clients.

C++ — MsgFacadeFn.hpp

msgf::Library lib;
msgf::Store   st   = lib.createStore();
msgf::Node    root = st.root();

root.declareText(L"Title", L"Hello");
root.declareInt (L"Count", 42);
root.declareText(L"Lang",  L"en", msgf::Attr);   // an attribute, same verb

msgf::List items = root.declareList(L"Items");
items.addText(L"one");

for (msgf::Cursor c = root.cursor(); !c.end(); c.next())
    wprintf(L"%s\n", c.name().c_str());

st.save(L"demo.p2p");

PowerShell — MsgcoreCom.MsgStore

$store = New-Object -ComObject MsgcoreCom.MsgStore
$win = $store.Root.Declare("window", 0, $true)
$win.Declare("width",  1024, $true)
$win.Declare("title",  "Ivyware € Chartboard", $true)

$store.FieldAt(".window.width").Value     # 1024, by path

foreach ($c in $store.FieldAt(".window")) {  # DISPID_NEWENUM, a snapshot
    if ($c.TypeName -eq 'INT32') { $c.Value *= 2 }   # and the elements are live
}

$store.Save("demo.p2p")    # the heap image, verbatim, atomically

Proven the boring way

Example trees

The original twelve kernel harnesses were rewritten on every tier, and each tree exits with the number of failed checks. Same programs, same questions, four answers that agree.

The same harnesses, four client tiers
TreeTierLinks againstLines for the eleven bodies
DirectExamplesKernel API, C++ with MFCTargetCore.lib and MFC2376
FacadeExamplesTargetFacade, C++TargetFacade.lib only922
ComExamplesTargetCom, C++ and PowerShell and VBScriptole32, oleaut32 and the type library. Nothing of the kernel.827
dotNetExamplesTargetCom, C#A 99-line hand-written interop file. No TlbImp.893
PanamaJavaExamplesTargetFacade vtables, Java 22+ FFINo header, no jextract, no JNI. java.lang.foreign reads the vptr.595 for the whole binding

Where to start. C++ callers include TargetFacadeFn.hpp and link one import library. Automation callers register TargetCom.dll per user with regsvr32 /n /i:user, which needs no elevation, and create TargetCom.P2PNetwork. Java callers read PanamaJavaExamples for the vtable indices. The platforms page has the full matrix.