🚨 Oracle not porting Rdb to x86 and EOL on Itanium 12/2027 🚨
Technical Capabilities
Having trouble finding what you need?
Get in touch with us, so we can answer your specific questions directly.
Get In Touch

STR$ - STRING Interface Routines

Overview

VX/STR$ is a call-compatible implementation of the OpenVMS STR$ string manipulation run-time library for x86 and ARM Linux. It is not a standalone product or daemon: it is a set of callable routines compiled into the shared VX/RT archive libvxrt.a. A migrated OpenVMS application, whether in translated C, COBOL, FORTRAN, BASIC, or Pascal, links against that archive and calls STR$TRIM, STR$CONCAT, and the rest exactly as it did on VMS.

The library covers string manipulation, searching, formatting, and decimal-string numeric operations across the OpenVMS VMS descriptor model, auto-detecting 32-bit and 64-bit descriptors so the same routine handles both. It preserves OpenVMS string handling behavior, including its condition-code return convention, so applications that depend on STR$ services migrate and run without rewriting the calling code.

<!-- Webflow embed (min). Source: str-page-technical/str-tech-body-embed-dark.html → python3 website/embeds/minify_webflow_embed.py … --> <div class="s7-tech-embed" data-vxstrd-body-embed="1" data-technical-mega-embed="1" lang="en"><h2 id="architecture">Architecture: how a STR$ call reaches the code</h2><p> VX/STR$ has no process model to administer. Each OpenVMS <code>STR$xxx</code> entry point is implemented as its own C++ function body, compiled to an object file, and archived into <code>libvxrt.a</code>. The relevant architecture is the symbol and entry-point plumbing that lets a single C++ body answer to VMS-style names and to language-specific calling conventions. The product surface is therefore one archive, roughly 40 built entry points, four external symbol names each, and one descriptor model. </p><div class="table-wrapper"><table><thead><tr><th>Layer</th><th>Mechanism</th></tr></thead><tbody><tr><td>Canonical C++ body</td><td><code>STR_D_&lt;NAME&gt;(...)</code> (for example <code>STR_D_TRIM</code>): the real implementation.</td></tr><tr><td>VMS-style aliases</td><td>An <code>__AliasF4()</code> macro emits assembler <code>.set</code> aliases mapping <code>STR$NAME</code> and <code>str$name</code> to the canonical body.</td></tr><tr><td>FORTRAN entry point</td><td><code>F4_STR_D_&lt;NAME&gt;(...)</code>: unpacks the hidden trailing string-length arguments, builds descriptors, then tail-calls the C++ body.</td></tr><tr><td>FORTRAN aliases</td><td>The same macro maps the trailing-underscore names <code>STR$NAME_</code> and <code>str$name_</code> to the FORTRAN entry.</td></tr><tr><td>Descriptor helpers</td><td><code>vxrt_strptr64</code>, <code>vxrt_dsc64t</code>, <code>vxrt_setlen</code>, and <code>vxrt_is_dsc64</code> resolve 32-bit, 64-bit, and varying descriptors.</td></tr></tbody></table></div><h6>Four external symbols per routine</h6><p> A single alias line at the foot of each source file emits four external symbols. For <code>STR$TRIM</code> the names are <code>STR$TRIM</code> and <code>str$trim</code> (aliases of the C++ body <code>STR_D_TRIM</code>), plus <code>STR$TRIM_</code> and <code>str$trim_</code> (trailing-underscore FORTRAN names, aliases of the FORTRAN thunk <code>F4_STR_D_TRIM</code>). C, C++, and COBOL callers bind to <code>STR$TRIM</code>; FORTRAN callers bind to <code>STR$TRIM_</code>. The alias macro: </p> <pre><span class="cmt">/* Linux build */</span> <span class="kw">#define</span> __MkAlias(sym, alias) \ __asm__(<span class="str">".global "</span> alias); \ __asm__(<span class="str">".set "</span> alias <span class="str">", "</span> sym) <span class="kw">#define</span> __AliasF4(sym, symF, aliasU, aliasL) \ __MkAlias2(#sym, #aliasU, #aliasL); \ __MkAlias2(#symF, #aliasU <span class="str">"_"</span>, #aliasL <span class="str">"_"</span>)</pre> <div class="callout"><strong>One body, four names.</strong> <code>F4_STR_D_NAME</code> is the internal FORTRAN thunk symbol, not itself a public alias. The four externally visible names are <code>STR$NAME</code>, <code>str$name</code>, <code>STR$NAME_</code>, and <code>str$name_</code>. On Apple and Cygwin builds the macro prepends a leading underscore to every symbol; production Linux builds use the plain names. </div><h6>The canonical call pattern</h6><p> Every routine follows the same shape: validate the pointers, resolve the source string through <code>vxrt_strptr64</code> (which is aware of 32-bit, 64-bit, and varying descriptors), do the work into a scratch buffer, then funnel the result through <code>STR$COPY_R64</code>, the universal class-aware copy primitive. </p><style>.s7-tech-embed[data-vxstrd-body-embed] .call-flow {margin: 1.5rem 0;padding: 16px;border: 1px solid var(--s7t-border);border-radius: 10px;background: rgba(255, 255, 255, 0.01);}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint {display: grid;grid-template-columns: 76px minmax(0, 1fr);gap: 14px;align-items: center;padding: 14px 16px;border-radius: 8px;background: var(--s7t-surface);}.s7-tech-embed[data-vxstrd-body-embed] .call-return {border: 1px solid var(--s7t-note-bd);background: var(--s7t-note-bg);}.s7-tech-embed[data-vxstrd-body-embed] .call-label,.s7-tech-embed[data-vxstrd-body-embed] .call-number {font-family: var(--s7t-mono);color: var(--s7t-accent);font-size: 0.76em;font-weight: 700;letter-spacing: 0.08em;text-transform: uppercase;}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint strong,.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint span:last-child,.s7-tech-embed[data-vxstrd-body-embed] .call-stage strong,.s7-tech-embed[data-vxstrd-body-embed] .call-stage span:last-child {display: block;}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint strong,.s7-tech-embed[data-vxstrd-body-embed] .call-stage strong {margin-bottom: 4px;color: var(--s7t-text);}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint span:last-child,.s7-tech-embed[data-vxstrd-body-embed] .call-stage span:last-child {color: var(--s7t-muted);font-size: 0.9em;line-height: 1.45;}.s7-tech-embed[data-vxstrd-body-embed] .call-stages {display: grid;grid-template-columns: repeat(4, minmax(0, 1fr));gap: 10px;margin: 10px 0;}.s7-tech-embed[data-vxstrd-body-embed] .call-stage {min-width: 0;padding: 15px;border-top: 3px solid var(--s7t-accent);border-radius: 6px;background: var(--s7t-surface2);}.s7-tech-embed[data-vxstrd-body-embed] .call-stage .call-number {margin-bottom: 10px;}@media (max-width: 900px) {.s7-tech-embed[data-vxstrd-body-embed] .call-stages {grid-template-columns: repeat(2, minmax(0, 1fr));}}@media (max-width: 600px) {.s7-tech-embed[data-vxstrd-body-embed] .call-stages,.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint {grid-template-columns: 1fr;}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint {gap: 5px;}}</style><div class="call-flow" role="group" aria-label="Canonical STR routine call pattern"><div class="call-endpoint"><span class="call-label">Caller</span><div><strong><code>STR$NAME</code></strong><span>Calls <code>STR_D_NAME(dest, source, ...)</code>.</span></div></div><div class="call-stages"><div class="call-stage"><span class="call-number">01</span><strong>Validate</strong><span>Return <code>SS$_ACCVIO</code> immediately if a required pointer is null.</span></div><div class="call-stage"><span class="call-number">02</span><strong>Resolve</strong><span><code>vxrt_strptr64</code> resolves a 32-bit, 64-bit, or varying descriptor.</span></div><div class="call-stage"><span class="call-number">03</span><strong>Transform</strong><span>Perform the routine's work in a scratch buffer.</span></div><div class="call-stage"><span class="call-number">04</span><strong>Copy</strong><span><code>STR_D_COPY_R64(dest, &amp;len, buf)</code> writes through the shared class-aware primitive.</span></div></div><div class="call-endpoint call-return"><span class="call-label">Return</span><div><strong>Condition value returns to the caller</strong><span>The least-significant bit is 1 for success.</span></div></div></div><h2 id="descriptors">The descriptor model</h2><p> The one data structure a caller must understand is the VMS descriptor. Every string argument is a descriptor passed by reference. The runtime auto-detects 32-bit versus 64-bit layout with <code>vxrt_is_dsc64</code>, and the class field selects the storage model. The STR$ routines actively handle three classes: Static (1), Dynamic (2), and Varying (11). Any other class returns <code>STR$_ILLSTRCLA</code>. </p><div class="table-wrapper"><table><thead><tr><th>Class symbol</th><th>Value</th><th>Name</th><th>Storage model</th></tr></thead><tbody><tr><td><code>DSC_sK_CLASS_S</code></td><td>1</td><td>Static</td><td>Fixed buffer; the length field is both capacity and valid length.</td></tr><tr><td><code>DSC_sK_CLASS_D</code></td><td>2</td><td>Dynamic</td><td>Runtime-allocated buffer, resizable via <code>STR$GET1_DX</code>, owned by the descriptor.</td></tr><tr><td><code>DSC_sK_CLASS_VS</code></td><td>11</td><td>Varying</td><td>Pascal-style: buffer prefixed by a 16-bit current-length word.</td></tr><tr><td><code>DSC_sK_CLASS_SD</code></td><td>9</td><td>Scaled decimal</td><td>Present in headers; not a STR$ string class.</td></tr><tr><td><code>DSC_sK_CLASS_VSA</code></td><td>12</td><td>Varying array</td><td>Present in headers.</td></tr><tr><td><code>DSC_sK_CLASS_SB</code></td><td>15</td><td>Static bounded</td><td>Present in headers.</td></tr></tbody></table></div><p> The datatype field is normally <code>DSC_sK_DTYPE_T</code> (value 14, text). The high bit <code>0x80</code> of the datatype marks a temporary string handed out by <code>STR$GET_TEMPSTR</code>; it is cleared by <code>STR$COPY_DX</code> when the temporary is consumed. </p><div class="callout"><strong>Descriptor auto-detection is transparent.</strong> Because the runtime detects 32-bit versus 64-bit descriptors at call time, the same routine handles both widths. A 64-bit variant such as <code>STR$COPY_R64</code> exists for callers that build 64-bit descriptors directly, but the class-aware primitive underneath is shared. </div><h2 id="routines">Callable routine catalogue</h2><p> VX/STR$ builds 40 public STR$ entry points into <code>libvxrt.a</code>, covering manipulation, searching, formatting, comparison, descriptor management, and decimal-string arithmetic. Two further routines are present in the source but not built. The status column marks the 40 built routines <span class="ac-live">live</span> and the two decimal-math routines that are not linked into the archive <span class="ac-no">not built</span>. </p><div class="table-wrapper"><table><thead><tr><th>STR$ API</th><th>Function</th><th>Status</th></tr></thead><tbody><tr><td><code>STR$ADD</code></td><td>Decimal-string addition on a sign, exponent, and digit-string triple.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$ANALYZE_SDESC</code></td><td>Extract length and data pointer from a 32-bit-length descriptor.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$ANALYZE_SDESC_64</code></td><td>64-bit-length variant; sets the descriptor-type flag.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$APPEND</code></td><td>In-place append onto a Dynamic or Varying destination.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$BYTES_COPIED</code></td><td>Resolve the number of bytes that fit, <code>min(origlen, dsclen)</code>.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$CASE_BLIND_COMPARE</code></td><td>Case-insensitive compare.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COMPARE</code></td><td>Space-padded byte compare; returns -1, 0, or 1.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COMPARE_EQL</code></td><td>Strict length and content equality (0 equal, 1 not equal).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COMPARE_MULTI</code></td><td>Flag-dispatched blind or exact compare for sorting and validation.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$CONCAT</code></td><td>Variadic append chain, terminated by a <code>-1</code> or NULL argument.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_DX</code></td><td>Descriptor-to-descriptor copy; clears the temporary bit.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_DX_TO_R</code></td><td>Descriptor to C buffer, NUL-terminated.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_R</code></td><td>Class-aware copy primitive (32-bit length).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_R64</code></td><td>64-bit-length copy primitive; the workhorse everything funnels through.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_R0</code></td><td>Copy a NUL-terminated C string into a descriptor.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$DIVIDE</code></td><td>Decimal-string long division, with a truncate or round flag.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$DUPL_CHAR</code></td><td>Fill a destination with N copies of one byte.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$ELEMENT</code></td><td>Extract the Nth delimiter-split token (0-based).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$FIND_FIRST_IN_SET</code></td><td>1-based position of the first character present in a set.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$FIND_FIRST_NOT_IN_SET</code></td><td>1-based position of the first character absent from a set.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$FIND_FIRST_SUBSTRING</code></td><td>Leftmost match of any of several variadic substrings.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$FREE1_DX</code></td><td>Free a Dynamic buffer and zero the descriptor fields.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$GET1_DX</code></td><td>Allocate or reallocate a Dynamic buffer (16-bit length).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$GET1_DX_64</code></td><td>64-bit-length dynamic allocation or reallocation.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$GET_TEMPSTR</code></td><td>Pool-allocate a temporary Static descriptor (up to 2000 concurrent).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$LEFT</code></td><td>Copy the leftmost N characters (position clamped).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$LEN_EXTR</code></td><td>Extract <code>len</code> characters from a 1-based start.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$LENGTH</code></td><td>Effective string length (flag handling defaults only).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$MATCH_WILD</code></td><td>Wildcard match with <code>*</code> and <code>%</code> and backtracking.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$MUL</code></td><td>Decimal-string multiply (see the divergences section for the precision caveat).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$POINTER</code></td><td>Return the bare data pointer for a descriptor.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$POSITION</code></td><td>1-based position of a substring, with optional start.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$POS_EXTR</code></td><td>Copy the substring between a 1-based start and end.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$PREFIX</code></td><td>In-place insert a prefix at the front of a destination.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$REPLACE</code></td><td>Replace a substring: <code>source[..start-1] + repl + source[end+1..]</code>.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$RIGHT</code></td><td>Copy the substring from a 1-based position to the end.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$SETVSLEN</code></td><td>Set the current-length word of a Varying string.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$TRANSLATE</code></td><td>Per-byte translate via match and translation tables.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$TRIM</code></td><td>Copy while dropping trailing spaces and tabs.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$UPCASE</code></td><td>Copy folding lowercase to uppercase.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$RECIP</code></td><td>Decimal-string reciprocal.</td><td><span class="ac-no">not built</span></td></tr><tr><td><code>STR$ROUND</code></td><td>Round or truncate a decimal string to N places.</td><td><span class="ac-no">not built</span></td></tr></tbody></table></div><p> The count is 42 public STR$ names across the source, 40 built into <code>libvxrt.a</code> and 2 not built (<code>STR$RECIP</code> and <code>STR$ROUND</code>). Calling either of the two unbuilt routines produces a link error rather than a run-time failure. </p><h2 id="status">Status and signal conventions</h2><p> Return values are 32-bit VMS condition codes. The success test is the same as on OpenVMS: the least significant bit set (<code>ret &amp; 1</code>) means success. The two common outcomes returned as ordinary values are <code>SS$_NORMAL</code> and the truncation warning <code>STR$_TRU</code>. The table lists the condition codes the library returns, with their VMS-visible names and values. </p><div class="table-wrapper"><table><thead><tr><th>VMS name</th><th>Value</th><th>Meaning</th></tr></thead><tbody><tr><td><code>SS$_NORMAL</code></td><td><code>1</code></td><td>Success.</td></tr><tr><td><code>SS$_ACCVIO</code></td><td><code>12</code></td><td>Access violation (NULL pointer or no buffer).</td></tr><tr><td><code>STR$_TRU</code></td><td><code>0x248200</code></td><td>Truncation warning.</td></tr><tr><td><code>STR$_FATINTERR</code></td><td><code>0x248044</code></td><td>Fatal internal error.</td></tr><tr><td><code>STR$_DIVBY_ZER</code></td><td><code>0x24804C</code></td><td>Divisor is zero.</td></tr><tr><td><code>STR$_ILLSTRCLA</code></td><td><code>0x248054</code></td><td>Illegal string class.</td></tr><tr><td><code>STR$_WRONUMARG</code></td><td><code>0x248064</code></td><td>Wrong number of arguments.</td></tr><tr><td><code>STR$_INSVIRMEM</code></td><td><code>0x24806C</code></td><td>Insufficient virtual memory.</td></tr><tr><td><code>STR$_STRTOOLON</code></td><td><code>0x248074</code></td><td>Source longer than destination (internal).</td></tr><tr><td><code>STR$_ERRFREDYN</code></td><td><code>0x24807C</code></td><td>Error freeing a dynamic string.</td></tr><tr><td><code>STR$_NOMATCH</code></td><td><code>0x248208</code></td><td>Wildcard: no match.</td></tr><tr><td><code>STR$_INVDELIM</code></td><td><code>0x248210</code></td><td>Invalid delimiter.</td></tr><tr><td><code>STR$_NOELEM</code></td><td><code>0x248218</code></td><td>Element does not exist.</td></tr><tr><td><code>STR$_ILLSTRPOS</code></td><td><code>0x248409</code></td><td>Illegal string position.</td></tr><tr><td><code>STR$_ILLSTRSPE</code></td><td><code>0x248411</code></td><td>Illegal start or end specifier.</td></tr><tr><td><code>STR$_MATCH</code></td><td><code>0x248419</code></td><td>Wildcard: matched.</td></tr></tbody></table></div><div class="callout"><strong>Test the low bit, not equality.</strong> Success covers any odd value, so callers should test <code>ret &amp; 1</code> (or <code>ODD(ret)</code> in VMS languages) rather than comparing against a single success constant. A truncation warning is a success value that still delivered a result. </div><h2 id="example">A real call path</h2><p><code>STR$TRIM</code> is small enough to show both entry points, and it is the template every routine in the library follows. The canonical descriptor body validates its pointers, resolves the source through <code>vxrt_strptr64</code>, scans off trailing spaces and tabs, and hands the result to the copy primitive: </p> <pre><span class="cmt">/* canonical descriptor body */</span> U32 STR_D_TRIM( VMS_DESCR *dest, VMS_DESCR *source, U16 *length ) { U64 i, splen; char *s_pointer; <span class="kw">if</span> (!dest || !source) <span class="kw">return</span> SS$_ACCVIO; s_pointer = (char *)vxrt_strptr64(source, &amp;splen); <span class="cmt">/* 32/64/varying aware */</span> <span class="kw">if</span> (!s_pointer) <span class="kw">return</span> SS$_ACCVIO; i = splen; <span class="kw">while</span> (i) { <span class="cmt">/* drop trailing ' ' and '\t' */</span> i--; <span class="kw">if</span> (s_pointer[i] != <span class="str">' '</span> &amp;&amp; s_pointer[i] != <span class="str">'\t'</span>) { i++; <span class="kw">break</span>; } } Msw( length, i ); <span class="cmt">/* store length only if the pointer is non-NULL */</span> <span class="kw">return</span> STR_D_COPY_R64(dest, &amp;i, (void*)s_pointer); }</pre> <h6>The FORTRAN thunk</h6><p> A separate FORTRAN-callable entry unpacks the hidden trailing length arguments the FORTRAN compiler passes for each string, builds descriptors, and tail-calls the same C++ body. A single <code>__AliasF4</code> line then wires up all four public names: </p> <pre>extern <span class="str">"C"</span> U32 F4_STR_D_TRIM( F4STR(dest), F4STR(source), U16 *len, F4STR_N(dest), F4STR_N(source) ) <span class="cmt">/* hidden length args */</span> { F4STR_DECL(dest); F4STR_DECL(source); F4STR_INIT_2(dest, source); <span class="cmt">/* build DESCR64 from ptr + len pairs */</span> <span class="kw">return</span> STR_D_TRIM(dest, source, len); } __AliasF4(STR_D_TRIM, F4_STR_D_TRIM, STR$TRIM, str$trim);</pre> <h6>Variadic routines need a terminator</h6><p><code>STR$CONCAT</code> illustrates the one calling-convention gotcha a migration engineer must know. The C, C++, and COBOL entry walks its argument list until it hits a <code>-1</code> or NULL descriptor, so callers must terminate the list explicitly or the loop reads past the call frame. The FORTRAN thunk instead guesses pointer versus length by the numeric magnitude of each argument, treating values above 32768 as pointers: </p> <pre><span class="cmt">/* C/C++/COBOL entry: list terminated by (VMS_DESCR *)-1 or NULL */</span> U32 STR_D_CONCAT(VMS_DESCR *dst, ...) { va_list ap; U32 ret = SS$_ACCVIO; va_start(ap, dst); <span class="kw">while</span> (1) { void *tstr = va_arg(ap, VMS_DESCR *); <span class="kw">if</span> (tstr == (VMS_DESCR *)-1 || tstr == (VMS_DESCR *)0) <span class="kw">break</span>; ret = STR_D_APPEND(dst, tstr); <span class="cmt">/* APPEND handles Dynamic and Varying */</span> <span class="kw">if</span> (!(ret &amp; 1)) <span class="kw">break</span>; <span class="cmt">/* stop on first failure */</span> } <span class="kw">return</span> ret; } __AliasF4(STR_D_CONCAT, F4_STR_D_CONCAT, STR$CONCAT, str$concat);</pre> <p> The same pattern appears in a migrated application. A translated program links this unchanged against <code>libvxrt.a</code> and tests each result with the VMS <code>ODD()</code> convention: </p> <pre><span class="cmt">! trim an account holder name field and build a display label</span> status = STR$TRIM (TRIMMED_NAME, ACCT_HOLDER_NAME) <span class="kw">IF</span> (.NOT. ODD(status)) <span class="kw">CALL</span> LIB$SIGNAL(status) status = STR$CONCAT (DISPLAY_LABEL, <span class="str">'ACCOUNT: '</span>, ACCT_NUMBER, <span class="str">' '</span>, TRIMMED_NAME) <span class="cmt">! FORTRAN thunk pads to a fixed slot count</span> <span class="kw">IF</span> (.NOT. ODD(status)) <span class="kw">CALL</span> LIB$SIGNAL(status)</pre> <h2 id="behavior">Behavior notes and divergences</h2><p> VX/STR$ aims for bug-for-bug OpenVMS compatibility, and in a few places it deliberately or knowingly diverges from the original specification. These are the behaviors a migration engineer should confirm against their own workload before cutover. </p><div class="table-wrapper"><table><thead><tr><th>Area</th><th>Behavior</th><th>Impact</th></tr></thead><tbody><tr><td><code>STR$RECIP</code>, <code>STR$ROUND</code></td><td>Present in source but not built into the archive.</td><td>Calling either produces a link error. Decimal-math reciprocal and rounding are deferred.</td></tr><tr><td><code>STR$MUL</code></td><td>Uses 64-bit integer multiply rather than digit-by-digit decimal math.</td><td>Loses precision beyond roughly 18 significant digits. Confirm before relying on wide decimal operands.</td></tr><tr><td><code>STR$LENGTH</code></td><td>Flag bits are parsed but only the default length behavior is implemented.</td><td>Non-default flag combinations behave as the default rather than raising an error.</td></tr><tr><td>NULL-pointer paths</td><td>Several routines return <code>SS$_ACCVIO</code> where VMS would raise <code>LIB$_INVARG</code> or <code>STR$_ILLSTRCLA</code>.</td><td>Error codes differ on invalid input, though both are failure values under the odd-success test.</td></tr><tr><td><code>STR$TRANSLATE</code></td><td>Uses a fixed stack scratch buffer of about 100000 bytes with no allocation fallback.</td><td>Source strings larger than roughly 100 KB overflow the stack. Other routines fall back to allocation above the fast path.</td></tr><tr><td><code>STR$ADD</code></td><td>A Varying zero result writes a single-byte Ctrl-A (<code>0x01</code>).</td><td>Intentional bug-for-bug VMS compatibility; matches the original behavior.</td></tr><tr><td><code>STR$COMPARE</code></td><td>Returns a three-valued -1, 0, or 1 with space-padding of the shorter operand.</td><td>Callers that assumed a two-valued result on some historical documentation should test the sign.</td></tr></tbody></table></div><div class="callout warn"><strong>Decimal-math caveat.</strong> The decimal-string arithmetic routines are the area most worth validating: <code>STR$MUL</code> is integer-backed and loses precision on wide operands, and <code>STR$RECIP</code> and <code>STR$ROUND</code> are not built. Workloads that lean on full-precision decimal math should be reviewed as part of the migration. </div><h2 id="limits">Constants, limits, build and test</h2><h6>Real constants and limits</h6><div class="table-wrapper"><table><thead><tr><th>Constant</th><th>Value</th><th>Applies to</th></tr></thead><tbody><tr><td>Max decimal digits per operand</td><td><code>250</code></td><td>Decimal operands in <code>STR$ADD</code> and <code>STR$DIVIDE</code>; over-length returns <code>STR$_TRU</code>.</td></tr><tr><td>Temporary-string pool size</td><td><code>2000</code></td><td>Maximum concurrent <code>STR$GET_TEMPSTR</code> descriptors.</td></tr><tr><td>Stack scratch buffer</td><td><code>100000</code> bytes</td><td>Fast path before allocation fallback (except <code>STR$TRANSLATE</code>, which has no fallback).</td></tr><tr><td>Temporary datatype bit</td><td><code>0x80</code></td><td>Marks a temporary string; cleared by <code>STR$COPY_DX</code>.</td></tr><tr><td>FORTRAN pointer or length threshold</td><td><code>32768</code></td><td>The <code>STR$CONCAT</code> FORTRAN thunk treats values above this as pointers.</td></tr></tbody></table></div><h6>Build</h6><p> The library is built with <code>make</code> in the STR$ source directory, which compiles each source file to an object and archives it into <code>libvxrt.a</code>. A migrated application links against that archive alongside the rest of VX/RT. There is no separate run-time service to install or configure. </p><h6>Regression testing</h6><p> A standalone regression driver exercises the major routines against both 32-bit and 64-bit descriptor classes. It is a separate test program and is not part of the shipped archive. The upstream reference is the OpenVMS RTL String Manipulation Reference Manual, and where the historical documentation disagrees with the current implementation, the implementation is authoritative. </p><h2 id="quickref">Quick reference</h2><h6>Manipulation and formatting</h6><div class="kw-grid"><div>STR$APPEND</div><div>STR$CONCAT</div><div>STR$PREFIX</div><div>STR$REPLACE</div><div>STR$TRIM</div><div>STR$UPCASE</div><div>STR$TRANSLATE</div><div>STR$DUPL_CHAR</div><div>STR$LEFT</div><div>STR$RIGHT</div><div>STR$POS_EXTR</div><div>STR$LEN_EXTR</div><div>STR$ELEMENT</div></div><h6>Searching and comparison</h6><div class="kw-grid"><div>STR$COMPARE</div><div>STR$COMPARE_EQL</div><div>STR$COMPARE_MULTI</div><div>STR$CASE_BLIND_COMPARE</div><div>STR$POSITION</div><div>STR$FIND_FIRST_SUBSTRING</div><div>STR$FIND_FIRST_IN_SET</div><div>STR$FIND_FIRST_NOT_IN_SET</div><div>STR$MATCH_WILD</div><div>STR$LENGTH</div></div><h6>Descriptor and memory management</h6><div class="kw-grid"><div>STR$GET1_DX</div><div>STR$GET1_DX_64</div><div>STR$FREE1_DX</div><div>STR$GET_TEMPSTR</div><div>STR$ANALYZE_SDESC</div><div>STR$ANALYZE_SDESC_64</div><div>STR$COPY_DX</div><div>STR$COPY_DX_TO_R</div><div>STR$COPY_R</div><div>STR$COPY_R64</div><div>STR$COPY_R0</div><div>STR$POINTER</div><div>STR$SETVSLEN</div><div>STR$BYTES_COPIED</div></div><h6>Decimal-string arithmetic</h6><div class="kw-grid"><div>STR$ADD</div><div>STR$DIVIDE</div><div>STR$MUL</div></div><h6>Things to remember</h6><div class="table-wrapper"><table><thead><tr><th>Topic</th><th>Rule</th></tr></thead><tbody><tr><td>Link against</td><td><code>libvxrt.a</code>, the shared VX/RT archive.</td></tr><tr><td>Call any of</td><td><code>STR$NAME</code> or <code>str$name</code> from C, C++, and COBOL; <code>STR$NAME_</code> or <code>str$name_</code> from FORTRAN.</td></tr><tr><td>Copy primitive</td><td>Everything funnels through <code>STR$COPY_R64(dest, &amp;len, src)</code>.</td></tr><tr><td>Classes handled</td><td>Static (1), Dynamic (2), Varying (11); anything else returns <code>STR$_ILLSTRCLA</code>.</td></tr><tr><td>Success test</td><td><code>ret &amp; 1</code>: the low bit set means success.</td></tr><tr><td>Variadic terminator</td><td><code>STR$CONCAT</code> and <code>STR$FIND_FIRST_SUBSTRING</code> need a <code>-1</code> terminator.</td></tr><tr><td>Not built</td><td><code>STR$RECIP</code> and <code>STR$ROUND</code> cause a link error if called.</td></tr><tr><td>Decimal-math caveat</td><td><code>STR$MUL</code> is integer-backed, not full-precision.</td></tr></tbody></table></div></div>

