Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
15aa08e
feat: add v3 of extension api with host callbacks
miljann995 May 27, 2026
ffc94f4
feat: remove extension name from parameters list
miljann995 May 27, 2026
effc829
Revert "feat: remove extension name from parameters list"
miljann995 May 27, 2026
4aa44e8
fix: adjust parameters
miljann995 May 28, 2026
d62a9de
chore: comments
miljann995 May 28, 2026
0adb869
feat: add test
miljann995 May 28, 2026
058e1a0
chore: comments
miljann995 May 28, 2026
6c2ba56
fix: add version check
miljann995 May 28, 2026
7060815
fix: set LogXEvent callback to nullptr if not provided by the host
miljann995 May 28, 2026
7329d03
fix: address nullptr concern
miljann995 May 28, 2026
8d50d54
fix: add version check
miljann995 May 28, 2026
80a7076
fix: address code review comments
miljann995 May 28, 2026
44b3f23
fix: address code review comments
miljann995 May 28, 2026
87b8636
fix: avoid race condition on concurrent cleanups
miljann995 May 28, 2026
dfc2d4d
fix: type mismatch
miljann995 May 28, 2026
01d9889
fix: address race condition concerns
miljann995 May 28, 2026
456aa78
fix: avoid dangling pointer of host callbacks
miljann995 May 28, 2026
011520d
feat: extract trace level into separate header file
miljann995 May 28, 2026
e726883
fix: extract trace level enum in a separate header file
miljann995 May 29, 2026
74d7a5b
fix: use caller-supplied extension name, if provided, or default exte…
miljann995 May 29, 2026
b5c7417
fix: forward compatibility on host callback versions
miljann995 May 29, 2026
2f9a8c9
fix: forward compatibility test
miljann995 May 29, 2026
6c5fbda
fix: adjust managed host callbacks struct to match the native one
miljann995 Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions extension-host/include/sqlextensionhostcallbacks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//*********************************************************************
// Copyright (c) Microsoft Corporation.
//
// Host callbacks protocol between the host and extensions.
// Optional API surface introduced alongside v3 of the Extension API.
//
// @File: sqlextensionhostcallbacks.h
//
//*********************************************************************

#ifndef __SQLEXTENSIONHOSTCALLBACKS
#define __SQLEXTENSIONHOSTCALLBACKS

#include "sqlexternallanguage.h"
#include "sqlextensiontracelevel.h"

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

// Callback function provided by the host for logging XEvents from Extension.
//
typedef void (*PFunc_ExtensionLogXEvent)(
const SQLCHAR *ExtensionName,
SQLULEN ExtensionNameLength,
SQLGUID SessionId,
SQLUSMALLINT TaskId,
SQLUSMALLINT TraceLevel,
SQLINTEGER ErrorCode,
const SQLCHAR *Message,
SQLULEN MessageLength
);

// Host callbacks structure passed from host to Extension.
//
#define SQLEXTENSION_HOST_CALLBACKS_VERSION_1 1
#define SQLEXTENSION_HOST_CALLBACKS_MIN_SUPPORTED_VERSION SQLEXTENSION_HOST_CALLBACKS_VERSION_1

typedef struct _SQLEXTENSION_HOST_CALLBACKS
Comment thread
miljann995 marked this conversation as resolved.
{
// Highest SQLEXTENSION_HOST_CALLBACKS_VERSION_* the host populates.
// Extension must validate before reading version-gated fields.
//
SQLUSMALLINT Version;

// Explicit padding so SizeInBytes is naturally 4-byte aligned regardless
// of compiler packing settings. Must be zero.
//
SQLUSMALLINT Reserved0;

// sizeof(SQLEXTENSION_HOST_CALLBACKS) as the host saw it at build time.
// Extension must validate this is greater or equal the size of every field it intends
// to read. Lets a newer Extension safely run against an older host that supplied a smaller struct.
//
SQLUINTEGER SizeInBytes;

// Version 1 callbacks.
//
PFunc_ExtensionLogXEvent LogXEvent;

// Reserved for future expansion. Zero-initialized by the host. Extension must not read or call these.
//
void *Reserved1;
void *Reserved2;
} SQLEXTENSION_HOST_CALLBACKS;

