Teknik Makale

HotXLS'te CALL ve WEBSERVICE Formula Callback Kapısı

Spreadsheet motorları, argümanları fonksiyona vermeden önce değerlendirir. =WEBSERVICE(AUDIT_TOKEN()) tutan bir hücre bu yüzden önce AUDIT_TOKENı çalıştırır ve ev sahibi uygulama AUDIT_TOKENı bir user-defined fonksiyon handlerı üzerinden yanıtlıyorsa, o handler kendi log satırını yazmış, sayacını artırmış ya da bağlantısını açmış olur; daha kimsenin WEBSERVICE kelimesine bakmasına gerek kalmadan. Handlerın içindeki bir blok liste için artık çok geçtir. Argümanlar döndükten sonra dıştaki ada bakan bir liste için de. Reddin dıştaki ad üzerinden, hiçbir argümana dokunulmadan ve ev sahibinin yazdığı hiçbir kod söz almadan önce verilmesi gerekir

Bunun soktuğu ortam sıradandır. Bir servis, yüklenen XLS ya da XLSX dosyalarını kabul eder, sunucuda yeniden hesaplar ve birkaç toplamı geri okur. Yıllar önce biri iki iş fonksiyonu için bir user-function handlerı kaydettirmiş ve yol boyunca tanımadığı her adı bir plugin tablosuna yönlendiren catch-all bir dal eklemiş. Ekibin hiç kimse bir hücreye =WEBSERVICE(...) yazmamış. Yazan, yükleyicidir. O formülü açma, yeniden hesaplama ve kaydetme boyunca bozulmadan tutmak bir dosya sadakati işidir; soket açabilen ya da dosya yazabilen ev sahibi koduna ulaşmasına izin vermek ise bir yetkilendirme kararıdır ve ikisinin sahibi farklı olmalıdır

Korunan bir formula Delphi callbacklerinize nasıl ulaşır?

HotXLS formula motorunda, hesaplayıcının yerel olarak değerlendiremediği her çağrı tek bir ortak dispatch zincirine düşer ve tehlikeli adlar oraya iki yoldan gelir. Bir yol, parserın tanıdığı ama hesaplayıcının uygulaması olmayan gömülü fonksiyonlardır: CALL ile REGISTER.ID gerçek Excel fonksiyon adlarıdır, tertemiz ayrışırlar ve sonra sizin DISCOUNT ya da REGIONRATEinizle aynı fallbacke düşer. Diğer yol, parserın hiç tanımadığı adlardır — WEBSERVICE ya da DDE gibi — bunlar bir kayıtta aynı adlı bir girdiyle eşleşebilir ya da catch-all bir event handlerın eline geçebilir. O fallbackin mekaniği HotXLSin özel fonksiyonları OnUserFunction üzerinden nasıl çözümlediği yazısında işlenir

Zincirin sırası, "bilinmeyen"in ne demek olduğunu belirler. Bir ad, her şeyden önce LAMBDA makinesine gider: LETle bağlı bir closure ya da formülü bir LAMBDA olan bir defined name; ikisi de HotXLS formula motorundaki closure desteğinde anlatılır. Ardından RegisterUserFunctionla kaydedilmiş workbook yerel fonksiyonları, sonra TXLSWorkbook.RegisterGlobalUserFunction sınıf metodundan proses çapındaki fonksiyonlar ve en son olaylar gelir. OnUserFunctionEx atanmışsa çalışır; OnUserFunction yalnızca OnUserFunctionEx yokken çalışır. Gerçekten bilinmeyen bir ad, her aşama vazgeçtiğinde #NAME? olur; tanınan ama uygulanmamış bir gömülü ise bunun yerine unsupported function bildirir