Frequently Asked Questions

Curious about how Sector7 can facilitate your application migration? Explore our FAQs for expert insights.

What is VX/STR$ and how does it help with OpenVMS migration?

VX/STR$ provides a call-compatible implementation of OpenVMS string processing functions for x86 and ARM Linux. It ensures that OpenVMS applications can manipulate, compare, and process strings while preserving compatibility with existing logic and execution behavior, making migration seamless without requiring major code modifications.

For organizations planning their modernization strategy, contact us to discuss how VX/STR$ can support your specific migration requirements.

Which string operations does VX/STR$ support?

VX/STR$ supports comprehensive string manipulation including searching, formatting, numeric operations, concatenation, comparison, trimming, case conversion, and wildcard matching.

How does VX/STR$ integrate with other VX tools?

VX/STR$ seamlessly integrates with VX/RT to provide full OpenVMS string processing capabilities on x86 and ARM Linux platforms.

Does VX/STR$ maintain OpenVMS string handling behavior?

Yes, VX/STR$ maintains OpenVMS string handling behavior, ensuring consistent results across platforms and preserving the exact execution logic of your original applications.

What types of string descriptors does VX/STR$ handle?

VX/STR$ handles various OpenVMS string descriptors including standard descriptors, 64-bit descriptors, and right-aligned descriptors. It provides functions like STR$ANALYZE_SDESC, STR$ANALYZE_SDESC_64, STR$COPY_DX, and STR$GET1_DX_64 for comprehensive descriptor management. Companies like Telstra have successfully leveraged similar string processing capabilities during their migrations.