// Optional API (v3+)
// Receives host callback functions from the host.
//
SQLEXTENSION_INTERFACE
SQLRETURN SetHostCallbacks(
SQLEXTENSION_HOST_CALLBACKS *Callbacks
);

#ifdef __cplusplus
} /* End of extern "C" { */
#endif/* __cplusplus */

#endif/* __SQLEXTENSIONHOSTCALLBACKS */
26 changes: 26 additions & 0 deletions extension-host/include/sqlextensiontracelevel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//*********************************************************************
// Copyright (c) Microsoft Corporation.
//
// ExtensionTraceLevel enum shared between the host and extensions.
//
// @File: sqlextensiontracelevel.h
//
//*********************************************************************

#ifndef __SQLEXTENSIONTRACELEVEL
#define __SQLEXTENSIONTRACELEVEL

// Trace levels for events logged via the LogXEvent host callback.
// Lowest numeric value is the most severe, matching the Windows ETW
// TRACE_LEVEL_* convention.
//
enum ExtensionTraceLevel
{
Extension_Critical = 1,
Extension_Error = 2,
Extension_Warning = 3,
Extension_Information = 4,
Extension_Verbose = 5
};

#endif /* __SQLEXTENSIONTRACELEVEL */
2 changes: 1 addition & 1 deletion extension-host/include/sqlexternallanguage.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
extern "C" {
#endif /* __cplusplus */

Comment thread
miljann995 marked this conversation as resolved.
#define EXTERNAL_LANGUAGE_EXTENSION_API 2
#define EXTERNAL_LANGUAGE_EXTENSION_API 3
Comment thread
miljann995 marked this conversation as resolved.
Comment thread
miljann995 marked this conversation as resolved.

SQLEXTENSION_INTERFACE
SQLUSMALLINT GetInterfaceVersion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <sql.h>
#include "sqlexternallanguage.h"
#include "sqlexternallibrary.h"
#include "sqlextensionhostcallbacks.h"
#include "DotnetEnvironment.h"

// Returns the string with the address of the first character and length
Expand Down Expand Up @@ -142,6 +143,17 @@ SQLEXTENSION_INTERFACE SQLRETURN CleanupSession(SQLGUID sessionId, SQLUSMALLINT
//
SQLEXTENSION_INTERFACE SQLRETURN Cleanup();

// Dotnet environment pointer
// Receives host callback function pointers from the host.
// Optional API, supported since v3 of the Extension API.
//
static DotnetEnvironment* g_dotnet_runtime = nullptr;
SQLEXTENSION_INTERFACE SQLRETURN SetHostCallbacks(
SQLEXTENSION_HOST_CALLBACKS *hostCallbacks);

// Dotnet environment pointer. Defined in nativecsharpextension.cpp.
//
extern DotnetEnvironment* g_dotnet_runtime;

// Host callbacks pointer provided by the host via SetHostCallbacks.
// Defined in nativecsharpextension.cpp.
//
Comment thread
miljann995 marked this conversation as resolved.
extern SQLEXTENSION_HOST_CALLBACKS* g_hostCallbacks;
121 changes: 121 additions & 0 deletions language-extensions/dotnet-core-CSharp/src/managed/CSharpExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,127 @@ public static short GetOutputParam(
});
}

/// <summary>
/// Delegate type matching the host's LogXEvent callback signature.
/// </summary>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LogXEventCallbackDelegate(
byte *extensionName,
ulong extensionNameLength,
Guid sessionId,
ushort taskId,
ushort traceLevel,
int errorCode,
byte *message,
ulong messageLength);