LAMBDA aramasından sonraki her aşama, kontrolü sizin yazdığınız koda devreder. HotXLSin güvenlik kontrolünü, kodunuza ulaşan her aşamanın içine değil önüne koymasının ve kontrolü üç dosya okuyucusunun yerine hesaplayıcının kendisine yerleştirmesinin sebebi budur. Classic BIFF tokenlarından, XLSX metninden ya da bir ODS msoxl: formülünden yüklenen formül aynı ayrışmış ifadeye dönüşür; böylece tek kontrol üç formatı da kapsar ve dönüştürülmüş bir workbook, özgünüyle aynı davranır

HotXLSin güvensiz formula callbacklerini kapatma düzeni: fonksiyon adı, argüman dizisi kurulmadan ve hiçbir resolver çalıştırılmadan reddelme setiyle karşılaştırılır; iç içe =WEBSERVICE(AUDIT_TOKEN()) xlfeUnsafeFunctionDenied ile reddedilir ve denetim kaydı boş kalır, güvenli çağrılar ise zincirde LAMBDA ile LET bağlarından OnUserFunctiona kadar yürür
Gerçekten bilinmeyen bir fonksiyon, her aşama vazgeçtiğinde #NAME? olur; güvenlik kontrolünün herhangi bir handlerın içinde değil, callback aşamalarının önünde durmasının sebebi budur

HotXLS hangi formula adlarını öntanımlı olarak reddediyor?

HotXLS, callbackler açıkça izin verilmedikçe 20 adlı sabit bir seti reddeder: DDE, CALL, REGISTER, REGISTER.ID, WEBSERVICE, RTD, SQL.REQUEST, EXEC, RUN, CREATE.OBJECT, APP.ACTIVATE, SEND.KEYS, OPEN, SAVE, SAVE.AS, FOPEN, FWRITE, FWRITELN, FCLOSE ve FILE.DELETE. Excelde ya da makro dilinde bunlar, yerel kod yükleyen, ağa ulaşan, diğer süreçlerle konuşan ya da dosya sistemine dokunan adlardır. Karşılaştırmadan önce motor, çevredeki boşlukları kırpar, adı büyük harfe çevirir ve tek bir _XLFN. ya da _XLWS. ön ekini soyar. Daha yeni bir Excel sürümünün _xlfn.webservice olarak sakladığı dosya, çıplak yazımla tam olarak aynı şekilde yakalanır

HotXLSin fonksiyon adını güvensiz callback karşılaştırmasından önce nasıl normalize ettiği: boşluklar kırpılır, ad büyük harfe çevrilir ve tek bir _XLFN. ya da _XLWS. öneki soyulur; _xlfn.webservice böylece çıplak yazımı gibi yakalanır ve sonuç, 20 adlı sabit reddelme setiyle tam eşleşme için karşılaştırılır
Liste, yerel kod yükleyen, ağa ulaşan, diğer süreçlerle konuşan ya da dosya sistemine dokunan adları kapsar; DDE, CALL ve WEBSERVICEden FWRITE ile FILE.DELETEe

Eşleşme tam eşleşmedir ve bu iki yönlü keser. MYWEBSERVICE olarak kaydettirdiğiniz bir handler etkilenmez. Rastlantı üzerine OPEN ya da RUN adını taşımış bir kurum içi UDF ise öntanımlı olarak reddedilir ve cazip tamir, o tek ad yeniden çalışsın diye workbookta AllowUnsafeFormulaCallbacksı True yapmaktır. Onun yerine fonksiyonu yeniden adlandırın. Tek bir kurum içi adı kurtarmak için bütün workbooku oyuna sokmak, o dosyadaki her formül için — sizin yazmadıklarınız dâhil — CALL, DDE ve FILE.DELETEyi de yeniden açar