Can VX/STR$ perform arithmetic operations on string data?

VX/STR$ supports arithmetic operations on string-based numeric data through functions like STR$ADD, STR$DIVIDE, and STR$MUL, maintaining OpenVMS compatibility for numeric string processing.

How does VX/STR$ handle memory management for strings?

VX/STR$ provides descriptor-based memory management through functions like STR$GET1_DX for allocating descriptors, STR$FREE1_DX for releasing memory, and STR$GET_TEMPSTR for temporary string buffers, ensuring proper memory handling consistent with OpenVMS practices.

What is VX/STR$?

VX/STR$ is a call-compatible implementation of the OpenVMS STR$ string processing routines for x86 and ARM Linux. It lets OpenVMS applications manipulate, compare, and process strings while preserving compatibility with existing logic and execution behavior. It ships as callable routines inside the shared VX/RT archive, so migrated code links against it and calls the STR$ routines exactly as it did on VMS.

Which categories of string operations does VX/STR$ cover?

VX/STR$ covers string manipulation, searching, formatting, and decimal-string numeric operations. That spans concatenation, appending and prefixing, comparison including case-blind compare, substring searching and positioning, trimming, case conversion, character translation, and wildcard matching, alongside descriptor and memory management routines.

Can VX/STR$ perform arithmetic on numeric strings?