/// <summary>
/// Managed representation of the SQLEXTENSION_HOST_CALLBACKS structure.
/// Must match the native struct layout exactly.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct SqlExtensionHostCallbacks
Comment thread
miljann995 marked this conversation as resolved.
{
// Highest SQLEXTENSION_HOST_CALLBACKS_VERSION_* the host populates.
// Extension must validate before reading version-gated fields.
//
public ushort Version;

// Explicit padding so SizeInBytes is naturally 4-byte aligned regardless
// of compiler packing settings. Must be zero.
//
public ushort Reserved0;

// sizeof(SQLEXTENSION_HOST_CALLBACKS) as the host saw it at build time.
// Extension must validate this is greater or equal the size of every field it intends
// to read. Lets a newer Extension safely run against an older host that supplied a smaller struct.
//
public uint SizeInBytes;

// Version 1 callbacks.
//
public IntPtr LogXEvent;

// Reserved for future expansion. Zero-initialized by the host. Extension must not read or call these.
//
public IntPtr Reserved1;
public IntPtr Reserved2;
}

/// <summary>
/// Minimal SQLEXTENSION_HOST_CALLBACKS version this extension understands.
/// If host callbacks version is greater than this, extension will still parse and read
/// known host callbacks and ignore unknown fields, allowing forward compatibility.
/// </summary>
///
private const ushort MinSupportedHostCallbacksVersion = 1;

/// <summary>
/// This delegate declares the delegate type of SetHostCallbacks.
/// </summary>
public delegate short SetHostCallbacksDelegate(
SqlExtensionHostCallbacks *hostCallbacks);

/// <summary>
/// This method implements SetHostCallbacks API.
/// Receives a pointer to the host callbacks structure, reads the callback
/// function pointers during this call, and stores any needed managed
/// delegates so managed code can call back into the host.
/// </summary>
/// <param name="hostCallbacks">
/// Pointer to the SQLEXTENSION_HOST_CALLBACKS structure provided by the host.
/// </param>
/// <returns>
/// SQL_SUCCESS(0), SQL_ERROR(-1)
/// </returns>
public static short SetHostCallbacks(
SqlExtensionHostCallbacks *hostCallbacks)
{
Logging.Trace("CSharpExtension::SetHostCallbacks");
return ExceptionUtils.WrapError(() =>
Comment thread
miljann995 marked this conversation as resolved.
{
if (hostCallbacks == null)
{
throw new ArgumentNullException(nameof(hostCallbacks));
}

// Validate the struct version before reading any version-gated fields.
//
if (hostCallbacks->Version < MinSupportedHostCallbacksVersion)
{
Logging.Error(
"CSharpExtension::SetHostCallbacks: unsupported host callbacks version: " +
hostCallbacks->Version);

throw new NotSupportedException(
"Unsupported SQLEXTENSION_HOST_CALLBACKS version: " +
hostCallbacks->Version);
}

if (hostCallbacks->LogXEvent != IntPtr.Zero)
{
var logXEvent = Marshal.GetDelegateForFunctionPointer<LogXEventCallbackDelegate>(
hostCallbacks->LogXEvent);
Logging.SetLogXEventCallback(logXEvent);
Comment thread
miljann995 marked this conversation as resolved.
Comment thread
miljann995 marked this conversation as resolved.

Logging.LogXEvent(
extensionName: null,
Guid.Empty,
Comment thread
miljann995 marked this conversation as resolved.
taskId: 0,
traceLevel: Logging.TraceLevel.Information,
errorCode: 0,
"CSharp extension loaded, host callbacks registered (version " + hostCallbacks->Version + ")");
}
Comment thread
miljann995 marked this conversation as resolved.
else
{
// Host opted out of XEvent logging.
// Clear any previously stored delegate.
//
Logging.SetLogXEventCallback(null);
}
});
}

/// <summary>
/// This delegate declares the delegate type of CleanupSession.
/// </summary>
Expand Down
Loading