Reddedme seti ayrıca kendi handlerlarınızı da çevrelemez. Catch-all bir dal rastgele plugin adlarını çalıştırıyorsa kapı, iyi bilinen 20 tehlikeliyi durdurur ve gerisinin tamamını kodunuza geçirir. Kalıcı cevap, catch-all handler yazmayı bırakmaktır. Her fonksiyonu adıyla RegisterUserFunction ile kaydetmek, kayıt defterinin kendisini bir izin listesine çevirir ve formula giriş kontrolünü anlamlı tutar: OnUserFunction ya da OnUserFunctionEx atandığında motor, olayın hangi adları sahipleneceğini bilemez ve giriş doğrulaması bilinmeyen fonksiyon adlarını işaretlemeyi tamamen bırakır

procedure TImportService.WireFunctions;
begin
  // Ad başına bir kayıt: kayıt defteri izin listesidir
  FBook.RegisterUserFunction('DISCOUNT', HandleDiscount);
  FBook.RegisterUserFunction('REGIONRATE', HandleRegionRate);
  // İki olayı da atamadan bırakın; bilinmeyen adlar hâlâ girişte reddedilsin
  // ve değerlendirmede #NAME? olsun
  FBook.OnUserFunction := nil;
  FBook.OnUserFunctionEx := nil;
end;

procedure TImportService.HandleDiscount(Sender: TObject;
  const FunctionName: WideString; const Args: Variant;
  var Value: Variant; var Handled: Boolean);
begin
  // Args, çoktan değerlendirilmiş argümanların sıfır tabanlı bir variant dizisidir
  if VarIsArray(Args) and (VarArrayHighBound(Args, 1) = 1) then
  begin
    Value := Args[0] * (1 - Args[1]);
    Handled := True;
  end;
end;

Kapı neden argüman değerlendirmesinden önce çalışmak zorunda?

HotXLS fonksiyon adını önce kontrol eder ve çağrıyı, argüman dizisini kurmadan, bir resolvera ya da iki kayıttan herhangi birine danışmadan, hatta hiçbir handler atanmamış olduğunu fark etmeden önce reddeder. Bu sıralama, açılıştaki iç içe formülü alt eden şeydir: dıştaki WEBSERVICE, AUDIT_TOKEN hâlâ değerlendirilmemiş bir çocuk düğümken reddedilir; ev sahibi handlerı hiç çalışmaz. Sıralamanın son parçası kayıt tutma için önemlidir. Hiç handlerı olmayan bir workbook ile beş handlerı olan bir workbook, =WEBSERVICE(...) için aynı durumu bildirir; böylece bir red kaydı, sonradan biri handler bağladığında anlam değiştirmez

procedure TImportService.HandleUdf(Sender: TObject;
  const FunctionName: WideString; const Args: Variant;
  var Value: Variant; var Handled: Boolean);
begin
  if SameText(FunctionName, 'AUDIT_TOKEN') then
  begin
    FAuditLog.Add('AUDIT_TOKEN evaluated');   // ev sahibi kodundaki yan etki
    Value := 'token-42';
    Handled := True;
  end;
end;

procedure TImportService.ProbeNestedCall;
var
  Sheet: TXLSWorksheet;
  Eval: TXLSFormulaEvaluationResult;
begin
  FBook.OnUserFunction := HandleUdf;
  Sheet := FBook.Sheets[1];                  // worksheet indeksleri 1den başlar
  Eval := Sheet.EvaluateFormulaAt(1, 1, '=WEBSERVICE(AUDIT_TOKEN())');
  // Beklenen: Eval.Status = xlfeUnsafeFunctionDenied, Eval.Value = Null,
  // Eval.Issue.NativeCode = -106 ve FAuditLog.Count = 0
end;

Yerel kod -106, herkese açık lxErrorUnsafeFunctionDenied sabitidir ve Eval.Issue.Message "The formula callback is blocked by the safety policy" yazar. xlfeUnsafeFunctionDenied, xlfeFailedden sonra TXLSFormulaEvaluationStatusün son üyesidir. Durumları bir case ve genel başarısızlık için bir else dalıyla eşleyen kod, reddi açıkça adlandırmadıkça "evaluation failed" altına dosyalar; bir yükleme hattı için geriye bildirilmeye değer tek sonuç budur