Yes. It provides STR$ADD and STR$DIVIDE for addition and division on string-based decimal numeric data, and STR$MUL for multiplication. Note that STR$MUL is integer-backed rather than full-precision decimal math, so workloads that rely on wide decimal operands should validate precision as part of the migration.

How does VX/STR$ manage string descriptors and memory?

It offers descriptor-based memory management. STR$GET1_DX and STR$GET1_DX_64 obtain dynamic descriptors, STR$FREE1_DX releases them, STR$GET_TEMPSTR provides a temporary buffer, and STR$ANALYZE_SDESC and STR$ANALYZE_SDESC_64 analyze standard and 64-bit descriptors. The runtime handles Static, Dynamic, and Varying descriptor classes.

Does VX/STR$ support 64-bit string descriptors?

Yes. Routines such as STR$ANALYZE_SDESC_64, STR$GET1_DX_64, and STR$COPY_R64 operate on 64-bit descriptors and strings alongside their 32-bit equivalents. The runtime auto-detects 32-bit versus 64-bit descriptors, so the same routine handles both widths.

On which platforms does VX/STR$ run?

VX/STR$ runs on x86 and ARM Linux. It is delivered inside VX/RT to provide full OpenVMS string processing capabilities on those platforms, and a migrated application links against the shared VX/RT archive to use it.

Will string results match what the application saw on OpenVMS?

VX/STR$ maintains OpenVMS string handling behavior to keep results consistent across platforms, including the VMS condition-code return convention where the low bit set means success. A small number of documented edge cases diverge, so those areas should be validated, but applications that rely on STR$ routines behave the same after migration.

How much application code has to change to adopt VX/STR$?

Little to none for the string handling itself. Because the STR$ routines are call-compatible and exported under the same VMS-visible names, applications that depend on STR$ services migrate without major modifications. This preserves productivity and minimizes development cost and project risk.

Transform Your Legacy Software Today!

Get In Touch
Unlock the potential of your legacy software with our expert migration services.