Çalışma kitabı varsayılanı mı, çağrı başına TXLSFormulaEvaluationOptions mı?

Workbook bayrağı öntanımlıyı kurar, çağrı başına seçenek son sözü söyler. TXLSWorkbook.AllowUnsafeFormulaCallbacks ve TXLSXWorkbook.AllowUnsafeFormulaCallbacks Falsela başlar ve sıradan yeniden hesaplamayı, Calculateı, üç argümanlı EvaluateFormulaAtı, bir değerlendirme şablonu üzerindeki EvaluateAtı, veri doğrulama liste formüllerini, salt okunur viewları ve XLSXte paralel yeniden hesaplama havuzundaki her workerı yönetir. Açık bir TXLSFormulaEvaluationOptions kaydı alan her giriş noktası, o çağrı için kararı Options.AllowUnsafeFormulaCallbacksdan alır ve onu workbook özelliğiyle OR yapmaz

Bu asimetri bilinçlidir ve doğru öntanımlıdır. Güvenilir bir iç iş, bütün workbooku çevirmeden tek bir RTD sorgusuna yetki verebilir; global olarak oyuna sokulmuş bir workbook, hassas bir değerlendirmeyi yine de rede zorlayabilir. XLSDefaultFormulaEvaluationOptions bayrağı False döndürür; kaydı o fonksiyondan kurmak, bilerek kurmadıkça red demektir. Bayrağı tümüyle yok sayan tek yol vardır: Classic koşullu biçimlendirme kuralları render için değerlendirilirken callbackler, workbook ne derse desin reddedilir

var
  Sheet: TXLSWorksheet;
  Options: TXLSFormulaEvaluationOptions;
  Eval: TXLSFormulaEvaluationResult;
begin
  Sheet := FBook.Sheets[1];

  // workbook kilitli kalır, tek güvenilir çağrı geçer
  FBook.AllowUnsafeFormulaCallbacks := False;
  Options := XLSDefaultFormulaEvaluationOptions;
  Options.AllowUnsafeFormulaCallbacks := True;
  Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);

  // workbook oyuna sokuldu ama yüklenen metnin bu değerlendirmesi sokulmadı
  FBook.AllowUnsafeFormulaCallbacks := True;
  Options := XLSDefaultFormulaEvaluationOptions;   // bayrak yeniden False
  Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
  if Eval.Status = xlfeUnsafeFunctionDenied then
    LogRejected(Eval.Issue.Message);   // LogRejected kendi rutininiz
end;

Workbook özelliğini değiştirmek, her iki workbook sınıfında da bağımlılık grafiğindeki her formülü kirli işaretler. Olmasaydı, callbacklere izin varken önbelleğe alınmış bir değer, izin geri çekildikten sonra servis edilebilir; ya da önbelleğe alınmış bir red, bir opt-inden uzun yaşayabilirdi. Özelliği tuttuğu değere atamak no-opdur ve önbelleğe dokunmaz; dolayısıyla döngü içinde çevirmek, yalnızca değer gerçekten değiştiğinde tam bir yeniden hesaplama maliyeti doğurur

Bilinmeyen ve güvensiz formula metnine kaydetme sırasında ne olur?

Bir formülü saklamak ile onu çalıştırmak iki ayrı sorudur ve giriş politikası yalnızca ilkini yanıtlar. Her iki workbook sınıfında da FormulaEntryPolicy, UnknownFunctionMode ve UnknownNameMode taşır; ikisi de xlfusmRejecte öntanımlıdır. Bilinmeyen bir çağrı taşıyan bir formülü normal Formula özelliğiyle atamak, hücre değeri, formula cache ve bağımlılıklar değişmeden önce "Formula entry rejected: unknown function" ile bir EArgumentException fırlatır. ValidateFormulaEntry aynı kararı, yan etki olmadan bir kayıt olarak döndürür. Dosya yüklemek, kopyalamak ve format dönüşümü gibi güvenilir yollar user-entry politikasından geçmez; katı bir öntanımlı, yalnızca açtığınız bir dosyada zaten var olan simgeleri asla reddetmemelidir

var
  Sheet: TXLSWorksheet;
  Policy: TXLSFormulaEntryPolicy;
  Check: TXLSFormulaEntryValidationResult;
begin
  Sheet := FBook.Sheets[1];
  Check := Sheet.ValidateFormulaEntry(3, 1, '=ACME_RATE(B3)');
  // öntanımlı politika, ACME_RATE için handler yok:
  // Check.Issue = xlfeiUnknownFunction, Check.Action = xlfeaReject

  Policy := FBook.FormulaEntryPolicy;
  Policy.UnknownFunctionMode := xlfusmPreserve;   // uyumluluk girişi
  FBook.FormulaEntryPolicy := Policy;
  Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)';  // saklandı, yetkilendirilmedi
  FBook.SaveAs('rates.xls');
end;

Classic BIFF8in rastgele bir fonksiyon adı için tokenı yoktur; HotXLS bilinmeyen bir çağrıyı, Excelin eklenti fonksiyonlarını yazdığı şekilde yazar. Formül, XTI girdisi her iki sayfa indeksi $FFFEe kurulu eklenti SUPBOOKunu işaret eden bir PtgNameX tokenı ($59) alır; ardından argüman tokenları ve 255 fonksiyon numarası ile ad slotunu da sayan bir argüman adedi taşıyan bir PtgFuncVar gelir. Arkasındaki ExternName gövdesi altı sıfır bayt, bir uzunluk baytı ve Unicode bayrağı, UTF-16 fonksiyon adı, sonra da #REF! taşıyan bir PtgErr olan $1C $17lik iki baytlık bir formüldür. Yazıcı, 1 ile 255 karakter arası adları ve en fazla 29 argümanı kabul eder ve yalnızca BIFF8i hedefler; bir BIFF5 kaydı çağrıyı taşıyamaz. Bu eklenti SUPBOOK girdilerinin dış workbook bağlantılarının yanında nasıl durduğu, BIFF dış bağlantıları için SUPBOOK ve XTI sınıflandırma kurallarında açıklanır

HotXLS bilinmeyen bir formula çağrısını classic BIFF8'e nasıl yazar: formula, XTI girdisi her iki sheet indeksi de $FFFE olan add-in SUPBOOK'una işaret eden bir PtgNameX token'ı taşır; ardından argüman token'ları ve fonksiyon numarası 255 olan bir PtgFuncVar gelir, arkasında ise $1C $17 iki baytlık, #REF! taşıyan PtgErr ile biten bir ExternName gövdesi vardır
XLSX ham fonksiyon metnini korur, ODS kendi msoxl: formülünü korur; =WEBSERVICE(...) kaydetmiş bir dosya metni sağlam biçimde yeniden açılır ve varsayılan olarak yine xlfeUnsafeFunctionDenied değerlendirilir

XLSX ham fonksiyon metnini saklar, ODS ise msoxl: formülünü. Üç formatta da =WEBSERVICE(...) kaydeden bir dosya, adı yerinde olarak yeniden açılır ve öntanımlı değerlendirmesi, ev sahibini çağırmadan xlfeUnsafeFunctionDenied döndürür. Kaydetme, bir formülün izinlerini asla yükseltmez: gidiş-dönüşten önce reddedilen aynı metin, sonra da reddedilir

Kendi yazmadığı workbookları yeniden hesaplayan bir sunucu için ayakta kalan kurulum kısadır: AllowUnsafeFormulaCallbacksı False bırakın, fonksiyonları catch-all bir olay atamak yerine adıyla kaydedin ve çağrı başına seçenekleri yalnızca formül metni sizin kodunuzdan geliyorsa geçin. Callback, giriş politikası ve değerlendirme referansı, HotXLS Delphi spreadsheet bileşeni belgeleriyle gelir